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::job_queue::JobState;
39use crate::core::{PackageId, Target, profiles::ProfileRoot};
40use crate::util::errors::CargoResult;
41use crate::util::internal;
42use crate::util::machine_message::{self, Message};
43use anyhow::{Context as _, bail};
44use cargo_platform::Cfg;
45use cargo_util::paths;
46use cargo_util_schemas::manifest::RustVersion;
47use std::collections::hash_map::{Entry, HashMap};
48use std::collections::{BTreeSet, HashSet};
49use std::path::{Path, PathBuf};
50use std::str;
51use std::sync::{Arc, Mutex};
52
53const CARGO_ERROR_SYNTAX: &str = "cargo::error=";
58const OLD_CARGO_WARNING_SYNTAX: &str = "cargo:warning=";
63const NEW_CARGO_WARNING_SYNTAX: &str = "cargo::warning=";
68
69#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
70pub enum Severity {
71 Error,
72 Warning,
73}
74
75pub type LogMessage = (Severity, String);
76
77#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
108pub enum LibraryPath {
109 CargoArtifact(PathBuf),
112 External(PathBuf),
115}
116
117impl LibraryPath {
118 fn new(p: PathBuf, script_out_dir: &Path) -> Self {
119 let search_path = get_dynamic_search_path(&p);
120 if search_path.starts_with(script_out_dir) {
121 Self::CargoArtifact(p)
122 } else {
123 Self::External(p)
124 }
125 }
126
127 pub fn into_path_buf(self) -> PathBuf {
128 match self {
129 LibraryPath::CargoArtifact(p) | LibraryPath::External(p) => p,
130 }
131 }
132}
133
134impl AsRef<PathBuf> for LibraryPath {
135 fn as_ref(&self) -> &PathBuf {
136 match self {
137 LibraryPath::CargoArtifact(p) | LibraryPath::External(p) => p,
138 }
139 }
140}
141
142#[derive(Clone, Debug, Hash, Default, PartialEq, Eq, PartialOrd, Ord)]
144pub struct BuildOutput {
145 pub library_paths: Vec<LibraryPath>,
147 pub library_links: Vec<String>,
149 pub linker_args: Vec<(LinkArgTarget, String)>,
151 pub cfgs: Vec<String>,
153 pub check_cfgs: Vec<String>,
155 pub env: Vec<(String, String)>,
157 pub metadata: Vec<(String, String)>,
159 pub rerun_if_changed: Vec<PathBuf>,
162 pub rerun_if_env_changed: Vec<String>,
164 pub log_messages: Vec<LogMessage>,
171}
172
173#[derive(Default)]
184pub struct BuildScriptOutputs {
185 outputs: HashMap<UnitHash, BuildOutput>,
186}
187
188#[derive(Default)]
192pub struct BuildScripts {
193 pub to_link: Vec<(PackageId, UnitHash)>,
210 seen_to_link: HashSet<(PackageId, UnitHash)>,
212 pub plugins: BTreeSet<(PackageId, UnitHash)>,
221}
222
223#[derive(Debug)]
226pub struct BuildDeps {
227 pub build_script_output: PathBuf,
230 pub rerun_if_changed: Vec<PathBuf>,
232 pub rerun_if_env_changed: Vec<String>,
234}
235
236#[derive(Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord)]
245pub enum LinkArgTarget {
246 All,
248 Cdylib,
250 Bin,
252 SingleBin(String),
254 Test,
256 Bench,
258 Example,
260}
261
262impl LinkArgTarget {
263 pub fn applies_to(&self, target: &Target, mode: CompileMode) -> bool {
265 let is_test = mode.is_any_test();
266 match self {
267 LinkArgTarget::All => true,
268 LinkArgTarget::Cdylib => !is_test && target.is_cdylib(),
269 LinkArgTarget::Bin => target.is_bin(),
270 LinkArgTarget::SingleBin(name) => target.is_bin() && target.name() == name,
271 LinkArgTarget::Test => target.is_test(),
272 LinkArgTarget::Bench => target.is_bench(),
273 LinkArgTarget::Example => target.is_exe_example(),
274 }
275 }
276}
277
278#[tracing::instrument(skip_all)]
280pub fn prepare(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<Job> {
281 let metadata = build_runner.get_run_build_script_metadata(unit);
282 if build_runner
283 .build_script_outputs
284 .lock()
285 .unwrap()
286 .contains_key(metadata)
287 {
288 fingerprint::prepare_target(build_runner, unit, false)
290 } else {
291 build_work(build_runner, unit)
292 }
293}
294
295fn emit_build_output(
298 state: &JobState<'_, '_>,
299 output: &BuildOutput,
300 out_dir: &Path,
301 package_id: PackageId,
302) -> CargoResult<()> {
303 let library_paths = output
304 .library_paths
305 .iter()
306 .map(|l| l.as_ref().display().to_string())
307 .collect::<Vec<_>>();
308
309 let msg = machine_message::BuildScript {
310 package_id: package_id.to_spec(),
311 linked_libs: &output.library_links,
312 linked_paths: &library_paths,
313 cfgs: &output.cfgs,
314 env: &output.env,
315 out_dir,
316 }
317 .to_json_string();
318 state.stdout(msg)?;
319 Ok(())
320}
321
322fn build_work(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<Job> {
331 assert!(unit.mode.is_run_custom_build());
332 let bcx = &build_runner.bcx;
333 let dependencies = build_runner.unit_deps(unit);
334 let build_script_unit = dependencies
335 .iter()
336 .find(|d| !d.unit.mode.is_run_custom_build() && d.unit.target.is_custom_build())
337 .map(|d| &d.unit)
338 .expect("running a script not depending on an actual script");
339 let script_dir = build_runner.files().build_script_dir(build_script_unit);
340
341 let script_out_dir = if bcx.gctx.cli_unstable().build_dir_new_layout {
342 build_runner.files().out_dir_new_layout(unit)
343 } else {
344 build_runner.files().build_script_out_dir(unit)
345 };
346
347 if let Some(deps) = unit.pkg.manifest().metabuild() {
348 prepare_metabuild(build_runner, build_script_unit, deps)?;
349 }
350
351 let bin_name = if bcx.gctx.cli_unstable().build_dir_new_layout {
353 unit.target.crate_name()
354 } else {
355 unit.target.name().to_string()
356 };
357 let to_exec = script_dir.join(bin_name);
358
359 let to_exec = to_exec.into_os_string();
367 let mut cmd = build_runner.compilation.host_process(to_exec, &unit.pkg)?;
368 let debug = unit.profile.debuginfo.is_turned_on();
369 cmd.env("OUT_DIR", &script_out_dir)
370 .env("CARGO_MANIFEST_DIR", unit.pkg.root())
371 .env("CARGO_MANIFEST_PATH", unit.pkg.manifest_path())
372 .env("NUM_JOBS", &bcx.jobs().to_string())
373 .env("TARGET", bcx.target_data.short_name(&unit.kind))
374 .env("DEBUG", debug.to_string())
375 .env("OPT_LEVEL", &unit.profile.opt_level)
376 .env(
377 "PROFILE",
378 match unit.profile.root {
379 ProfileRoot::Release => "release",
380 ProfileRoot::Debug => "debug",
381 },
382 )
383 .env("HOST", &bcx.host_triple())
384 .env("RUSTC", &bcx.rustc().path)
385 .env("RUSTDOC", &*bcx.gctx.rustdoc()?)
386 .inherit_jobserver(&build_runner.jobserver);
387
388 for (var, value) in artifact::get_env(build_runner, unit, dependencies)? {
390 cmd.env(&var, value);
391 }
392
393 if let Some(linker) = &build_runner.compilation.target_linker(unit.kind) {
394 cmd.env("RUSTC_LINKER", linker);
395 }
396
397 if let Some(links) = unit.pkg.manifest().links() {
398 cmd.env("CARGO_MANIFEST_LINKS", links);
399 }
400
401 if let Some(trim_paths) = unit.profile.trim_paths.as_ref() {
402 cmd.env("CARGO_TRIM_PATHS_SCOPE", trim_paths.to_string());
403 if !trim_paths.is_none() {
404 let pairs = super::trim_paths_remap(build_runner, unit);
405 cmd.env(
406 "CARGO_TRIM_PATHS_REMAP",
407 paths::join_paths(&pairs, "CARGO_TRIM_PATHS_REMAP")?,
408 );
409 }
410 }
411
412 for feat in &unit.features {
415 cmd.env(&format!("CARGO_FEATURE_{}", super::envify(feat)), "1");
416 }
417
418 let mut cfg_map = HashMap::new();
419 cfg_map.insert(
420 "feature",
421 unit.features.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
422 );
423 if unit.profile.debug_assertions {
427 cfg_map.insert("debug_assertions", Vec::new());
428 }
429 for cfg in bcx.target_data.cfg(unit.kind) {
430 match *cfg {
431 Cfg::Name(ref n) => {
432 if n.as_str() == "debug_assertions" {
434 continue;
435 }
436 cfg_map.insert(n.as_str(), Vec::new());
437 }
438 Cfg::KeyPair(ref k, ref v) => {
439 let values = cfg_map.entry(k.as_str()).or_default();
440 values.push(v.as_str());
441 }
442 }
443 }
444 for (k, v) in cfg_map {
445 let k = format!("CARGO_CFG_{}", super::envify(k));
448 cmd.env(&k, v.join(","));
449 }
450
451 if let Some(wrapper) = bcx.rustc().wrapper.as_ref() {
453 cmd.env("RUSTC_WRAPPER", wrapper);
454 } else {
455 cmd.env_remove("RUSTC_WRAPPER");
456 }
457 cmd.env_remove("RUSTC_WORKSPACE_WRAPPER");
458 if build_runner.bcx.ws.is_member(&unit.pkg) {
459 if let Some(wrapper) = bcx.rustc().workspace_wrapper.as_ref() {
460 cmd.env("RUSTC_WORKSPACE_WRAPPER", wrapper);
461 }
462 }
463 cmd.env("CARGO_ENCODED_RUSTFLAGS", unit.rustflags.join("\x1f"));
464 cmd.env_remove("RUSTFLAGS");
465
466 if build_runner.bcx.ws.gctx().extra_verbose() {
467 cmd.display_env_vars();
468 }
469
470 let any_build_script_metadata = bcx.gctx.cli_unstable().any_build_script_metadata;
471
472 let lib_deps = dependencies
478 .iter()
479 .filter_map(|dep| {
480 if dep.unit.mode.is_run_custom_build() {
481 let dep_metadata = build_runner.get_run_build_script_metadata(&dep.unit);
482
483 let dep_name = dep.dep_name.unwrap_or(dep.unit.pkg.name());
484
485 Some((
486 dep_name,
487 dep.unit
488 .pkg
489 .manifest()
490 .links()
491 .map(|links| links.to_string()),
492 dep.unit.pkg.package_id(),
493 dep_metadata,
494 ))
495 } else {
496 None
497 }
498 })
499 .collect::<Vec<_>>();
500 let library_name = unit.pkg.library().map(|t| t.crate_name());
501 let pkg_descr = unit.pkg.to_string();
502 let build_script_outputs = Arc::clone(&build_runner.build_script_outputs);
503 let id = unit.pkg.package_id();
504 let run_files = BuildScriptRunFiles::for_unit(build_runner, unit);
505 let host_target_root = build_runner.files().host_dest().map(|v| v.to_path_buf());
506 let all = (
507 id,
508 library_name.clone(),
509 pkg_descr.clone(),
510 Arc::clone(&build_script_outputs),
511 run_files.stdout.clone(),
512 script_out_dir.clone(),
513 );
514 let build_scripts = build_runner.build_scripts.get(unit).cloned();
515 let json_messages = bcx.build_config.emit_json();
516 let extra_verbose = bcx.gctx.extra_verbose();
517 let (prev_output, prev_script_out_dir) = prev_build_output(build_runner, unit);
518 let metadata_hash = build_runner.get_run_build_script_metadata(unit);
519
520 paths::create_dir_all(&script_dir)?;
521 paths::create_dir_all(&script_out_dir)?;
522 paths::create_dir_all(&run_files.root)?;
523
524 let nightly_features_allowed = build_runner.bcx.gctx.nightly_features_allowed;
525 let targets: Vec<Target> = unit.pkg.targets().to_vec();
526 let msrv = unit.pkg.rust_version().cloned();
527 let targets_fresh = targets.clone();
529 let msrv_fresh = msrv.clone();
530
531 let env_profile_name = unit.profile.name.to_uppercase();
532 let built_with_debuginfo = build_runner
533 .bcx
534 .unit_graph
535 .get(unit)
536 .and_then(|deps| deps.iter().find(|dep| dep.unit.target == unit.target))
537 .map(|dep| dep.unit.profile.debuginfo.is_turned_on())
538 .unwrap_or(false);
539
540 let dirty = Work::new(move |state| {
546 paths::create_dir_all(&script_out_dir)
551 .context("failed to create script output directory for build command")?;
552
553 {
558 let build_script_outputs = build_script_outputs.lock().unwrap();
559 for (name, links, dep_id, dep_metadata) in lib_deps {
560 let script_output = build_script_outputs.get(dep_metadata).ok_or_else(|| {
561 internal(format!(
562 "failed to locate build state for env vars: {}/{}",
563 dep_id, dep_metadata
564 ))
565 })?;
566 let data = &script_output.metadata;
567 for (key, value) in data.iter() {
568 if let Some(ref links) = links {
569 cmd.env(
570 &format!("DEP_{}_{}", super::envify(&links), super::envify(key)),
571 value,
572 );
573 }
574 if any_build_script_metadata {
575 cmd.env(
576 &format!("CARGO_DEP_{}_{}", super::envify(&name), super::envify(key)),
577 value,
578 );
579 }
580 }
581 }
582 if let Some(build_scripts) = build_scripts
583 && let Some(ref host_target_root) = host_target_root
584 {
585 super::add_plugin_deps(
586 &mut cmd,
587 &build_script_outputs,
588 &build_scripts,
589 host_target_root,
590 )?;
591 }
592 }
593
594 state.running(&cmd);
596 let timestamp = paths::set_invocation_time(&run_files.root)?;
597 let prefix = format!("[{} {}] ", id.name(), id.version());
598 let mut log_messages_in_case_of_panic = Vec::new();
599 let span = tracing::debug_span!("build_script", process = cmd.to_string());
600 let output = span.in_scope(|| {
601 cmd.exec_with_streaming(
602 &mut |stdout| {
603 if let Some(error) = stdout.strip_prefix(CARGO_ERROR_SYNTAX) {
604 log_messages_in_case_of_panic.push((Severity::Error, error.to_owned()));
605 }
606 if let Some(warning) = stdout
607 .strip_prefix(OLD_CARGO_WARNING_SYNTAX)
608 .or(stdout.strip_prefix(NEW_CARGO_WARNING_SYNTAX))
609 {
610 log_messages_in_case_of_panic.push((Severity::Warning, warning.to_owned()));
611 }
612 if extra_verbose {
613 state.stdout(format!("{}{}", prefix, stdout))?;
614 }
615 Ok(())
616 },
617 &mut |stderr| {
618 if extra_verbose {
619 state.stderr(format!("{}{}", prefix, stderr))?;
620 }
621 Ok(())
622 },
623 true,
624 )
625 .with_context(|| {
626 let mut build_error_context =
627 format!("failed to run custom build command for `{}`", pkg_descr);
628
629 #[expect(clippy::disallowed_methods, reason = "consistency with rustc")]
633 if let Ok(show_backtraces) = std::env::var("RUST_BACKTRACE") {
634 if !built_with_debuginfo && show_backtraces != "0" {
635 build_error_context.push_str(&format!(
636 "\n\
637 note: To improve backtraces for build dependencies, set the \
638 CARGO_PROFILE_{env_profile_name}_BUILD_OVERRIDE_DEBUG=true environment \
639 variable to enable debug information generation.",
640 ));
641 }
642 }
643
644 build_error_context
645 })
646 });
647
648 if let Err(error) = output {
650 insert_log_messages_in_build_outputs(
651 build_script_outputs,
652 id,
653 metadata_hash,
654 log_messages_in_case_of_panic,
655 );
656 return Err(error);
657 }
658 else if log_messages_in_case_of_panic
660 .iter()
661 .any(|(severity, _)| *severity == Severity::Error)
662 {
663 insert_log_messages_in_build_outputs(
664 build_script_outputs,
665 id,
666 metadata_hash,
667 log_messages_in_case_of_panic,
668 );
669 anyhow::bail!("build script logged errors");
670 }
671
672 let output = output.unwrap();
673
674 paths::write(&run_files.stdout, &output.stdout)?;
682 paths::set_file_time_no_err(run_files.stdout, timestamp);
685 paths::write(&run_files.stderr, &output.stderr)?;
686 paths::write(&run_files.root_output, paths::path2bytes(&script_out_dir)?)?;
687 let parsed_output = BuildOutput::parse(
688 &output.stdout,
689 library_name,
690 &pkg_descr,
691 &script_out_dir,
692 &script_out_dir,
693 nightly_features_allowed,
694 &targets,
695 &msrv,
696 )?;
697
698 if json_messages {
699 emit_build_output(state, &parsed_output, script_out_dir.as_path(), id)?;
700 }
701 build_script_outputs
702 .lock()
703 .unwrap()
704 .insert(id, metadata_hash, parsed_output);
705 Ok(())
706 });
707
708 let fresh = Work::new(move |state| {
712 let (id, library_name, pkg_descr, build_script_outputs, output_file, script_out_dir) = all;
713 let output = match prev_output {
714 Some(output) => output,
715 None => BuildOutput::parse_file(
716 &output_file,
717 library_name,
718 &pkg_descr,
719 &prev_script_out_dir,
720 &script_out_dir,
721 nightly_features_allowed,
722 &targets_fresh,
723 &msrv_fresh,
724 )?,
725 };
726
727 if json_messages {
728 emit_build_output(state, &output, script_out_dir.as_path(), id)?;
729 }
730
731 build_script_outputs
732 .lock()
733 .unwrap()
734 .insert(id, metadata_hash, output);
735 Ok(())
736 });
737
738 let mut job = fingerprint::prepare_target(build_runner, unit, false)?;
739 if job.freshness().is_dirty() {
740 job.before(dirty);
741 } else {
742 job.before(fresh);
743 }
744 Ok(job)
745}
746
747fn insert_log_messages_in_build_outputs(
750 build_script_outputs: Arc<Mutex<BuildScriptOutputs>>,
751 id: PackageId,
752 metadata_hash: UnitHash,
753 log_messages: Vec<LogMessage>,
754) {
755 let build_output_with_only_log_messages = BuildOutput {
756 log_messages,
757 ..BuildOutput::default()
758 };
759 build_script_outputs.lock().unwrap().insert(
760 id,
761 metadata_hash,
762 build_output_with_only_log_messages,
763 );
764}
765
766impl BuildOutput {
767 pub fn parse_file(
769 path: &Path,
770 library_name: Option<String>,
771 pkg_descr: &str,
772 script_out_dir_when_generated: &Path,
773 script_out_dir: &Path,
774 nightly_features_allowed: bool,
775 targets: &[Target],
776 msrv: &Option<RustVersion>,
777 ) -> CargoResult<BuildOutput> {
778 let contents = paths::read_bytes(path)?;
779 BuildOutput::parse(
780 &contents,
781 library_name,
782 pkg_descr,
783 script_out_dir_when_generated,
784 script_out_dir,
785 nightly_features_allowed,
786 targets,
787 msrv,
788 )
789 }
790
791 pub fn parse(
796 input: &[u8],
797 library_name: Option<String>,
799 pkg_descr: &str,
800 script_out_dir_when_generated: &Path,
801 script_out_dir: &Path,
802 nightly_features_allowed: bool,
803 targets: &[Target],
804 msrv: &Option<RustVersion>,
805 ) -> CargoResult<BuildOutput> {
806 let mut library_paths = Vec::new();
807 let mut library_links = Vec::new();
808 let mut linker_args = Vec::new();
809 let mut cfgs = Vec::new();
810 let mut check_cfgs = Vec::new();
811 let mut env = Vec::new();
812 let mut metadata = Vec::new();
813 let mut rerun_if_changed = Vec::new();
814 let mut rerun_if_env_changed = Vec::new();
815 let mut log_messages = Vec::new();
816 let whence = format!("build script of `{}`", pkg_descr);
817 const RESERVED_PREFIXES: &[&str] = &[
825 "rustc-flags=",
826 "rustc-link-lib=",
827 "rustc-link-search=",
828 "rustc-link-arg-cdylib=",
829 "rustc-cdylib-link-arg=",
830 "rustc-link-arg-bins=",
831 "rustc-link-arg-bin=",
832 "rustc-link-arg-tests=",
833 "rustc-link-arg-benches=",
834 "rustc-link-arg-examples=",
835 "rustc-link-arg=",
836 "rustc-cfg=",
837 "rustc-check-cfg=",
838 "rustc-env=",
839 "warning=",
840 "rerun-if-changed=",
841 "rerun-if-env-changed=",
842 ];
843 const DOCS_LINK_SUGGESTION: &str = "See https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script \
844 for more information about build script outputs.";
845
846 fn has_reserved_prefix(flag: &str) -> bool {
847 RESERVED_PREFIXES
848 .iter()
849 .any(|reserved_prefix| flag.starts_with(reserved_prefix))
850 }
851
852 fn check_minimum_supported_rust_version_for_new_syntax(
853 pkg_descr: &str,
854 msrv: &Option<RustVersion>,
855 flag: &str,
856 ) -> CargoResult<()> {
857 if let Some(msrv) = msrv {
858 let new_syntax_added_in = RustVersion::new(1, 77, 0);
859 if !new_syntax_added_in.is_compatible_with(&msrv.to_partial()) {
860 let old_syntax_suggestion = if has_reserved_prefix(flag) {
861 format!(
862 "Switch to the old `cargo:{flag}` syntax (note the single colon).\n"
863 )
864 } else if flag.starts_with("metadata=") {
865 let old_format_flag = flag.strip_prefix("metadata=").unwrap();
866 format!(
867 "Switch to the old `cargo:{old_format_flag}` syntax instead of `cargo::{flag}` (note the single colon).\n"
868 )
869 } else {
870 String::new()
871 };
872
873 bail!(
874 "the `cargo::` syntax for build script output instructions was added in \
875 Rust 1.77.0, but the minimum supported Rust version of `{pkg_descr}` is {msrv}.\n\
876 {old_syntax_suggestion}\
877 {DOCS_LINK_SUGGESTION}"
878 );
879 }
880 }
881
882 Ok(())
883 }
884
885 fn parse_directive<'a>(
886 whence: &str,
887 line: &str,
888 data: &'a str,
889 old_syntax: bool,
890 ) -> CargoResult<(&'a str, &'a str)> {
891 let mut iter = data.splitn(2, "=");
892 let key = iter.next();
893 let value = iter.next();
894 match (key, value) {
895 (Some(a), Some(b)) => Ok((a, b.trim_end())),
896 _ => bail!(
897 "invalid output in {whence}: `{line}`\n\
898 Expected a line with `{syntax}KEY=VALUE` with an `=` character, \
899 but none was found.\n\
900 {DOCS_LINK_SUGGESTION}",
901 syntax = if old_syntax { "cargo:" } else { "cargo::" },
902 ),
903 }
904 }
905
906 fn parse_metadata<'a>(
907 whence: &str,
908 line: &str,
909 data: &'a str,
910 old_syntax: bool,
911 ) -> CargoResult<(&'a str, &'a str)> {
912 let mut iter = data.splitn(2, "=");
913 let key = iter.next();
914 let value = iter.next();
915 match (key, value) {
916 (Some(a), Some(b)) => Ok((a, b.trim_end())),
917 _ => bail!(
918 "invalid output in {whence}: `{line}`\n\
919 Expected a line with `{syntax}KEY=VALUE` with an `=` character, \
920 but none was found.\n\
921 {DOCS_LINK_SUGGESTION}",
922 syntax = if old_syntax {
923 "cargo:"
924 } else {
925 "cargo::metadata="
926 },
927 ),
928 }
929 }
930
931 for line in input.split(|b| *b == b'\n') {
932 let line = match str::from_utf8(line) {
933 Ok(line) => line.trim(),
934 Err(..) => continue,
935 };
936 let mut old_syntax = false;
937 let (key, value) = if let Some(data) = line.strip_prefix("cargo::") {
938 check_minimum_supported_rust_version_for_new_syntax(pkg_descr, msrv, data)?;
939 parse_directive(whence.as_str(), line, data, old_syntax)?
941 } else if let Some(data) = line.strip_prefix("cargo:") {
942 old_syntax = true;
943 if has_reserved_prefix(data) {
945 parse_directive(whence.as_str(), line, data, old_syntax)?
946 } else {
947 ("metadata", data)
949 }
950 } else {
951 continue;
953 };
954 let value = value.replace(
956 script_out_dir_when_generated.to_str().unwrap(),
957 script_out_dir.to_str().unwrap(),
958 );
959
960 let syntax_prefix = if old_syntax { "cargo:" } else { "cargo::" };
961 macro_rules! check_and_add_target {
962 ($target_kind: expr, $is_target_kind: expr, $link_type: expr) => {
963 if !targets.iter().any(|target| $is_target_kind(target)) {
964 bail!(
965 "invalid instruction `{}{}` from {}\n\
966 The package {} does not have a {} target.",
967 syntax_prefix,
968 key,
969 whence,
970 pkg_descr,
971 $target_kind
972 );
973 }
974 linker_args.push(($link_type, value));
975 };
976 }
977
978 match key {
980 "rustc-flags" => {
981 let (paths, links) = BuildOutput::parse_rustc_flags(&value, &whence)?;
982 library_links.extend(links.into_iter());
983 library_paths.extend(
984 paths
985 .into_iter()
986 .map(|p| LibraryPath::new(p, script_out_dir)),
987 );
988 }
989 "rustc-link-lib" => library_links.push(value.to_string()),
990 "rustc-link-search" => {
991 library_paths.push(LibraryPath::new(PathBuf::from(value), script_out_dir))
992 }
993 "rustc-link-arg-cdylib" | "rustc-cdylib-link-arg" => {
994 if !targets.iter().any(|target| target.is_cdylib()) {
995 log_messages.push((
996 Severity::Warning,
997 format!(
998 "{}{} was specified in the build script of {}, \
999 but that package does not contain a cdylib target\n\
1000 \n\
1001 Allowing this was an unintended change in the 1.50 \
1002 release, and may become an error in the future. \
1003 For more information, see \
1004 <https://github.com/rust-lang/cargo/issues/9562>.",
1005 syntax_prefix, key, pkg_descr
1006 ),
1007 ));
1008 }
1009 linker_args.push((LinkArgTarget::Cdylib, value))
1010 }
1011 "rustc-link-arg-bins" => {
1012 check_and_add_target!("bin", Target::is_bin, LinkArgTarget::Bin);
1013 }
1014 "rustc-link-arg-bin" => {
1015 let (bin_name, arg) = value.split_once('=').ok_or_else(|| {
1016 anyhow::format_err!(
1017 "invalid instruction `{}{}={}` from {}\n\
1018 The instruction should have the form {}{}=BIN=ARG",
1019 syntax_prefix,
1020 key,
1021 value,
1022 whence,
1023 syntax_prefix,
1024 key
1025 )
1026 })?;
1027 if !targets
1028 .iter()
1029 .any(|target| target.is_bin() && target.name() == bin_name)
1030 {
1031 bail!(
1032 "invalid instruction `{}{}` from {}\n\
1033 The package {} does not have a bin target with the name `{}`.",
1034 syntax_prefix,
1035 key,
1036 whence,
1037 pkg_descr,
1038 bin_name
1039 );
1040 }
1041 linker_args.push((
1042 LinkArgTarget::SingleBin(bin_name.to_owned()),
1043 arg.to_string(),
1044 ));
1045 }
1046 "rustc-link-arg-tests" => {
1047 check_and_add_target!("test", Target::is_test, LinkArgTarget::Test);
1048 }
1049 "rustc-link-arg-benches" => {
1050 check_and_add_target!("benchmark", Target::is_bench, LinkArgTarget::Bench);
1051 }
1052 "rustc-link-arg-examples" => {
1053 check_and_add_target!("example", Target::is_example, LinkArgTarget::Example);
1054 }
1055 "rustc-link-arg" => {
1056 linker_args.push((LinkArgTarget::All, value));
1057 }
1058 "rustc-cfg" => cfgs.push(value.to_string()),
1059 "rustc-check-cfg" => check_cfgs.push(value.to_string()),
1060 "rustc-env" => {
1061 let (key, val) = BuildOutput::parse_rustc_env(&value, &whence)?;
1062 if key == "RUSTC_BOOTSTRAP" {
1065 let rustc_bootstrap_allows = |name: Option<&str>| {
1075 let name = match name {
1076 None => return false,
1080 Some(n) => n,
1081 };
1082 #[expect(
1083 clippy::disallowed_methods,
1084 reason = "consistency with rustc, not specified behavior"
1085 )]
1086 std::env::var("RUSTC_BOOTSTRAP")
1087 .map_or(false, |var| var.split(',').any(|s| s == name))
1088 };
1089 if nightly_features_allowed
1090 || rustc_bootstrap_allows(library_name.as_deref())
1091 {
1092 log_messages.push((Severity::Warning, format!("cannot set `RUSTC_BOOTSTRAP={}` from {}.\n\
1093 note: crates cannot set `RUSTC_BOOTSTRAP` themselves, as doing so would subvert the stability guarantees of Rust for your project.",
1094 val, whence
1095 )));
1096 } else {
1097 bail!(
1100 "cannot set `RUSTC_BOOTSTRAP={}` from {}.\n\
1101 note: crates cannot set `RUSTC_BOOTSTRAP` themselves, as doing so would subvert the stability guarantees of Rust for your project.\n\
1102 help: If you're sure you want to do this in your project, set the environment variable `RUSTC_BOOTSTRAP={}` before running cargo instead.",
1103 val,
1104 whence,
1105 library_name.as_deref().unwrap_or("1"),
1106 );
1107 }
1108 } else {
1109 env.push((key, val));
1110 }
1111 }
1112 "error" => log_messages.push((Severity::Error, value.to_string())),
1113 "warning" => log_messages.push((Severity::Warning, value.to_string())),
1114 "rerun-if-changed" => rerun_if_changed.push(PathBuf::from(value)),
1115 "rerun-if-env-changed" => rerun_if_env_changed.push(value.to_string()),
1116 "metadata" => {
1117 let (key, value) = parse_metadata(whence.as_str(), line, &value, old_syntax)?;
1118 metadata.push((key.to_owned(), value.to_owned()));
1119 }
1120 _ => bail!(
1121 "invalid output in {whence}: `{line}`\n\
1122 Unknown key: `{key}`.\n\
1123 {DOCS_LINK_SUGGESTION}",
1124 ),
1125 }
1126 }
1127
1128 Ok(BuildOutput {
1129 library_paths,
1130 library_links,
1131 linker_args,
1132 cfgs,
1133 check_cfgs,
1134 env,
1135 metadata,
1136 rerun_if_changed,
1137 rerun_if_env_changed,
1138 log_messages,
1139 })
1140 }
1141
1142 pub fn parse_rustc_flags(
1146 value: &str,
1147 whence: &str,
1148 ) -> CargoResult<(Vec<PathBuf>, Vec<String>)> {
1149 let value = value.trim();
1150 let mut flags_iter = value
1151 .split(|c: char| c.is_whitespace())
1152 .filter(|w| w.chars().any(|c| !c.is_whitespace()));
1153 let (mut library_paths, mut library_links) = (Vec::new(), Vec::new());
1154
1155 while let Some(flag) = flags_iter.next() {
1156 if flag.starts_with("-l") || flag.starts_with("-L") {
1157 let (flag, mut value) = flag.split_at(2);
1161 if value.is_empty() {
1162 value = match flags_iter.next() {
1163 Some(v) => v,
1164 None => bail! {
1165 "flag in rustc-flags has no value in {}: {}",
1166 whence,
1167 value
1168 },
1169 }
1170 }
1171
1172 match flag {
1173 "-l" => library_links.push(value.to_string()),
1174 "-L" => library_paths.push(PathBuf::from(value)),
1175
1176 _ => unreachable!(),
1178 };
1179 } else {
1180 bail!(
1181 "only `-l` and `-L` flags are allowed in {}: `{}`",
1182 whence,
1183 value
1184 )
1185 }
1186 }
1187 Ok((library_paths, library_links))
1188 }
1189
1190 pub fn parse_rustc_env(value: &str, whence: &str) -> CargoResult<(String, String)> {
1194 match value.split_once('=') {
1195 Some((n, v)) => Ok((n.to_owned(), v.to_owned())),
1196 _ => bail!("Variable rustc-env has no value in {whence}: {value}"),
1197 }
1198 }
1199}
1200
1201fn prepare_metabuild(
1205 build_runner: &BuildRunner<'_, '_>,
1206 unit: &Unit,
1207 deps: &[String],
1208) -> CargoResult<()> {
1209 let mut output = Vec::new();
1210 let available_deps = build_runner.unit_deps(unit);
1211 let meta_deps: Vec<_> = deps
1213 .iter()
1214 .filter_map(|name| {
1215 available_deps
1216 .iter()
1217 .find(|d| d.unit.pkg.name().as_str() == name.as_str())
1218 .map(|d| d.unit.target.crate_name())
1219 })
1220 .collect();
1221 output.push("fn main() {\n".to_string());
1222 for dep in &meta_deps {
1223 output.push(format!(" {}::metabuild();\n", dep));
1224 }
1225 output.push("}\n".to_string());
1226 let output = output.join("");
1227 let path = unit
1228 .pkg
1229 .manifest()
1230 .metabuild_path(build_runner.bcx.ws.build_dir());
1231 paths::create_dir_all(path.parent().unwrap())?;
1232 paths::write_if_changed(path, &output)?;
1233 Ok(())
1234}
1235
1236impl BuildDeps {
1237 pub fn new(output_file: &Path, output: Option<&BuildOutput>) -> BuildDeps {
1240 BuildDeps {
1241 build_script_output: output_file.to_path_buf(),
1242 rerun_if_changed: output
1243 .map(|p| &p.rerun_if_changed)
1244 .cloned()
1245 .unwrap_or_default(),
1246 rerun_if_env_changed: output
1247 .map(|p| &p.rerun_if_env_changed)
1248 .cloned()
1249 .unwrap_or_default(),
1250 }
1251 }
1252}
1253
1254pub fn build_map(build_runner: &mut BuildRunner<'_, '_>) -> CargoResult<()> {
1276 let mut ret = HashMap::new();
1277 for unit in &build_runner.bcx.roots {
1278 build(&mut ret, build_runner, unit)?;
1279 }
1280 build_runner
1281 .build_scripts
1282 .extend(ret.into_iter().map(|(k, v)| (k, Arc::new(v))));
1283 return Ok(());
1284
1285 fn build<'a>(
1288 out: &'a mut HashMap<Unit, BuildScripts>,
1289 build_runner: &mut BuildRunner<'_, '_>,
1290 unit: &Unit,
1291 ) -> CargoResult<&'a BuildScripts> {
1292 if out.contains_key(unit) {
1295 return Ok(&out[unit]);
1296 }
1297
1298 if unit.mode.is_run_custom_build() {
1300 if let Some(links) = unit.pkg.manifest().links() {
1301 if let Some(output) = unit.links_overrides.get(links) {
1302 let metadata = build_runner.get_run_build_script_metadata(unit);
1303 build_runner.build_script_outputs.lock().unwrap().insert(
1304 unit.pkg.package_id(),
1305 metadata,
1306 output.clone(),
1307 );
1308 }
1309 }
1310 }
1311
1312 let mut ret = BuildScripts::default();
1313
1314 if !unit.target.is_custom_build() && unit.pkg.has_custom_build() {
1316 let script_metas = build_runner
1317 .find_build_script_metadatas(unit)
1318 .expect("has_custom_build should have RunCustomBuild");
1319 for script_meta in script_metas {
1320 add_to_link(&mut ret, unit.pkg.package_id(), script_meta);
1321 }
1322 }
1323
1324 if unit.mode.is_run_custom_build() {
1325 parse_previous_explicit_deps(build_runner, unit);
1326 }
1327
1328 let mut dependencies: Vec<Unit> = build_runner
1333 .unit_deps(unit)
1334 .iter()
1335 .map(|d| d.unit.clone())
1336 .collect();
1337 dependencies.sort_by_key(|u| u.pkg.package_id());
1338
1339 for dep_unit in dependencies.iter() {
1340 let dep_scripts = build(out, build_runner, dep_unit)?;
1341
1342 if dep_unit.target.for_host() {
1343 ret.plugins.extend(dep_scripts.to_link.iter().cloned());
1344 } else if dep_unit.target.is_linkable() {
1345 for &(pkg, metadata) in dep_scripts.to_link.iter() {
1346 add_to_link(&mut ret, pkg, metadata);
1347 }
1348 }
1349 }
1350
1351 match out.entry(unit.clone()) {
1352 Entry::Vacant(entry) => Ok(entry.insert(ret)),
1353 Entry::Occupied(_) => panic!("cyclic dependencies in `build_map`"),
1354 }
1355 }
1356
1357 fn add_to_link(scripts: &mut BuildScripts, pkg: PackageId, metadata: UnitHash) {
1360 if scripts.seen_to_link.insert((pkg, metadata)) {
1361 scripts.to_link.push((pkg, metadata));
1362 }
1363 }
1364
1365 fn parse_previous_explicit_deps(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) {
1367 let run_files = BuildScriptRunFiles::for_unit(build_runner, unit);
1368 let (prev_output, _) = prev_build_output(build_runner, unit);
1369 let deps = BuildDeps::new(&run_files.stdout, prev_output.as_ref());
1370 build_runner.build_explicit_deps.insert(unit.clone(), deps);
1371 }
1372}
1373
1374fn prev_build_output(
1380 build_runner: &mut BuildRunner<'_, '_>,
1381 unit: &Unit,
1382) -> (Option<BuildOutput>, PathBuf) {
1383 let script_out_dir = if build_runner.bcx.gctx.cli_unstable().build_dir_new_layout {
1384 build_runner.files().out_dir_new_layout(unit)
1385 } else {
1386 build_runner.files().build_script_out_dir(unit)
1387 };
1388 let run_files = BuildScriptRunFiles::for_unit(build_runner, unit);
1389
1390 let prev_script_out_dir = paths::read_bytes(&run_files.root_output)
1391 .and_then(|bytes| paths::bytes2path(&bytes))
1392 .unwrap_or_else(|_| script_out_dir.clone());
1393
1394 (
1395 BuildOutput::parse_file(
1396 &run_files.stdout,
1397 unit.pkg.library().map(|t| t.crate_name()),
1398 &unit.pkg.to_string(),
1399 &prev_script_out_dir,
1400 &script_out_dir,
1401 build_runner.bcx.gctx.nightly_features_allowed,
1402 unit.pkg.targets(),
1403 &unit.pkg.rust_version().cloned(),
1404 )
1405 .ok(),
1406 prev_script_out_dir,
1407 )
1408}
1409
1410impl BuildScriptOutputs {
1411 fn insert(&mut self, pkg_id: PackageId, metadata: UnitHash, parsed_output: BuildOutput) {
1413 match self.outputs.entry(metadata) {
1414 Entry::Vacant(entry) => {
1415 entry.insert(parsed_output);
1416 }
1417 Entry::Occupied(entry) => panic!(
1418 "build script output collision for {}/{}\n\
1419 old={:?}\nnew={:?}",
1420 pkg_id,
1421 metadata,
1422 entry.get(),
1423 parsed_output
1424 ),
1425 }
1426 }
1427
1428 fn contains_key(&self, metadata: UnitHash) -> bool {
1430 self.outputs.contains_key(&metadata)
1431 }
1432
1433 pub fn get(&self, meta: UnitHash) -> Option<&BuildOutput> {
1435 self.outputs.get(&meta)
1436 }
1437
1438 pub fn iter(&self) -> impl Iterator<Item = (&UnitHash, &BuildOutput)> {
1440 self.outputs.iter()
1441 }
1442}
1443
1444struct BuildScriptRunFiles {
1446 root: PathBuf,
1448 stdout: PathBuf,
1450 stderr: PathBuf,
1452 root_output: PathBuf,
1455}
1456
1457impl BuildScriptRunFiles {
1458 pub fn for_unit(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> Self {
1459 let root = build_runner.files().build_script_run_dir(unit);
1460 let stdout = if build_runner.bcx.gctx.cli_unstable().build_dir_new_layout {
1461 root.join("stdout")
1462 } else {
1463 root.join("output")
1464 };
1465 let stderr = root.join("stderr");
1466 let root_output = root.join("root-output");
1467 Self {
1468 root,
1469 stdout,
1470 stderr,
1471 root_output,
1472 }
1473 }
1474}