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