1use super::{BuildRunner, Job, Unit, Work, fingerprint, get_dynamic_search_path};
35use crate::core::compiler::CompileMode;
36use crate::core::compiler::artifact;
37use crate::core::compiler::build_runner::UnitHash;
38use crate::core::compiler::fingerprint::DirtyReason;
39use crate::core::compiler::job_queue::JobState;
40use crate::core::{PackageId, Target, profiles::ProfileRoot};
41use crate::util::errors::CargoResult;
42use crate::util::internal;
43use crate::util::machine_message::{self, Message};
44use anyhow::{Context as _, bail};
45use cargo_platform::Cfg;
46use cargo_util::paths;
47use cargo_util_schemas::manifest::RustVersion;
48use std::collections::hash_map::{Entry, HashMap};
49use std::collections::{BTreeSet, HashSet};
50use std::path::{Path, PathBuf};
51use std::str::{self, FromStr};
52use std::sync::{Arc, Mutex};
53
54const CARGO_ERROR_SYNTAX: &str = "cargo::error=";
59const OLD_CARGO_WARNING_SYNTAX: &str = "cargo:warning=";
64const NEW_CARGO_WARNING_SYNTAX: &str = "cargo::warning=";
69
70#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
71pub enum Severity {
72 Error,
73 Warning,
74}
75
76pub type LogMessage = (Severity, String);
77
78#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
109pub enum LibraryPath {
110 CargoArtifact(PathBuf),
113 External(PathBuf),
116}
117
118impl LibraryPath {
119 fn new(p: PathBuf, script_out_dir: &Path) -> Self {
120 let search_path = get_dynamic_search_path(&p);
121 if search_path.starts_with(script_out_dir) {
122 Self::CargoArtifact(p)
123 } else {
124 Self::External(p)
125 }
126 }
127
128 pub fn into_path_buf(self) -> PathBuf {
129 match self {
130 LibraryPath::CargoArtifact(p) | LibraryPath::External(p) => p,
131 }
132 }
133}
134
135impl AsRef<PathBuf> for LibraryPath {
136 fn as_ref(&self) -> &PathBuf {
137 match self {
138 LibraryPath::CargoArtifact(p) | LibraryPath::External(p) => p,
139 }
140 }
141}
142
143#[derive(Clone, Debug, Hash, Default, PartialEq, Eq, PartialOrd, Ord)]
145pub struct BuildOutput {
146 pub library_paths: Vec<LibraryPath>,
148 pub library_links: Vec<String>,
150 pub linker_args: Vec<(LinkArgTarget, String)>,
152 pub cfgs: Vec<String>,
154 pub check_cfgs: Vec<String>,
156 pub env: Vec<(String, String)>,
158 pub metadata: Vec<(String, String)>,
160 pub rerun_if_changed: Vec<PathBuf>,
163 pub rerun_if_env_changed: Vec<String>,
165 pub log_messages: Vec<LogMessage>,
172}
173
174#[derive(Default)]
185pub struct BuildScriptOutputs {
186 outputs: HashMap<UnitHash, BuildOutput>,
187}
188
189#[derive(Default)]
193pub struct BuildScripts {
194 pub to_link: Vec<(PackageId, UnitHash)>,
211 seen_to_link: HashSet<(PackageId, UnitHash)>,
213 pub plugins: BTreeSet<(PackageId, UnitHash)>,
222}
223
224#[derive(Debug)]
227pub struct BuildDeps {
228 pub build_script_output: PathBuf,
231 pub rerun_if_changed: Vec<PathBuf>,
233 pub rerun_if_env_changed: Vec<String>,
235}
236
237#[derive(Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord)]
246pub enum LinkArgTarget {
247 All,
249 Cdylib,
251 Bin,
253 SingleBin(String),
255 Test,
257 Bench,
259 Example,
261}
262
263impl LinkArgTarget {
264 pub fn applies_to(&self, target: &Target, mode: CompileMode) -> bool {
266 let is_test = mode.is_any_test();
267 match self {
268 LinkArgTarget::All => true,
269 LinkArgTarget::Cdylib => !is_test && target.is_cdylib(),
270 LinkArgTarget::Bin => target.is_bin(),
271 LinkArgTarget::SingleBin(name) => target.is_bin() && target.name() == name,
272 LinkArgTarget::Test => target.is_test(),
273 LinkArgTarget::Bench => target.is_bench(),
274 LinkArgTarget::Example => target.is_exe_example(),
275 }
276 }
277}
278
279#[tracing::instrument(skip_all)]
281pub fn prepare(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<Job> {
282 let metadata = build_runner.get_run_build_script_metadata(unit);
283 if build_runner
284 .build_script_outputs
285 .lock()
286 .unwrap()
287 .contains_key(metadata)
288 {
289 fingerprint::prepare_target(build_runner, unit, false)
291 } else {
292 build_work(build_runner, unit)
293 }
294}
295
296fn emit_build_output(
299 state: &JobState<'_, '_>,
300 output: &BuildOutput,
301 out_dir: &Path,
302 package_id: PackageId,
303) -> CargoResult<()> {
304 let library_paths = output
305 .library_paths
306 .iter()
307 .map(|l| l.as_ref().display().to_string())
308 .collect::<Vec<_>>();
309
310 let msg = machine_message::BuildScript {
311 package_id: package_id.to_spec(),
312 linked_libs: &output.library_links,
313 linked_paths: &library_paths,
314 cfgs: &output.cfgs,
315 env: &output.env,
316 out_dir,
317 }
318 .to_json_string();
319 state.stdout(msg)?;
320 Ok(())
321}
322
323fn build_work(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<Job> {
332 assert!(unit.mode.is_run_custom_build());
333 let bcx = &build_runner.bcx;
334 let dependencies = build_runner.unit_deps(unit);
335 let build_script_unit = dependencies
336 .iter()
337 .find(|d| !d.unit.mode.is_run_custom_build() && d.unit.target.is_custom_build())
338 .map(|d| &d.unit)
339 .expect("running a script not depending on an actual script");
340 let script_dir = build_runner.files().build_script_dir(build_script_unit);
341 let script_out_dir = build_runner.files().build_script_out_dir(unit);
342 let script_run_dir = build_runner.files().build_script_run_dir(unit);
343 let build_plan = bcx.build_config.build_plan;
344 let invocation_name = unit.buildkey();
345
346 if let Some(deps) = unit.pkg.manifest().metabuild() {
347 prepare_metabuild(build_runner, build_script_unit, deps)?;
348 }
349
350 let to_exec = script_dir.join(unit.target.name());
352
353 let to_exec = to_exec.into_os_string();
361 let mut cmd = build_runner.compilation.host_process(to_exec, &unit.pkg)?;
362 let debug = unit.profile.debuginfo.is_turned_on();
363 cmd.env("OUT_DIR", &script_out_dir)
364 .env("CARGO_MANIFEST_DIR", unit.pkg.root())
365 .env("CARGO_MANIFEST_PATH", unit.pkg.manifest_path())
366 .env("NUM_JOBS", &bcx.jobs().to_string())
367 .env("TARGET", bcx.target_data.short_name(&unit.kind))
368 .env("DEBUG", debug.to_string())
369 .env("OPT_LEVEL", &unit.profile.opt_level)
370 .env(
371 "PROFILE",
372 match unit.profile.root {
373 ProfileRoot::Release => "release",
374 ProfileRoot::Debug => "debug",
375 },
376 )
377 .env("HOST", &bcx.host_triple())
378 .env("RUSTC", &bcx.rustc().path)
379 .env("RUSTDOC", &*bcx.gctx.rustdoc()?)
380 .inherit_jobserver(&build_runner.jobserver);
381
382 for (var, value) in artifact::get_env(build_runner, dependencies)? {
384 cmd.env(&var, value);
385 }
386
387 if let Some(linker) = &build_runner.compilation.target_linker(unit.kind) {
388 cmd.env("RUSTC_LINKER", linker);
389 }
390
391 if let Some(links) = unit.pkg.manifest().links() {
392 cmd.env("CARGO_MANIFEST_LINKS", links);
393 }
394
395 if let Some(trim_paths) = unit.profile.trim_paths.as_ref() {
396 cmd.env("CARGO_TRIM_PATHS", trim_paths.to_string());
397 }
398
399 for feat in &unit.features {
402 cmd.env(&format!("CARGO_FEATURE_{}", super::envify(feat)), "1");
403 }
404
405 let mut cfg_map = HashMap::new();
406 cfg_map.insert(
407 "feature",
408 unit.features.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
409 );
410 for cfg in bcx.target_data.cfg(unit.kind) {
411 match *cfg {
412 Cfg::Name(ref n) => {
413 cfg_map.insert(n.as_str(), Vec::new());
414 }
415 Cfg::KeyPair(ref k, ref v) => {
416 let values = cfg_map.entry(k.as_str()).or_default();
417 values.push(v.as_str());
418 }
419 }
420 }
421 for (k, v) in cfg_map {
422 if k == "debug_assertions" {
423 continue;
426 }
427 let k = format!("CARGO_CFG_{}", super::envify(k));
430 cmd.env(&k, v.join(","));
431 }
432
433 if let Some(wrapper) = bcx.rustc().wrapper.as_ref() {
435 cmd.env("RUSTC_WRAPPER", wrapper);
436 } else {
437 cmd.env_remove("RUSTC_WRAPPER");
438 }
439 cmd.env_remove("RUSTC_WORKSPACE_WRAPPER");
440 if build_runner.bcx.ws.is_member(&unit.pkg) {
441 if let Some(wrapper) = bcx.rustc().workspace_wrapper.as_ref() {
442 cmd.env("RUSTC_WORKSPACE_WRAPPER", wrapper);
443 }
444 }
445 cmd.env("CARGO_ENCODED_RUSTFLAGS", unit.rustflags.join("\x1f"));
446 cmd.env_remove("RUSTFLAGS");
447
448 if build_runner.bcx.ws.gctx().extra_verbose() {
449 cmd.display_env_vars();
450 }
451
452 let lib_deps = dependencies
458 .iter()
459 .filter_map(|dep| {
460 if dep.unit.mode.is_run_custom_build() {
461 let dep_metadata = build_runner.get_run_build_script_metadata(&dep.unit);
462 Some((
463 dep.unit.pkg.manifest().links().unwrap().to_string(),
464 dep.unit.pkg.package_id(),
465 dep_metadata,
466 ))
467 } else {
468 None
469 }
470 })
471 .collect::<Vec<_>>();
472 let library_name = unit.pkg.library().map(|t| t.crate_name());
473 let pkg_descr = unit.pkg.to_string();
474 let build_script_outputs = Arc::clone(&build_runner.build_script_outputs);
475 let id = unit.pkg.package_id();
476 let output_file = script_run_dir.join("output");
477 let err_file = script_run_dir.join("stderr");
478 let root_output_file = script_run_dir.join("root-output");
479 let host_target_root = build_runner.files().host_dest().to_path_buf();
480 let all = (
481 id,
482 library_name.clone(),
483 pkg_descr.clone(),
484 Arc::clone(&build_script_outputs),
485 output_file.clone(),
486 script_out_dir.clone(),
487 );
488 let build_scripts = build_runner.build_scripts.get(unit).cloned();
489 let json_messages = bcx.build_config.emit_json();
490 let extra_verbose = bcx.gctx.extra_verbose();
491 let (prev_output, prev_script_out_dir) = prev_build_output(build_runner, unit);
492 let metadata_hash = build_runner.get_run_build_script_metadata(unit);
493
494 paths::create_dir_all(&script_dir)?;
495 paths::create_dir_all(&script_out_dir)?;
496
497 let nightly_features_allowed = build_runner.bcx.gctx.nightly_features_allowed;
498 let targets: Vec<Target> = unit.pkg.targets().to_vec();
499 let msrv = unit.pkg.rust_version().cloned();
500 let targets_fresh = targets.clone();
502 let msrv_fresh = msrv.clone();
503
504 let env_profile_name = unit.profile.name.to_uppercase();
505 let built_with_debuginfo = build_runner
506 .bcx
507 .unit_graph
508 .get(unit)
509 .and_then(|deps| deps.iter().find(|dep| dep.unit.target == unit.target))
510 .map(|dep| dep.unit.profile.debuginfo.is_turned_on())
511 .unwrap_or(false);
512
513 let dirty = Work::new(move |state| {
519 paths::create_dir_all(&script_out_dir)
524 .context("failed to create script output directory for build command")?;
525
526 if !build_plan {
531 let build_script_outputs = build_script_outputs.lock().unwrap();
532 for (name, dep_id, dep_metadata) in lib_deps {
533 let script_output = build_script_outputs.get(dep_metadata).ok_or_else(|| {
534 internal(format!(
535 "failed to locate build state for env vars: {}/{}",
536 dep_id, dep_metadata
537 ))
538 })?;
539 let data = &script_output.metadata;
540 for (key, value) in data.iter() {
541 cmd.env(
542 &format!("DEP_{}_{}", super::envify(&name), super::envify(key)),
543 value,
544 );
545 }
546 }
547 if let Some(build_scripts) = build_scripts {
548 super::add_plugin_deps(
549 &mut cmd,
550 &build_script_outputs,
551 &build_scripts,
552 &host_target_root,
553 )?;
554 }
555 }
556
557 if build_plan {
558 state.build_plan(invocation_name, cmd.clone(), Arc::new(Vec::new()));
559 return Ok(());
560 }
561
562 state.running(&cmd);
564 let timestamp = paths::set_invocation_time(&script_run_dir)?;
565 let prefix = format!("[{} {}] ", id.name(), id.version());
566 let mut log_messages_in_case_of_panic = Vec::new();
567 let span = tracing::debug_span!("build_script", process = cmd.to_string());
568 let output = span.in_scope(|| {
569 cmd.exec_with_streaming(
570 &mut |stdout| {
571 if let Some(error) = stdout.strip_prefix(CARGO_ERROR_SYNTAX) {
572 log_messages_in_case_of_panic.push((Severity::Error, error.to_owned()));
573 }
574 if let Some(warning) = stdout
575 .strip_prefix(OLD_CARGO_WARNING_SYNTAX)
576 .or(stdout.strip_prefix(NEW_CARGO_WARNING_SYNTAX))
577 {
578 log_messages_in_case_of_panic.push((Severity::Warning, warning.to_owned()));
579 }
580 if extra_verbose {
581 state.stdout(format!("{}{}", prefix, stdout))?;
582 }
583 Ok(())
584 },
585 &mut |stderr| {
586 if extra_verbose {
587 state.stderr(format!("{}{}", prefix, stderr))?;
588 }
589 Ok(())
590 },
591 true,
592 )
593 .with_context(|| {
594 let mut build_error_context =
595 format!("failed to run custom build command for `{}`", pkg_descr);
596
597 #[allow(clippy::disallowed_methods)]
604 if let Ok(show_backtraces) = std::env::var("RUST_BACKTRACE") {
605 if !built_with_debuginfo && show_backtraces != "0" {
606 build_error_context.push_str(&format!(
607 "\n\
608 note: To improve backtraces for build dependencies, set the \
609 CARGO_PROFILE_{env_profile_name}_BUILD_OVERRIDE_DEBUG=true environment \
610 variable to enable debug information generation.",
611 ));
612 }
613 }
614
615 build_error_context
616 })
617 });
618
619 if let Err(error) = output {
621 insert_log_messages_in_build_outputs(
622 build_script_outputs,
623 id,
624 metadata_hash,
625 log_messages_in_case_of_panic,
626 );
627 return Err(error);
628 }
629 else if log_messages_in_case_of_panic
631 .iter()
632 .any(|(severity, _)| *severity == Severity::Error)
633 {
634 insert_log_messages_in_build_outputs(
635 build_script_outputs,
636 id,
637 metadata_hash,
638 log_messages_in_case_of_panic,
639 );
640 anyhow::bail!("build script logged errors");
641 }
642
643 let output = output.unwrap();
644
645 paths::write(&output_file, &output.stdout)?;
653 paths::set_file_time_no_err(output_file, timestamp);
656 paths::write(&err_file, &output.stderr)?;
657 paths::write(&root_output_file, paths::path2bytes(&script_out_dir)?)?;
658 let parsed_output = BuildOutput::parse(
659 &output.stdout,
660 library_name,
661 &pkg_descr,
662 &script_out_dir,
663 &script_out_dir,
664 nightly_features_allowed,
665 &targets,
666 &msrv,
667 )?;
668
669 if json_messages {
670 emit_build_output(state, &parsed_output, script_out_dir.as_path(), id)?;
671 }
672 build_script_outputs
673 .lock()
674 .unwrap()
675 .insert(id, metadata_hash, parsed_output);
676 Ok(())
677 });
678
679 let fresh = Work::new(move |state| {
683 let (id, library_name, pkg_descr, build_script_outputs, output_file, script_out_dir) = all;
684 let output = match prev_output {
685 Some(output) => output,
686 None => BuildOutput::parse_file(
687 &output_file,
688 library_name,
689 &pkg_descr,
690 &prev_script_out_dir,
691 &script_out_dir,
692 nightly_features_allowed,
693 &targets_fresh,
694 &msrv_fresh,
695 )?,
696 };
697
698 if json_messages {
699 emit_build_output(state, &output, script_out_dir.as_path(), id)?;
700 }
701
702 build_script_outputs
703 .lock()
704 .unwrap()
705 .insert(id, metadata_hash, output);
706 Ok(())
707 });
708
709 let mut job = if build_runner.bcx.build_config.build_plan {
710 Job::new_dirty(Work::noop(), DirtyReason::FreshBuild)
711 } else {
712 fingerprint::prepare_target(build_runner, unit, false)?
713 };
714 if job.freshness().is_dirty() {
715 job.before(dirty);
716 } else {
717 job.before(fresh);
718 }
719 Ok(job)
720}
721
722fn insert_log_messages_in_build_outputs(
725 build_script_outputs: Arc<Mutex<BuildScriptOutputs>>,
726 id: PackageId,
727 metadata_hash: UnitHash,
728 log_messages: Vec<LogMessage>,
729) {
730 let build_output_with_only_log_messages = BuildOutput {
731 log_messages,
732 ..BuildOutput::default()
733 };
734 build_script_outputs.lock().unwrap().insert(
735 id,
736 metadata_hash,
737 build_output_with_only_log_messages,
738 );
739}
740
741impl BuildOutput {
742 pub fn parse_file(
744 path: &Path,
745 library_name: Option<String>,
746 pkg_descr: &str,
747 script_out_dir_when_generated: &Path,
748 script_out_dir: &Path,
749 nightly_features_allowed: bool,
750 targets: &[Target],
751 msrv: &Option<RustVersion>,
752 ) -> CargoResult<BuildOutput> {
753 let contents = paths::read_bytes(path)?;
754 BuildOutput::parse(
755 &contents,
756 library_name,
757 pkg_descr,
758 script_out_dir_when_generated,
759 script_out_dir,
760 nightly_features_allowed,
761 targets,
762 msrv,
763 )
764 }
765
766 pub fn parse(
771 input: &[u8],
772 library_name: Option<String>,
774 pkg_descr: &str,
775 script_out_dir_when_generated: &Path,
776 script_out_dir: &Path,
777 nightly_features_allowed: bool,
778 targets: &[Target],
779 msrv: &Option<RustVersion>,
780 ) -> CargoResult<BuildOutput> {
781 let mut library_paths = Vec::new();
782 let mut library_links = Vec::new();
783 let mut linker_args = Vec::new();
784 let mut cfgs = Vec::new();
785 let mut check_cfgs = Vec::new();
786 let mut env = Vec::new();
787 let mut metadata = Vec::new();
788 let mut rerun_if_changed = Vec::new();
789 let mut rerun_if_env_changed = Vec::new();
790 let mut log_messages = Vec::new();
791 let whence = format!("build script of `{}`", pkg_descr);
792 const RESERVED_PREFIXES: &[&str] = &[
800 "rustc-flags=",
801 "rustc-link-lib=",
802 "rustc-link-search=",
803 "rustc-link-arg-cdylib=",
804 "rustc-cdylib-link-arg=",
805 "rustc-link-arg-bins=",
806 "rustc-link-arg-bin=",
807 "rustc-link-arg-tests=",
808 "rustc-link-arg-benches=",
809 "rustc-link-arg-examples=",
810 "rustc-link-arg=",
811 "rustc-cfg=",
812 "rustc-check-cfg=",
813 "rustc-env=",
814 "warning=",
815 "rerun-if-changed=",
816 "rerun-if-env-changed=",
817 ];
818 const DOCS_LINK_SUGGESTION: &str = "See https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script \
819 for more information about build script outputs.";
820
821 fn has_reserved_prefix(flag: &str) -> bool {
822 RESERVED_PREFIXES
823 .iter()
824 .any(|reserved_prefix| flag.starts_with(reserved_prefix))
825 }
826
827 fn check_minimum_supported_rust_version_for_new_syntax(
828 pkg_descr: &str,
829 msrv: &Option<RustVersion>,
830 flag: &str,
831 ) -> CargoResult<()> {
832 if let Some(msrv) = msrv {
833 let new_syntax_added_in = RustVersion::from_str("1.77.0")?;
834 if !new_syntax_added_in.is_compatible_with(msrv.as_partial()) {
835 let old_syntax_suggestion = if has_reserved_prefix(flag) {
836 format!(
837 "Switch to the old `cargo:{flag}` syntax (note the single colon).\n"
838 )
839 } else if flag.starts_with("metadata=") {
840 let old_format_flag = flag.strip_prefix("metadata=").unwrap();
841 format!(
842 "Switch to the old `cargo:{old_format_flag}` syntax instead of `cargo::{flag}` (note the single colon).\n"
843 )
844 } else {
845 String::new()
846 };
847
848 bail!(
849 "the `cargo::` syntax for build script output instructions was added in \
850 Rust 1.77.0, but the minimum supported Rust version of `{pkg_descr}` is {msrv}.\n\
851 {old_syntax_suggestion}\
852 {DOCS_LINK_SUGGESTION}"
853 );
854 }
855 }
856
857 Ok(())
858 }
859
860 fn parse_directive<'a>(
861 whence: &str,
862 line: &str,
863 data: &'a str,
864 old_syntax: bool,
865 ) -> CargoResult<(&'a str, &'a str)> {
866 let mut iter = data.splitn(2, "=");
867 let key = iter.next();
868 let value = iter.next();
869 match (key, value) {
870 (Some(a), Some(b)) => Ok((a, b.trim_end())),
871 _ => bail!(
872 "invalid output in {whence}: `{line}`\n\
873 Expected a line with `{syntax}KEY=VALUE` with an `=` character, \
874 but none was found.\n\
875 {DOCS_LINK_SUGGESTION}",
876 syntax = if old_syntax { "cargo:" } else { "cargo::" },
877 ),
878 }
879 }
880
881 fn parse_metadata<'a>(
882 whence: &str,
883 line: &str,
884 data: &'a str,
885 old_syntax: bool,
886 ) -> CargoResult<(&'a str, &'a str)> {
887 let mut iter = data.splitn(2, "=");
888 let key = iter.next();
889 let value = iter.next();
890 match (key, value) {
891 (Some(a), Some(b)) => Ok((a, b.trim_end())),
892 _ => bail!(
893 "invalid output in {whence}: `{line}`\n\
894 Expected a line with `{syntax}KEY=VALUE` with an `=` character, \
895 but none was found.\n\
896 {DOCS_LINK_SUGGESTION}",
897 syntax = if old_syntax {
898 "cargo:"
899 } else {
900 "cargo::metadata="
901 },
902 ),
903 }
904 }
905
906 for line in input.split(|b| *b == b'\n') {
907 let line = match str::from_utf8(line) {
908 Ok(line) => line.trim(),
909 Err(..) => continue,
910 };
911 let mut old_syntax = false;
912 let (key, value) = if let Some(data) = line.strip_prefix("cargo::") {
913 check_minimum_supported_rust_version_for_new_syntax(pkg_descr, msrv, data)?;
914 parse_directive(whence.as_str(), line, data, old_syntax)?
916 } else if let Some(data) = line.strip_prefix("cargo:") {
917 old_syntax = true;
918 if has_reserved_prefix(data) {
920 parse_directive(whence.as_str(), line, data, old_syntax)?
921 } else {
922 ("metadata", data)
924 }
925 } else {
926 continue;
928 };
929 let value = value.replace(
931 script_out_dir_when_generated.to_str().unwrap(),
932 script_out_dir.to_str().unwrap(),
933 );
934
935 let syntax_prefix = if old_syntax { "cargo:" } else { "cargo::" };
936 macro_rules! check_and_add_target {
937 ($target_kind: expr, $is_target_kind: expr, $link_type: expr) => {
938 if !targets.iter().any(|target| $is_target_kind(target)) {
939 bail!(
940 "invalid instruction `{}{}` from {}\n\
941 The package {} does not have a {} target.",
942 syntax_prefix,
943 key,
944 whence,
945 pkg_descr,
946 $target_kind
947 );
948 }
949 linker_args.push(($link_type, value));
950 };
951 }
952
953 match key {
955 "rustc-flags" => {
956 let (paths, links) = BuildOutput::parse_rustc_flags(&value, &whence)?;
957 library_links.extend(links.into_iter());
958 library_paths.extend(
959 paths
960 .into_iter()
961 .map(|p| LibraryPath::new(p, script_out_dir)),
962 );
963 }
964 "rustc-link-lib" => library_links.push(value.to_string()),
965 "rustc-link-search" => {
966 library_paths.push(LibraryPath::new(PathBuf::from(value), script_out_dir))
967 }
968 "rustc-link-arg-cdylib" | "rustc-cdylib-link-arg" => {
969 if !targets.iter().any(|target| target.is_cdylib()) {
970 log_messages.push((
971 Severity::Warning,
972 format!(
973 "{}{} was specified in the build script of {}, \
974 but that package does not contain a cdylib target\n\
975 \n\
976 Allowing this was an unintended change in the 1.50 \
977 release, and may become an error in the future. \
978 For more information, see \
979 <https://github.com/rust-lang/cargo/issues/9562>.",
980 syntax_prefix, key, pkg_descr
981 ),
982 ));
983 }
984 linker_args.push((LinkArgTarget::Cdylib, value))
985 }
986 "rustc-link-arg-bins" => {
987 check_and_add_target!("bin", Target::is_bin, LinkArgTarget::Bin);
988 }
989 "rustc-link-arg-bin" => {
990 let (bin_name, arg) = value.split_once('=').ok_or_else(|| {
991 anyhow::format_err!(
992 "invalid instruction `{}{}={}` from {}\n\
993 The instruction should have the form {}{}=BIN=ARG",
994 syntax_prefix,
995 key,
996 value,
997 whence,
998 syntax_prefix,
999 key
1000 )
1001 })?;
1002 if !targets
1003 .iter()
1004 .any(|target| target.is_bin() && target.name() == bin_name)
1005 {
1006 bail!(
1007 "invalid instruction `{}{}` from {}\n\
1008 The package {} does not have a bin target with the name `{}`.",
1009 syntax_prefix,
1010 key,
1011 whence,
1012 pkg_descr,
1013 bin_name
1014 );
1015 }
1016 linker_args.push((
1017 LinkArgTarget::SingleBin(bin_name.to_owned()),
1018 arg.to_string(),
1019 ));
1020 }
1021 "rustc-link-arg-tests" => {
1022 check_and_add_target!("test", Target::is_test, LinkArgTarget::Test);
1023 }
1024 "rustc-link-arg-benches" => {
1025 check_and_add_target!("benchmark", Target::is_bench, LinkArgTarget::Bench);
1026 }
1027 "rustc-link-arg-examples" => {
1028 check_and_add_target!("example", Target::is_example, LinkArgTarget::Example);
1029 }
1030 "rustc-link-arg" => {
1031 linker_args.push((LinkArgTarget::All, value));
1032 }
1033 "rustc-cfg" => cfgs.push(value.to_string()),
1034 "rustc-check-cfg" => check_cfgs.push(value.to_string()),
1035 "rustc-env" => {
1036 let (key, val) = BuildOutput::parse_rustc_env(&value, &whence)?;
1037 if key == "RUSTC_BOOTSTRAP" {
1040 let rustc_bootstrap_allows = |name: Option<&str>| {
1050 let name = match name {
1051 None => return false,
1055 Some(n) => n,
1056 };
1057 #[allow(clippy::disallowed_methods)]
1061 std::env::var("RUSTC_BOOTSTRAP")
1062 .map_or(false, |var| var.split(',').any(|s| s == name))
1063 };
1064 if nightly_features_allowed
1065 || rustc_bootstrap_allows(library_name.as_deref())
1066 {
1067 log_messages.push((Severity::Warning, format!("Cannot set `RUSTC_BOOTSTRAP={}` from {}.\n\
1068 note: Crates cannot set `RUSTC_BOOTSTRAP` themselves, as doing so would subvert the stability guarantees of Rust for your project.",
1069 val, whence
1070 )));
1071 } else {
1072 bail!(
1075 "Cannot set `RUSTC_BOOTSTRAP={}` from {}.\n\
1076 note: Crates cannot set `RUSTC_BOOTSTRAP` themselves, as doing so would subvert the stability guarantees of Rust for your project.\n\
1077 help: If you're sure you want to do this in your project, set the environment variable `RUSTC_BOOTSTRAP={}` before running cargo instead.",
1078 val,
1079 whence,
1080 library_name.as_deref().unwrap_or("1"),
1081 );
1082 }
1083 } else {
1084 env.push((key, val));
1085 }
1086 }
1087 "error" => log_messages.push((Severity::Error, value.to_string())),
1088 "warning" => log_messages.push((Severity::Warning, value.to_string())),
1089 "rerun-if-changed" => rerun_if_changed.push(PathBuf::from(value)),
1090 "rerun-if-env-changed" => rerun_if_env_changed.push(value.to_string()),
1091 "metadata" => {
1092 let (key, value) = parse_metadata(whence.as_str(), line, &value, old_syntax)?;
1093 metadata.push((key.to_owned(), value.to_owned()));
1094 }
1095 _ => bail!(
1096 "invalid output in {whence}: `{line}`\n\
1097 Unknown key: `{key}`.\n\
1098 {DOCS_LINK_SUGGESTION}",
1099 ),
1100 }
1101 }
1102
1103 Ok(BuildOutput {
1104 library_paths,
1105 library_links,
1106 linker_args,
1107 cfgs,
1108 check_cfgs,
1109 env,
1110 metadata,
1111 rerun_if_changed,
1112 rerun_if_env_changed,
1113 log_messages,
1114 })
1115 }
1116
1117 pub fn parse_rustc_flags(
1121 value: &str,
1122 whence: &str,
1123 ) -> CargoResult<(Vec<PathBuf>, Vec<String>)> {
1124 let value = value.trim();
1125 let mut flags_iter = value
1126 .split(|c: char| c.is_whitespace())
1127 .filter(|w| w.chars().any(|c| !c.is_whitespace()));
1128 let (mut library_paths, mut library_links) = (Vec::new(), Vec::new());
1129
1130 while let Some(flag) = flags_iter.next() {
1131 if flag.starts_with("-l") || flag.starts_with("-L") {
1132 let (flag, mut value) = flag.split_at(2);
1136 if value.is_empty() {
1137 value = match flags_iter.next() {
1138 Some(v) => v,
1139 None => bail! {
1140 "Flag in rustc-flags has no value in {}: {}",
1141 whence,
1142 value
1143 },
1144 }
1145 }
1146
1147 match flag {
1148 "-l" => library_links.push(value.to_string()),
1149 "-L" => library_paths.push(PathBuf::from(value)),
1150
1151 _ => unreachable!(),
1153 };
1154 } else {
1155 bail!(
1156 "Only `-l` and `-L` flags are allowed in {}: `{}`",
1157 whence,
1158 value
1159 )
1160 }
1161 }
1162 Ok((library_paths, library_links))
1163 }
1164
1165 pub fn parse_rustc_env(value: &str, whence: &str) -> CargoResult<(String, String)> {
1169 match value.split_once('=') {
1170 Some((n, v)) => Ok((n.to_owned(), v.to_owned())),
1171 _ => bail!("Variable rustc-env has no value in {whence}: {value}"),
1172 }
1173 }
1174}
1175
1176fn prepare_metabuild(
1180 build_runner: &BuildRunner<'_, '_>,
1181 unit: &Unit,
1182 deps: &[String],
1183) -> CargoResult<()> {
1184 let mut output = Vec::new();
1185 let available_deps = build_runner.unit_deps(unit);
1186 let meta_deps: Vec<_> = deps
1188 .iter()
1189 .filter_map(|name| {
1190 available_deps
1191 .iter()
1192 .find(|d| d.unit.pkg.name().as_str() == name.as_str())
1193 .map(|d| d.unit.target.crate_name())
1194 })
1195 .collect();
1196 output.push("fn main() {\n".to_string());
1197 for dep in &meta_deps {
1198 output.push(format!(" {}::metabuild();\n", dep));
1199 }
1200 output.push("}\n".to_string());
1201 let output = output.join("");
1202 let path = unit
1203 .pkg
1204 .manifest()
1205 .metabuild_path(build_runner.bcx.ws.build_dir());
1206 paths::create_dir_all(path.parent().unwrap())?;
1207 paths::write_if_changed(path, &output)?;
1208 Ok(())
1209}
1210
1211impl BuildDeps {
1212 pub fn new(output_file: &Path, output: Option<&BuildOutput>) -> BuildDeps {
1215 BuildDeps {
1216 build_script_output: output_file.to_path_buf(),
1217 rerun_if_changed: output
1218 .map(|p| &p.rerun_if_changed)
1219 .cloned()
1220 .unwrap_or_default(),
1221 rerun_if_env_changed: output
1222 .map(|p| &p.rerun_if_env_changed)
1223 .cloned()
1224 .unwrap_or_default(),
1225 }
1226 }
1227}
1228
1229pub fn build_map(build_runner: &mut BuildRunner<'_, '_>) -> CargoResult<()> {
1251 let mut ret = HashMap::new();
1252 for unit in &build_runner.bcx.roots {
1253 build(&mut ret, build_runner, unit)?;
1254 }
1255 build_runner
1256 .build_scripts
1257 .extend(ret.into_iter().map(|(k, v)| (k, Arc::new(v))));
1258 return Ok(());
1259
1260 fn build<'a>(
1263 out: &'a mut HashMap<Unit, BuildScripts>,
1264 build_runner: &mut BuildRunner<'_, '_>,
1265 unit: &Unit,
1266 ) -> CargoResult<&'a BuildScripts> {
1267 if out.contains_key(unit) {
1270 return Ok(&out[unit]);
1271 }
1272
1273 if unit.mode.is_run_custom_build() {
1275 if let Some(links) = unit.pkg.manifest().links() {
1276 if let Some(output) = unit.links_overrides.get(links) {
1277 let metadata = build_runner.get_run_build_script_metadata(unit);
1278 build_runner.build_script_outputs.lock().unwrap().insert(
1279 unit.pkg.package_id(),
1280 metadata,
1281 output.clone(),
1282 );
1283 }
1284 }
1285 }
1286
1287 let mut ret = BuildScripts::default();
1288
1289 if !unit.target.is_custom_build() && unit.pkg.has_custom_build() {
1291 let script_metas = build_runner
1292 .find_build_script_metadatas(unit)
1293 .expect("has_custom_build should have RunCustomBuild");
1294 for script_meta in script_metas {
1295 add_to_link(&mut ret, unit.pkg.package_id(), script_meta);
1296 }
1297 }
1298
1299 if unit.mode.is_run_custom_build() {
1300 parse_previous_explicit_deps(build_runner, unit);
1301 }
1302
1303 let mut dependencies: Vec<Unit> = build_runner
1308 .unit_deps(unit)
1309 .iter()
1310 .map(|d| d.unit.clone())
1311 .collect();
1312 dependencies.sort_by_key(|u| u.pkg.package_id());
1313
1314 for dep_unit in dependencies.iter() {
1315 let dep_scripts = build(out, build_runner, dep_unit)?;
1316
1317 if dep_unit.target.for_host() {
1318 ret.plugins.extend(dep_scripts.to_link.iter().cloned());
1319 } else if dep_unit.target.is_linkable() {
1320 for &(pkg, metadata) in dep_scripts.to_link.iter() {
1321 add_to_link(&mut ret, pkg, metadata);
1322 }
1323 }
1324 }
1325
1326 match out.entry(unit.clone()) {
1327 Entry::Vacant(entry) => Ok(entry.insert(ret)),
1328 Entry::Occupied(_) => panic!("cyclic dependencies in `build_map`"),
1329 }
1330 }
1331
1332 fn add_to_link(scripts: &mut BuildScripts, pkg: PackageId, metadata: UnitHash) {
1335 if scripts.seen_to_link.insert((pkg, metadata)) {
1336 scripts.to_link.push((pkg, metadata));
1337 }
1338 }
1339
1340 fn parse_previous_explicit_deps(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) {
1342 let script_run_dir = build_runner.files().build_script_run_dir(unit);
1343 let output_file = script_run_dir.join("output");
1344 let (prev_output, _) = prev_build_output(build_runner, unit);
1345 let deps = BuildDeps::new(&output_file, prev_output.as_ref());
1346 build_runner.build_explicit_deps.insert(unit.clone(), deps);
1347 }
1348}
1349
1350fn prev_build_output(
1356 build_runner: &mut BuildRunner<'_, '_>,
1357 unit: &Unit,
1358) -> (Option<BuildOutput>, PathBuf) {
1359 let script_out_dir = build_runner.files().build_script_out_dir(unit);
1360 let script_run_dir = build_runner.files().build_script_run_dir(unit);
1361 let root_output_file = script_run_dir.join("root-output");
1362 let output_file = script_run_dir.join("output");
1363
1364 let prev_script_out_dir = paths::read_bytes(&root_output_file)
1365 .and_then(|bytes| paths::bytes2path(&bytes))
1366 .unwrap_or_else(|_| script_out_dir.clone());
1367
1368 (
1369 BuildOutput::parse_file(
1370 &output_file,
1371 unit.pkg.library().map(|t| t.crate_name()),
1372 &unit.pkg.to_string(),
1373 &prev_script_out_dir,
1374 &script_out_dir,
1375 build_runner.bcx.gctx.nightly_features_allowed,
1376 unit.pkg.targets(),
1377 &unit.pkg.rust_version().cloned(),
1378 )
1379 .ok(),
1380 prev_script_out_dir,
1381 )
1382}
1383
1384impl BuildScriptOutputs {
1385 fn insert(&mut self, pkg_id: PackageId, metadata: UnitHash, parsed_output: BuildOutput) {
1387 match self.outputs.entry(metadata) {
1388 Entry::Vacant(entry) => {
1389 entry.insert(parsed_output);
1390 }
1391 Entry::Occupied(entry) => panic!(
1392 "build script output collision for {}/{}\n\
1393 old={:?}\nnew={:?}",
1394 pkg_id,
1395 metadata,
1396 entry.get(),
1397 parsed_output
1398 ),
1399 }
1400 }
1401
1402 fn contains_key(&self, metadata: UnitHash) -> bool {
1404 self.outputs.contains_key(&metadata)
1405 }
1406
1407 pub fn get(&self, meta: UnitHash) -> Option<&BuildOutput> {
1409 self.outputs.get(&meta)
1410 }
1411
1412 pub fn iter(&self) -> impl Iterator<Item = (&UnitHash, &BuildOutput)> {
1414 self.outputs.iter()
1415 }
1416}