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 let script_run_dir = build_runner.files().build_script_run_dir(unit);
347
348 if let Some(deps) = unit.pkg.manifest().metabuild() {
349 prepare_metabuild(build_runner, build_script_unit, deps)?;
350 }
351
352 let to_exec = script_dir.join(unit.target.name());
354
355 let to_exec = to_exec.into_os_string();
363 let mut cmd = build_runner.compilation.host_process(to_exec, &unit.pkg)?;
364 let debug = unit.profile.debuginfo.is_turned_on();
365 cmd.env("OUT_DIR", &script_out_dir)
366 .env("CARGO_MANIFEST_DIR", unit.pkg.root())
367 .env("CARGO_MANIFEST_PATH", unit.pkg.manifest_path())
368 .env("NUM_JOBS", &bcx.jobs().to_string())
369 .env("TARGET", bcx.target_data.short_name(&unit.kind))
370 .env("DEBUG", debug.to_string())
371 .env("OPT_LEVEL", &unit.profile.opt_level)
372 .env(
373 "PROFILE",
374 match unit.profile.root {
375 ProfileRoot::Release => "release",
376 ProfileRoot::Debug => "debug",
377 },
378 )
379 .env("HOST", &bcx.host_triple())
380 .env("RUSTC", &bcx.rustc().path)
381 .env("RUSTDOC", &*bcx.gctx.rustdoc()?)
382 .inherit_jobserver(&build_runner.jobserver);
383
384 for (var, value) in artifact::get_env(build_runner, unit, dependencies)? {
386 cmd.env(&var, value);
387 }
388
389 if let Some(linker) = &build_runner.compilation.target_linker(unit.kind) {
390 cmd.env("RUSTC_LINKER", linker);
391 }
392
393 if let Some(links) = unit.pkg.manifest().links() {
394 cmd.env("CARGO_MANIFEST_LINKS", links);
395 }
396
397 if let Some(trim_paths) = unit.profile.trim_paths.as_ref() {
398 cmd.env("CARGO_TRIM_PATHS", trim_paths.to_string());
399 }
400
401 for feat in &unit.features {
404 cmd.env(&format!("CARGO_FEATURE_{}", super::envify(feat)), "1");
405 }
406
407 let mut cfg_map = HashMap::new();
408 cfg_map.insert(
409 "feature",
410 unit.features.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
411 );
412 if unit.profile.debug_assertions {
416 cfg_map.insert("debug_assertions", Vec::new());
417 }
418 for cfg in bcx.target_data.cfg(unit.kind) {
419 match *cfg {
420 Cfg::Name(ref n) => {
421 if n.as_str() == "debug_assertions" {
423 continue;
424 }
425 cfg_map.insert(n.as_str(), Vec::new());
426 }
427 Cfg::KeyPair(ref k, ref v) => {
428 let values = cfg_map.entry(k.as_str()).or_default();
429 values.push(v.as_str());
430 }
431 }
432 }
433 for (k, v) in cfg_map {
434 let k = format!("CARGO_CFG_{}", super::envify(k));
437 cmd.env(&k, v.join(","));
438 }
439
440 if let Some(wrapper) = bcx.rustc().wrapper.as_ref() {
442 cmd.env("RUSTC_WRAPPER", wrapper);
443 } else {
444 cmd.env_remove("RUSTC_WRAPPER");
445 }
446 cmd.env_remove("RUSTC_WORKSPACE_WRAPPER");
447 if build_runner.bcx.ws.is_member(&unit.pkg) {
448 if let Some(wrapper) = bcx.rustc().workspace_wrapper.as_ref() {
449 cmd.env("RUSTC_WORKSPACE_WRAPPER", wrapper);
450 }
451 }
452 cmd.env("CARGO_ENCODED_RUSTFLAGS", unit.rustflags.join("\x1f"));
453 cmd.env_remove("RUSTFLAGS");
454
455 if build_runner.bcx.ws.gctx().extra_verbose() {
456 cmd.display_env_vars();
457 }
458
459 let any_build_script_metadata = bcx.gctx.cli_unstable().any_build_script_metadata;
460
461 let lib_deps = dependencies
467 .iter()
468 .filter_map(|dep| {
469 if dep.unit.mode.is_run_custom_build() {
470 let dep_metadata = build_runner.get_run_build_script_metadata(&dep.unit);
471
472 let dep_name = dep.dep_name.unwrap_or(dep.unit.pkg.name());
473
474 Some((
475 dep_name,
476 dep.unit
477 .pkg
478 .manifest()
479 .links()
480 .map(|links| links.to_string()),
481 dep.unit.pkg.package_id(),
482 dep_metadata,
483 ))
484 } else {
485 None
486 }
487 })
488 .collect::<Vec<_>>();
489 let library_name = unit.pkg.library().map(|t| t.crate_name());
490 let pkg_descr = unit.pkg.to_string();
491 let build_script_outputs = Arc::clone(&build_runner.build_script_outputs);
492 let id = unit.pkg.package_id();
493 let output_file = script_run_dir.join("output");
494 let err_file = script_run_dir.join("stderr");
495 let root_output_file = script_run_dir.join("root-output");
496 let host_target_root = build_runner.files().host_dest().map(|v| v.to_path_buf());
497 let all = (
498 id,
499 library_name.clone(),
500 pkg_descr.clone(),
501 Arc::clone(&build_script_outputs),
502 output_file.clone(),
503 script_out_dir.clone(),
504 );
505 let build_scripts = build_runner.build_scripts.get(unit).cloned();
506 let json_messages = bcx.build_config.emit_json();
507 let extra_verbose = bcx.gctx.extra_verbose();
508 let (prev_output, prev_script_out_dir) = prev_build_output(build_runner, unit);
509 let metadata_hash = build_runner.get_run_build_script_metadata(unit);
510
511 paths::create_dir_all(&script_dir)?;
512 paths::create_dir_all(&script_out_dir)?;
513 paths::create_dir_all(&script_run_dir)?;
514
515 let nightly_features_allowed = build_runner.bcx.gctx.nightly_features_allowed;
516 let targets: Vec<Target> = unit.pkg.targets().to_vec();
517 let msrv = unit.pkg.rust_version().cloned();
518 let targets_fresh = targets.clone();
520 let msrv_fresh = msrv.clone();
521
522 let env_profile_name = unit.profile.name.to_uppercase();
523 let built_with_debuginfo = build_runner
524 .bcx
525 .unit_graph
526 .get(unit)
527 .and_then(|deps| deps.iter().find(|dep| dep.unit.target == unit.target))
528 .map(|dep| dep.unit.profile.debuginfo.is_turned_on())
529 .unwrap_or(false);
530
531 let dirty = Work::new(move |state| {
537 paths::create_dir_all(&script_out_dir)
542 .context("failed to create script output directory for build command")?;
543
544 {
549 let build_script_outputs = build_script_outputs.lock().unwrap();
550 for (name, links, dep_id, dep_metadata) in lib_deps {
551 let script_output = build_script_outputs.get(dep_metadata).ok_or_else(|| {
552 internal(format!(
553 "failed to locate build state for env vars: {}/{}",
554 dep_id, dep_metadata
555 ))
556 })?;
557 let data = &script_output.metadata;
558 for (key, value) in data.iter() {
559 if let Some(ref links) = links {
560 cmd.env(
561 &format!("DEP_{}_{}", super::envify(&links), super::envify(key)),
562 value,
563 );
564 }
565 if any_build_script_metadata {
566 cmd.env(
567 &format!("CARGO_DEP_{}_{}", super::envify(&name), super::envify(key)),
568 value,
569 );
570 }
571 }
572 }
573 if let Some(build_scripts) = build_scripts
574 && let Some(ref host_target_root) = host_target_root
575 {
576 super::add_plugin_deps(
577 &mut cmd,
578 &build_script_outputs,
579 &build_scripts,
580 host_target_root,
581 )?;
582 }
583 }
584
585 state.running(&cmd);
587 let timestamp = paths::set_invocation_time(&script_run_dir)?;
588 let prefix = format!("[{} {}] ", id.name(), id.version());
589 let mut log_messages_in_case_of_panic = Vec::new();
590 let span = tracing::debug_span!("build_script", process = cmd.to_string());
591 let output = span.in_scope(|| {
592 cmd.exec_with_streaming(
593 &mut |stdout| {
594 if let Some(error) = stdout.strip_prefix(CARGO_ERROR_SYNTAX) {
595 log_messages_in_case_of_panic.push((Severity::Error, error.to_owned()));
596 }
597 if let Some(warning) = stdout
598 .strip_prefix(OLD_CARGO_WARNING_SYNTAX)
599 .or(stdout.strip_prefix(NEW_CARGO_WARNING_SYNTAX))
600 {
601 log_messages_in_case_of_panic.push((Severity::Warning, warning.to_owned()));
602 }
603 if extra_verbose {
604 state.stdout(format!("{}{}", prefix, stdout))?;
605 }
606 Ok(())
607 },
608 &mut |stderr| {
609 if extra_verbose {
610 state.stderr(format!("{}{}", prefix, stderr))?;
611 }
612 Ok(())
613 },
614 true,
615 )
616 .with_context(|| {
617 let mut build_error_context =
618 format!("failed to run custom build command for `{}`", pkg_descr);
619
620 #[expect(clippy::disallowed_methods, reason = "consistency with rustc")]
624 if let Ok(show_backtraces) = std::env::var("RUST_BACKTRACE") {
625 if !built_with_debuginfo && show_backtraces != "0" {
626 build_error_context.push_str(&format!(
627 "\n\
628 note: To improve backtraces for build dependencies, set the \
629 CARGO_PROFILE_{env_profile_name}_BUILD_OVERRIDE_DEBUG=true environment \
630 variable to enable debug information generation.",
631 ));
632 }
633 }
634
635 build_error_context
636 })
637 });
638
639 if let Err(error) = output {
641 insert_log_messages_in_build_outputs(
642 build_script_outputs,
643 id,
644 metadata_hash,
645 log_messages_in_case_of_panic,
646 );
647 return Err(error);
648 }
649 else if log_messages_in_case_of_panic
651 .iter()
652 .any(|(severity, _)| *severity == Severity::Error)
653 {
654 insert_log_messages_in_build_outputs(
655 build_script_outputs,
656 id,
657 metadata_hash,
658 log_messages_in_case_of_panic,
659 );
660 anyhow::bail!("build script logged errors");
661 }
662
663 let output = output.unwrap();
664
665 paths::write(&output_file, &output.stdout)?;
673 paths::set_file_time_no_err(output_file, timestamp);
676 paths::write(&err_file, &output.stderr)?;
677 paths::write(&root_output_file, paths::path2bytes(&script_out_dir)?)?;
678 let parsed_output = BuildOutput::parse(
679 &output.stdout,
680 library_name,
681 &pkg_descr,
682 &script_out_dir,
683 &script_out_dir,
684 nightly_features_allowed,
685 &targets,
686 &msrv,
687 )?;
688
689 if json_messages {
690 emit_build_output(state, &parsed_output, script_out_dir.as_path(), id)?;
691 }
692 build_script_outputs
693 .lock()
694 .unwrap()
695 .insert(id, metadata_hash, parsed_output);
696 Ok(())
697 });
698
699 let fresh = Work::new(move |state| {
703 let (id, library_name, pkg_descr, build_script_outputs, output_file, script_out_dir) = all;
704 let output = match prev_output {
705 Some(output) => output,
706 None => BuildOutput::parse_file(
707 &output_file,
708 library_name,
709 &pkg_descr,
710 &prev_script_out_dir,
711 &script_out_dir,
712 nightly_features_allowed,
713 &targets_fresh,
714 &msrv_fresh,
715 )?,
716 };
717
718 if json_messages {
719 emit_build_output(state, &output, script_out_dir.as_path(), id)?;
720 }
721
722 build_script_outputs
723 .lock()
724 .unwrap()
725 .insert(id, metadata_hash, output);
726 Ok(())
727 });
728
729 let mut job = fingerprint::prepare_target(build_runner, unit, false)?;
730 if job.freshness().is_dirty() {
731 job.before(dirty);
732 } else {
733 job.before(fresh);
734 }
735 Ok(job)
736}
737
738fn insert_log_messages_in_build_outputs(
741 build_script_outputs: Arc<Mutex<BuildScriptOutputs>>,
742 id: PackageId,
743 metadata_hash: UnitHash,
744 log_messages: Vec<LogMessage>,
745) {
746 let build_output_with_only_log_messages = BuildOutput {
747 log_messages,
748 ..BuildOutput::default()
749 };
750 build_script_outputs.lock().unwrap().insert(
751 id,
752 metadata_hash,
753 build_output_with_only_log_messages,
754 );
755}
756
757impl BuildOutput {
758 pub fn parse_file(
760 path: &Path,
761 library_name: Option<String>,
762 pkg_descr: &str,
763 script_out_dir_when_generated: &Path,
764 script_out_dir: &Path,
765 nightly_features_allowed: bool,
766 targets: &[Target],
767 msrv: &Option<RustVersion>,
768 ) -> CargoResult<BuildOutput> {
769 let contents = paths::read_bytes(path)?;
770 BuildOutput::parse(
771 &contents,
772 library_name,
773 pkg_descr,
774 script_out_dir_when_generated,
775 script_out_dir,
776 nightly_features_allowed,
777 targets,
778 msrv,
779 )
780 }
781
782 pub fn parse(
787 input: &[u8],
788 library_name: Option<String>,
790 pkg_descr: &str,
791 script_out_dir_when_generated: &Path,
792 script_out_dir: &Path,
793 nightly_features_allowed: bool,
794 targets: &[Target],
795 msrv: &Option<RustVersion>,
796 ) -> CargoResult<BuildOutput> {
797 let mut library_paths = Vec::new();
798 let mut library_links = Vec::new();
799 let mut linker_args = Vec::new();
800 let mut cfgs = Vec::new();
801 let mut check_cfgs = Vec::new();
802 let mut env = Vec::new();
803 let mut metadata = Vec::new();
804 let mut rerun_if_changed = Vec::new();
805 let mut rerun_if_env_changed = Vec::new();
806 let mut log_messages = Vec::new();
807 let whence = format!("build script of `{}`", pkg_descr);
808 const RESERVED_PREFIXES: &[&str] = &[
816 "rustc-flags=",
817 "rustc-link-lib=",
818 "rustc-link-search=",
819 "rustc-link-arg-cdylib=",
820 "rustc-cdylib-link-arg=",
821 "rustc-link-arg-bins=",
822 "rustc-link-arg-bin=",
823 "rustc-link-arg-tests=",
824 "rustc-link-arg-benches=",
825 "rustc-link-arg-examples=",
826 "rustc-link-arg=",
827 "rustc-cfg=",
828 "rustc-check-cfg=",
829 "rustc-env=",
830 "warning=",
831 "rerun-if-changed=",
832 "rerun-if-env-changed=",
833 ];
834 const DOCS_LINK_SUGGESTION: &str = "See https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script \
835 for more information about build script outputs.";
836
837 fn has_reserved_prefix(flag: &str) -> bool {
838 RESERVED_PREFIXES
839 .iter()
840 .any(|reserved_prefix| flag.starts_with(reserved_prefix))
841 }
842
843 fn check_minimum_supported_rust_version_for_new_syntax(
844 pkg_descr: &str,
845 msrv: &Option<RustVersion>,
846 flag: &str,
847 ) -> CargoResult<()> {
848 if let Some(msrv) = msrv {
849 let new_syntax_added_in = RustVersion::new(1, 77, 0);
850 if !new_syntax_added_in.is_compatible_with(&msrv.to_partial()) {
851 let old_syntax_suggestion = if has_reserved_prefix(flag) {
852 format!(
853 "Switch to the old `cargo:{flag}` syntax (note the single colon).\n"
854 )
855 } else if flag.starts_with("metadata=") {
856 let old_format_flag = flag.strip_prefix("metadata=").unwrap();
857 format!(
858 "Switch to the old `cargo:{old_format_flag}` syntax instead of `cargo::{flag}` (note the single colon).\n"
859 )
860 } else {
861 String::new()
862 };
863
864 bail!(
865 "the `cargo::` syntax for build script output instructions was added in \
866 Rust 1.77.0, but the minimum supported Rust version of `{pkg_descr}` is {msrv}.\n\
867 {old_syntax_suggestion}\
868 {DOCS_LINK_SUGGESTION}"
869 );
870 }
871 }
872
873 Ok(())
874 }
875
876 fn parse_directive<'a>(
877 whence: &str,
878 line: &str,
879 data: &'a str,
880 old_syntax: bool,
881 ) -> CargoResult<(&'a str, &'a str)> {
882 let mut iter = data.splitn(2, "=");
883 let key = iter.next();
884 let value = iter.next();
885 match (key, value) {
886 (Some(a), Some(b)) => Ok((a, b.trim_end())),
887 _ => bail!(
888 "invalid output in {whence}: `{line}`\n\
889 Expected a line with `{syntax}KEY=VALUE` with an `=` character, \
890 but none was found.\n\
891 {DOCS_LINK_SUGGESTION}",
892 syntax = if old_syntax { "cargo:" } else { "cargo::" },
893 ),
894 }
895 }
896
897 fn parse_metadata<'a>(
898 whence: &str,
899 line: &str,
900 data: &'a str,
901 old_syntax: bool,
902 ) -> CargoResult<(&'a str, &'a str)> {
903 let mut iter = data.splitn(2, "=");
904 let key = iter.next();
905 let value = iter.next();
906 match (key, value) {
907 (Some(a), Some(b)) => Ok((a, b.trim_end())),
908 _ => bail!(
909 "invalid output in {whence}: `{line}`\n\
910 Expected a line with `{syntax}KEY=VALUE` with an `=` character, \
911 but none was found.\n\
912 {DOCS_LINK_SUGGESTION}",
913 syntax = if old_syntax {
914 "cargo:"
915 } else {
916 "cargo::metadata="
917 },
918 ),
919 }
920 }
921
922 for line in input.split(|b| *b == b'\n') {
923 let line = match str::from_utf8(line) {
924 Ok(line) => line.trim(),
925 Err(..) => continue,
926 };
927 let mut old_syntax = false;
928 let (key, value) = if let Some(data) = line.strip_prefix("cargo::") {
929 check_minimum_supported_rust_version_for_new_syntax(pkg_descr, msrv, data)?;
930 parse_directive(whence.as_str(), line, data, old_syntax)?
932 } else if let Some(data) = line.strip_prefix("cargo:") {
933 old_syntax = true;
934 if has_reserved_prefix(data) {
936 parse_directive(whence.as_str(), line, data, old_syntax)?
937 } else {
938 ("metadata", data)
940 }
941 } else {
942 continue;
944 };
945 let value = value.replace(
947 script_out_dir_when_generated.to_str().unwrap(),
948 script_out_dir.to_str().unwrap(),
949 );
950
951 let syntax_prefix = if old_syntax { "cargo:" } else { "cargo::" };
952 macro_rules! check_and_add_target {
953 ($target_kind: expr, $is_target_kind: expr, $link_type: expr) => {
954 if !targets.iter().any(|target| $is_target_kind(target)) {
955 bail!(
956 "invalid instruction `{}{}` from {}\n\
957 The package {} does not have a {} target.",
958 syntax_prefix,
959 key,
960 whence,
961 pkg_descr,
962 $target_kind
963 );
964 }
965 linker_args.push(($link_type, value));
966 };
967 }
968
969 match key {
971 "rustc-flags" => {
972 let (paths, links) = BuildOutput::parse_rustc_flags(&value, &whence)?;
973 library_links.extend(links.into_iter());
974 library_paths.extend(
975 paths
976 .into_iter()
977 .map(|p| LibraryPath::new(p, script_out_dir)),
978 );
979 }
980 "rustc-link-lib" => library_links.push(value.to_string()),
981 "rustc-link-search" => {
982 library_paths.push(LibraryPath::new(PathBuf::from(value), script_out_dir))
983 }
984 "rustc-link-arg-cdylib" | "rustc-cdylib-link-arg" => {
985 if !targets.iter().any(|target| target.is_cdylib()) {
986 log_messages.push((
987 Severity::Warning,
988 format!(
989 "{}{} was specified in the build script of {}, \
990 but that package does not contain a cdylib target\n\
991 \n\
992 Allowing this was an unintended change in the 1.50 \
993 release, and may become an error in the future. \
994 For more information, see \
995 <https://github.com/rust-lang/cargo/issues/9562>.",
996 syntax_prefix, key, pkg_descr
997 ),
998 ));
999 }
1000 linker_args.push((LinkArgTarget::Cdylib, value))
1001 }
1002 "rustc-link-arg-bins" => {
1003 check_and_add_target!("bin", Target::is_bin, LinkArgTarget::Bin);
1004 }
1005 "rustc-link-arg-bin" => {
1006 let (bin_name, arg) = value.split_once('=').ok_or_else(|| {
1007 anyhow::format_err!(
1008 "invalid instruction `{}{}={}` from {}\n\
1009 The instruction should have the form {}{}=BIN=ARG",
1010 syntax_prefix,
1011 key,
1012 value,
1013 whence,
1014 syntax_prefix,
1015 key
1016 )
1017 })?;
1018 if !targets
1019 .iter()
1020 .any(|target| target.is_bin() && target.name() == bin_name)
1021 {
1022 bail!(
1023 "invalid instruction `{}{}` from {}\n\
1024 The package {} does not have a bin target with the name `{}`.",
1025 syntax_prefix,
1026 key,
1027 whence,
1028 pkg_descr,
1029 bin_name
1030 );
1031 }
1032 linker_args.push((
1033 LinkArgTarget::SingleBin(bin_name.to_owned()),
1034 arg.to_string(),
1035 ));
1036 }
1037 "rustc-link-arg-tests" => {
1038 check_and_add_target!("test", Target::is_test, LinkArgTarget::Test);
1039 }
1040 "rustc-link-arg-benches" => {
1041 check_and_add_target!("benchmark", Target::is_bench, LinkArgTarget::Bench);
1042 }
1043 "rustc-link-arg-examples" => {
1044 check_and_add_target!("example", Target::is_example, LinkArgTarget::Example);
1045 }
1046 "rustc-link-arg" => {
1047 linker_args.push((LinkArgTarget::All, value));
1048 }
1049 "rustc-cfg" => cfgs.push(value.to_string()),
1050 "rustc-check-cfg" => check_cfgs.push(value.to_string()),
1051 "rustc-env" => {
1052 let (key, val) = BuildOutput::parse_rustc_env(&value, &whence)?;
1053 if key == "RUSTC_BOOTSTRAP" {
1056 let rustc_bootstrap_allows = |name: Option<&str>| {
1066 let name = match name {
1067 None => return false,
1071 Some(n) => n,
1072 };
1073 #[expect(
1074 clippy::disallowed_methods,
1075 reason = "consistency with rustc, not specified behavior"
1076 )]
1077 std::env::var("RUSTC_BOOTSTRAP")
1078 .map_or(false, |var| var.split(',').any(|s| s == name))
1079 };
1080 if nightly_features_allowed
1081 || rustc_bootstrap_allows(library_name.as_deref())
1082 {
1083 log_messages.push((Severity::Warning, format!("cannot set `RUSTC_BOOTSTRAP={}` from {}.\n\
1084 note: crates cannot set `RUSTC_BOOTSTRAP` themselves, as doing so would subvert the stability guarantees of Rust for your project.",
1085 val, whence
1086 )));
1087 } else {
1088 bail!(
1091 "cannot set `RUSTC_BOOTSTRAP={}` from {}.\n\
1092 note: crates cannot set `RUSTC_BOOTSTRAP` themselves, as doing so would subvert the stability guarantees of Rust for your project.\n\
1093 help: If you're sure you want to do this in your project, set the environment variable `RUSTC_BOOTSTRAP={}` before running cargo instead.",
1094 val,
1095 whence,
1096 library_name.as_deref().unwrap_or("1"),
1097 );
1098 }
1099 } else {
1100 env.push((key, val));
1101 }
1102 }
1103 "error" => log_messages.push((Severity::Error, value.to_string())),
1104 "warning" => log_messages.push((Severity::Warning, value.to_string())),
1105 "rerun-if-changed" => rerun_if_changed.push(PathBuf::from(value)),
1106 "rerun-if-env-changed" => rerun_if_env_changed.push(value.to_string()),
1107 "metadata" => {
1108 let (key, value) = parse_metadata(whence.as_str(), line, &value, old_syntax)?;
1109 metadata.push((key.to_owned(), value.to_owned()));
1110 }
1111 _ => bail!(
1112 "invalid output in {whence}: `{line}`\n\
1113 Unknown key: `{key}`.\n\
1114 {DOCS_LINK_SUGGESTION}",
1115 ),
1116 }
1117 }
1118
1119 Ok(BuildOutput {
1120 library_paths,
1121 library_links,
1122 linker_args,
1123 cfgs,
1124 check_cfgs,
1125 env,
1126 metadata,
1127 rerun_if_changed,
1128 rerun_if_env_changed,
1129 log_messages,
1130 })
1131 }
1132
1133 pub fn parse_rustc_flags(
1137 value: &str,
1138 whence: &str,
1139 ) -> CargoResult<(Vec<PathBuf>, Vec<String>)> {
1140 let value = value.trim();
1141 let mut flags_iter = value
1142 .split(|c: char| c.is_whitespace())
1143 .filter(|w| w.chars().any(|c| !c.is_whitespace()));
1144 let (mut library_paths, mut library_links) = (Vec::new(), Vec::new());
1145
1146 while let Some(flag) = flags_iter.next() {
1147 if flag.starts_with("-l") || flag.starts_with("-L") {
1148 let (flag, mut value) = flag.split_at(2);
1152 if value.is_empty() {
1153 value = match flags_iter.next() {
1154 Some(v) => v,
1155 None => bail! {
1156 "flag in rustc-flags has no value in {}: {}",
1157 whence,
1158 value
1159 },
1160 }
1161 }
1162
1163 match flag {
1164 "-l" => library_links.push(value.to_string()),
1165 "-L" => library_paths.push(PathBuf::from(value)),
1166
1167 _ => unreachable!(),
1169 };
1170 } else {
1171 bail!(
1172 "only `-l` and `-L` flags are allowed in {}: `{}`",
1173 whence,
1174 value
1175 )
1176 }
1177 }
1178 Ok((library_paths, library_links))
1179 }
1180
1181 pub fn parse_rustc_env(value: &str, whence: &str) -> CargoResult<(String, String)> {
1185 match value.split_once('=') {
1186 Some((n, v)) => Ok((n.to_owned(), v.to_owned())),
1187 _ => bail!("Variable rustc-env has no value in {whence}: {value}"),
1188 }
1189 }
1190}
1191
1192fn prepare_metabuild(
1196 build_runner: &BuildRunner<'_, '_>,
1197 unit: &Unit,
1198 deps: &[String],
1199) -> CargoResult<()> {
1200 let mut output = Vec::new();
1201 let available_deps = build_runner.unit_deps(unit);
1202 let meta_deps: Vec<_> = deps
1204 .iter()
1205 .filter_map(|name| {
1206 available_deps
1207 .iter()
1208 .find(|d| d.unit.pkg.name().as_str() == name.as_str())
1209 .map(|d| d.unit.target.crate_name())
1210 })
1211 .collect();
1212 output.push("fn main() {\n".to_string());
1213 for dep in &meta_deps {
1214 output.push(format!(" {}::metabuild();\n", dep));
1215 }
1216 output.push("}\n".to_string());
1217 let output = output.join("");
1218 let path = unit
1219 .pkg
1220 .manifest()
1221 .metabuild_path(build_runner.bcx.ws.build_dir());
1222 paths::create_dir_all(path.parent().unwrap())?;
1223 paths::write_if_changed(path, &output)?;
1224 Ok(())
1225}
1226
1227impl BuildDeps {
1228 pub fn new(output_file: &Path, output: Option<&BuildOutput>) -> BuildDeps {
1231 BuildDeps {
1232 build_script_output: output_file.to_path_buf(),
1233 rerun_if_changed: output
1234 .map(|p| &p.rerun_if_changed)
1235 .cloned()
1236 .unwrap_or_default(),
1237 rerun_if_env_changed: output
1238 .map(|p| &p.rerun_if_env_changed)
1239 .cloned()
1240 .unwrap_or_default(),
1241 }
1242 }
1243}
1244
1245pub fn build_map(build_runner: &mut BuildRunner<'_, '_>) -> CargoResult<()> {
1267 let mut ret = HashMap::new();
1268 for unit in &build_runner.bcx.roots {
1269 build(&mut ret, build_runner, unit)?;
1270 }
1271 build_runner
1272 .build_scripts
1273 .extend(ret.into_iter().map(|(k, v)| (k, Arc::new(v))));
1274 return Ok(());
1275
1276 fn build<'a>(
1279 out: &'a mut HashMap<Unit, BuildScripts>,
1280 build_runner: &mut BuildRunner<'_, '_>,
1281 unit: &Unit,
1282 ) -> CargoResult<&'a BuildScripts> {
1283 if out.contains_key(unit) {
1286 return Ok(&out[unit]);
1287 }
1288
1289 if unit.mode.is_run_custom_build() {
1291 if let Some(links) = unit.pkg.manifest().links() {
1292 if let Some(output) = unit.links_overrides.get(links) {
1293 let metadata = build_runner.get_run_build_script_metadata(unit);
1294 build_runner.build_script_outputs.lock().unwrap().insert(
1295 unit.pkg.package_id(),
1296 metadata,
1297 output.clone(),
1298 );
1299 }
1300 }
1301 }
1302
1303 let mut ret = BuildScripts::default();
1304
1305 if !unit.target.is_custom_build() && unit.pkg.has_custom_build() {
1307 let script_metas = build_runner
1308 .find_build_script_metadatas(unit)
1309 .expect("has_custom_build should have RunCustomBuild");
1310 for script_meta in script_metas {
1311 add_to_link(&mut ret, unit.pkg.package_id(), script_meta);
1312 }
1313 }
1314
1315 if unit.mode.is_run_custom_build() {
1316 parse_previous_explicit_deps(build_runner, unit);
1317 }
1318
1319 let mut dependencies: Vec<Unit> = build_runner
1324 .unit_deps(unit)
1325 .iter()
1326 .map(|d| d.unit.clone())
1327 .collect();
1328 dependencies.sort_by_key(|u| u.pkg.package_id());
1329
1330 for dep_unit in dependencies.iter() {
1331 let dep_scripts = build(out, build_runner, dep_unit)?;
1332
1333 if dep_unit.target.for_host() {
1334 ret.plugins.extend(dep_scripts.to_link.iter().cloned());
1335 } else if dep_unit.target.is_linkable() {
1336 for &(pkg, metadata) in dep_scripts.to_link.iter() {
1337 add_to_link(&mut ret, pkg, metadata);
1338 }
1339 }
1340 }
1341
1342 match out.entry(unit.clone()) {
1343 Entry::Vacant(entry) => Ok(entry.insert(ret)),
1344 Entry::Occupied(_) => panic!("cyclic dependencies in `build_map`"),
1345 }
1346 }
1347
1348 fn add_to_link(scripts: &mut BuildScripts, pkg: PackageId, metadata: UnitHash) {
1351 if scripts.seen_to_link.insert((pkg, metadata)) {
1352 scripts.to_link.push((pkg, metadata));
1353 }
1354 }
1355
1356 fn parse_previous_explicit_deps(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) {
1358 let script_run_dir = build_runner.files().build_script_run_dir(unit);
1359 let output_file = script_run_dir.join("output");
1360 let (prev_output, _) = prev_build_output(build_runner, unit);
1361 let deps = BuildDeps::new(&output_file, prev_output.as_ref());
1362 build_runner.build_explicit_deps.insert(unit.clone(), deps);
1363 }
1364}
1365
1366fn prev_build_output(
1372 build_runner: &mut BuildRunner<'_, '_>,
1373 unit: &Unit,
1374) -> (Option<BuildOutput>, PathBuf) {
1375 let script_out_dir = if build_runner.bcx.gctx.cli_unstable().build_dir_new_layout {
1376 build_runner.files().out_dir_new_layout(unit)
1377 } else {
1378 build_runner.files().build_script_out_dir(unit)
1379 };
1380 let script_run_dir = build_runner.files().build_script_run_dir(unit);
1381 let root_output_file = script_run_dir.join("root-output");
1382 let output_file = script_run_dir.join("output");
1383
1384 let prev_script_out_dir = paths::read_bytes(&root_output_file)
1385 .and_then(|bytes| paths::bytes2path(&bytes))
1386 .unwrap_or_else(|_| script_out_dir.clone());
1387
1388 (
1389 BuildOutput::parse_file(
1390 &output_file,
1391 unit.pkg.library().map(|t| t.crate_name()),
1392 &unit.pkg.to_string(),
1393 &prev_script_out_dir,
1394 &script_out_dir,
1395 build_runner.bcx.gctx.nightly_features_allowed,
1396 unit.pkg.targets(),
1397 &unit.pkg.rust_version().cloned(),
1398 )
1399 .ok(),
1400 prev_script_out_dir,
1401 )
1402}
1403
1404impl BuildScriptOutputs {
1405 fn insert(&mut self, pkg_id: PackageId, metadata: UnitHash, parsed_output: BuildOutput) {
1407 match self.outputs.entry(metadata) {
1408 Entry::Vacant(entry) => {
1409 entry.insert(parsed_output);
1410 }
1411 Entry::Occupied(entry) => panic!(
1412 "build script output collision for {}/{}\n\
1413 old={:?}\nnew={:?}",
1414 pkg_id,
1415 metadata,
1416 entry.get(),
1417 parsed_output
1418 ),
1419 }
1420 }
1421
1422 fn contains_key(&self, metadata: UnitHash) -> bool {
1424 self.outputs.contains_key(&metadata)
1425 }
1426
1427 pub fn get(&self, meta: UnitHash) -> Option<&BuildOutput> {
1429 self.outputs.get(&meta)
1430 }
1431
1432 pub fn iter(&self) -> impl Iterator<Item = (&UnitHash, &BuildOutput)> {
1434 self.outputs.iter()
1435 }
1436}