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 capture_rusage = bcx.capture_rusage();
521 let (prev_output, prev_script_out_dir) = prev_build_output(build_runner, unit);
522 let metadata_hash = build_runner.get_run_build_script_metadata(unit);
523
524 paths::create_dir_all(&script_dir)?;
525 paths::create_dir_all(&script_out_dir)?;
526 paths::create_dir_all(&run_files.root)?;
527
528 let nightly_features_allowed = build_runner.bcx.gctx.nightly_features_allowed;
529 let targets: Vec<Target> = unit.pkg.targets().to_vec();
530 let msrv = unit.pkg.rust_version().cloned();
531 let targets_fresh = targets.clone();
533 let msrv_fresh = msrv.clone();
534
535 let env_profile_name = unit.profile.name.to_uppercase();
536 let built_with_debuginfo = build_runner
537 .bcx
538 .unit_graph
539 .get(unit)
540 .and_then(|deps| deps.iter().find(|dep| dep.unit.target == unit.target))
541 .map(|dep| dep.unit.profile.debuginfo.is_turned_on())
542 .unwrap_or(false);
543
544 let dirty = Work::new(move |state| {
550 paths::create_dir_all(&script_out_dir)
555 .context("failed to create script output directory for build command")?;
556
557 {
562 let build_script_outputs = build_script_outputs.lock().unwrap();
563 for (name, links, dep_id, dep_metadata) in lib_deps {
564 let script_output = build_script_outputs.get(dep_metadata).ok_or_else(|| {
565 internal(format!(
566 "failed to locate build state for env vars: {}/{}",
567 dep_id, dep_metadata
568 ))
569 })?;
570 let data = &script_output.metadata;
571 for (key, value) in data.iter() {
572 if let Some(ref links) = links {
573 cmd.env(
574 &format!("DEP_{}_{}", super::envify(&links), super::envify(key)),
575 value,
576 );
577 }
578 if any_build_script_metadata {
579 cmd.env(
580 &format!("CARGO_DEP_{}_{}", super::envify(&name), super::envify(key)),
581 value,
582 );
583 }
584 }
585 }
586 if let Some(build_scripts) = build_scripts
587 && let Some(ref host_target_root) = host_target_root
588 {
589 super::add_plugin_deps(
590 &mut cmd,
591 &build_script_outputs,
592 &build_scripts,
593 host_target_root,
594 )?;
595 }
596 }
597
598 state.running(&cmd);
600 let timestamp = paths::set_invocation_time(&run_files.root)?;
601 let prefix = format!("[{} {}] ", id.name(), id.version());
602 let mut log_messages_in_case_of_panic = Vec::new();
603 let span = tracing::debug_span!("build_script", process = cmd.to_string());
604 let (stats, output) = span.in_scope(|| {
605 let (stats, result) = cmd.exec_with_streaming(
606 &mut |stdout| {
607 if let Some(error) = stdout.strip_prefix(CARGO_ERROR_SYNTAX) {
608 log_messages_in_case_of_panic.push((Severity::Error, error.to_owned()));
609 }
610 if let Some(warning) = stdout
611 .strip_prefix(OLD_CARGO_WARNING_SYNTAX)
612 .or(stdout.strip_prefix(NEW_CARGO_WARNING_SYNTAX))
613 {
614 log_messages_in_case_of_panic.push((Severity::Warning, warning.to_owned()));
615 }
616 if extra_verbose {
617 state.stdout(format!("{}{}", prefix, stdout))?;
618 }
619 Ok(())
620 },
621 &mut |stderr| {
622 if extra_verbose {
623 state.stderr(format!("{}{}", prefix, stderr))?;
624 }
625 Ok(())
626 },
627 true,
628 capture_rusage,
629 );
630 let result = result.with_context(|| {
631 let mut build_error_context =
632 format!("failed to run custom build command for `{}`", pkg_descr);
633
634 #[expect(clippy::disallowed_methods, reason = "consistency with rustc")]
638 if let Ok(show_backtraces) = std::env::var("RUST_BACKTRACE") {
639 if !built_with_debuginfo && show_backtraces != "0" {
640 build_error_context.push_str(&format!(
641 "\n\
642 note: To improve backtraces for build dependencies, set the \
643 CARGO_PROFILE_{env_profile_name}_BUILD_OVERRIDE_DEBUG=true environment \
644 variable to enable debug information generation.",
645 ));
646 }
647 }
648
649 build_error_context
650 });
651 (stats, result)
652 });
653
654 if let Some(peak_memory) = stats.peak_rss {
655 state.peak_memory(peak_memory);
656 }
657
658 if let Err(error) = output {
660 insert_log_messages_in_build_outputs(
661 build_script_outputs,
662 id,
663 metadata_hash,
664 log_messages_in_case_of_panic,
665 );
666 return Err(error);
667 }
668 else if log_messages_in_case_of_panic
670 .iter()
671 .any(|(severity, _)| *severity == Severity::Error)
672 {
673 insert_log_messages_in_build_outputs(
674 build_script_outputs,
675 id,
676 metadata_hash,
677 log_messages_in_case_of_panic,
678 );
679 anyhow::bail!("build script logged errors");
680 }
681
682 let output = output.unwrap();
683
684 paths::write(&run_files.stdout, &output.stdout)?;
692 paths::set_file_time_no_err(run_files.stdout, timestamp);
695 paths::write(&run_files.stderr, &output.stderr)?;
696 paths::write(&run_files.root_output, paths::path2bytes(&script_out_dir)?)?;
697 let parsed_output = BuildOutput::parse(
698 &output.stdout,
699 library_name,
700 &pkg_descr,
701 &script_out_dir,
702 &script_out_dir,
703 nightly_features_allowed,
704 &targets,
705 &msrv,
706 )?;
707
708 if json_messages {
709 emit_build_output(state, &parsed_output, script_out_dir.as_path(), id)?;
710 }
711 build_script_outputs
712 .lock()
713 .unwrap()
714 .insert(id, metadata_hash, parsed_output);
715 Ok(())
716 });
717
718 let fresh = Work::new(move |state| {
722 let (id, library_name, pkg_descr, build_script_outputs, output_file, script_out_dir) = all;
723 let output = match prev_output {
724 Some(output) => output,
725 None => BuildOutput::parse_file(
726 &output_file,
727 library_name,
728 &pkg_descr,
729 &prev_script_out_dir,
730 &script_out_dir,
731 nightly_features_allowed,
732 &targets_fresh,
733 &msrv_fresh,
734 )?,
735 };
736
737 if json_messages {
738 emit_build_output(state, &output, script_out_dir.as_path(), id)?;
739 }
740
741 build_script_outputs
742 .lock()
743 .unwrap()
744 .insert(id, metadata_hash, output);
745 Ok(())
746 });
747
748 let mut job = fingerprint::prepare_target(build_runner, unit, false)?;
749 if job.freshness().is_dirty() {
750 job.before(dirty);
751 } else {
752 job.before(fresh);
753 }
754 Ok(job)
755}
756
757fn insert_log_messages_in_build_outputs(
760 build_script_outputs: Arc<Mutex<BuildScriptOutputs>>,
761 id: PackageId,
762 metadata_hash: UnitHash,
763 log_messages: Vec<LogMessage>,
764) {
765 let build_output_with_only_log_messages = BuildOutput {
766 log_messages,
767 ..BuildOutput::default()
768 };
769 build_script_outputs.lock().unwrap().insert(
770 id,
771 metadata_hash,
772 build_output_with_only_log_messages,
773 );
774}
775
776impl BuildOutput {
777 pub fn parse_file(
779 path: &Path,
780 library_name: Option<String>,
781 pkg_descr: &str,
782 script_out_dir_when_generated: &Path,
783 script_out_dir: &Path,
784 nightly_features_allowed: bool,
785 targets: &[Target],
786 msrv: &Option<RustVersion>,
787 ) -> CargoResult<BuildOutput> {
788 let contents = paths::read_bytes(path)?;
789 BuildOutput::parse(
790 &contents,
791 library_name,
792 pkg_descr,
793 script_out_dir_when_generated,
794 script_out_dir,
795 nightly_features_allowed,
796 targets,
797 msrv,
798 )
799 }
800
801 pub fn parse(
806 input: &[u8],
807 library_name: Option<String>,
809 pkg_descr: &str,
810 script_out_dir_when_generated: &Path,
811 script_out_dir: &Path,
812 nightly_features_allowed: bool,
813 targets: &[Target],
814 msrv: &Option<RustVersion>,
815 ) -> CargoResult<BuildOutput> {
816 let mut library_paths = Vec::new();
817 let mut library_links = Vec::new();
818 let mut linker_args = Vec::new();
819 let mut cfgs = Vec::new();
820 let mut check_cfgs = Vec::new();
821 let mut env = Vec::new();
822 let mut metadata = Vec::new();
823 let mut rerun_if_changed = Vec::new();
824 let mut rerun_if_env_changed = Vec::new();
825 let mut log_messages = Vec::new();
826 let whence = format!("build script of `{}`", pkg_descr);
827 const RESERVED_PREFIXES: &[&str] = &[
835 "rustc-flags=",
836 "rustc-link-lib=",
837 "rustc-link-search=",
838 "rustc-link-arg-cdylib=",
839 "rustc-cdylib-link-arg=",
840 "rustc-link-arg-bins=",
841 "rustc-link-arg-bin=",
842 "rustc-link-arg-tests=",
843 "rustc-link-arg-benches=",
844 "rustc-link-arg-examples=",
845 "rustc-link-arg=",
846 "rustc-cfg=",
847 "rustc-check-cfg=",
848 "rustc-env=",
849 "warning=",
850 "rerun-if-changed=",
851 "rerun-if-env-changed=",
852 ];
853 const DOCS_LINK_SUGGESTION: &str = "See https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script \
854 for more information about build script outputs.";
855
856 fn has_reserved_prefix(flag: &str) -> bool {
857 RESERVED_PREFIXES
858 .iter()
859 .any(|reserved_prefix| flag.starts_with(reserved_prefix))
860 }
861
862 fn check_minimum_supported_rust_version_for_new_syntax(
863 pkg_descr: &str,
864 msrv: &Option<RustVersion>,
865 flag: &str,
866 ) -> CargoResult<()> {
867 if let Some(msrv) = msrv {
868 let new_syntax_added_in = RustVersion::new(1, 77, 0);
869 if !new_syntax_added_in.is_compatible_with(&msrv.to_partial()) {
870 let old_syntax_suggestion = if has_reserved_prefix(flag) {
871 format!(
872 "Switch to the old `cargo:{flag}` syntax (note the single colon).\n"
873 )
874 } else if flag.starts_with("metadata=") {
875 let old_format_flag = flag.strip_prefix("metadata=").unwrap();
876 format!(
877 "Switch to the old `cargo:{old_format_flag}` syntax instead of `cargo::{flag}` (note the single colon).\n"
878 )
879 } else {
880 String::new()
881 };
882
883 bail!(
884 "the `cargo::` syntax for build script output instructions was added in \
885 Rust 1.77.0, but the minimum supported Rust version of `{pkg_descr}` is {msrv}.\n\
886 {old_syntax_suggestion}\
887 {DOCS_LINK_SUGGESTION}"
888 );
889 }
890 }
891
892 Ok(())
893 }
894
895 fn parse_directive<'a>(
896 whence: &str,
897 line: &str,
898 data: &'a str,
899 old_syntax: bool,
900 ) -> CargoResult<(&'a str, &'a str)> {
901 let mut iter = data.splitn(2, "=");
902 let key = iter.next();
903 let value = iter.next();
904 match (key, value) {
905 (Some(a), Some(b)) => Ok((a, b.trim_end())),
906 _ => bail!(
907 "invalid output in {whence}: `{line}`\n\
908 Expected a line with `{syntax}KEY=VALUE` with an `=` character, \
909 but none was found.\n\
910 {DOCS_LINK_SUGGESTION}",
911 syntax = if old_syntax { "cargo:" } else { "cargo::" },
912 ),
913 }
914 }
915
916 fn parse_metadata<'a>(
917 whence: &str,
918 line: &str,
919 data: &'a str,
920 old_syntax: bool,
921 ) -> CargoResult<(&'a str, &'a str)> {
922 let mut iter = data.splitn(2, "=");
923 let key = iter.next();
924 let value = iter.next();
925 match (key, value) {
926 (Some(a), Some(b)) => Ok((a, b.trim_end())),
927 _ => bail!(
928 "invalid output in {whence}: `{line}`\n\
929 Expected a line with `{syntax}KEY=VALUE` with an `=` character, \
930 but none was found.\n\
931 {DOCS_LINK_SUGGESTION}",
932 syntax = if old_syntax {
933 "cargo:"
934 } else {
935 "cargo::metadata="
936 },
937 ),
938 }
939 }
940
941 for line in input.split(|b| *b == b'\n') {
942 let line = match str::from_utf8(line) {
943 Ok(line) => line.trim(),
944 Err(..) => continue,
945 };
946 let mut old_syntax = false;
947 let (key, value) = if let Some(data) = line.strip_prefix("cargo::") {
948 check_minimum_supported_rust_version_for_new_syntax(pkg_descr, msrv, data)?;
949 parse_directive(whence.as_str(), line, data, old_syntax)?
951 } else if let Some(data) = line.strip_prefix("cargo:") {
952 old_syntax = true;
953 if has_reserved_prefix(data) {
955 parse_directive(whence.as_str(), line, data, old_syntax)?
956 } else {
957 ("metadata", data)
959 }
960 } else {
961 continue;
963 };
964 let value = value.replace(
966 script_out_dir_when_generated.to_str().unwrap(),
967 script_out_dir.to_str().unwrap(),
968 );
969
970 let syntax_prefix = if old_syntax { "cargo:" } else { "cargo::" };
971 macro_rules! check_and_add_target {
972 ($target_kind: expr, $is_target_kind: expr, $link_type: expr) => {
973 if !targets.iter().any(|target| $is_target_kind(target)) {
974 bail!(
975 "invalid instruction `{}{}` from {}\n\
976 The package {} does not have a {} target.",
977 syntax_prefix,
978 key,
979 whence,
980 pkg_descr,
981 $target_kind
982 );
983 }
984 linker_args.push(($link_type, value));
985 };
986 }
987
988 match key {
990 "rustc-flags" => {
991 let (paths, links) = BuildOutput::parse_rustc_flags(&value, &whence)?;
992 library_links.extend(links.into_iter());
993 library_paths.extend(
994 paths
995 .into_iter()
996 .map(|p| LibraryPath::new(p, script_out_dir)),
997 );
998 }
999 "rustc-link-lib" => library_links.push(value.to_string()),
1000 "rustc-link-search" => {
1001 library_paths.push(LibraryPath::new(PathBuf::from(value), script_out_dir))
1002 }
1003 "rustc-link-arg-cdylib" | "rustc-cdylib-link-arg" => {
1004 if !targets.iter().any(|target| target.is_cdylib()) {
1005 log_messages.push((
1006 Severity::Warning,
1007 format!(
1008 "{}{} was specified in the build script of {}, \
1009 but that package does not contain a cdylib target\n\
1010 \n\
1011 Allowing this was an unintended change in the 1.50 \
1012 release, and may become an error in the future. \
1013 For more information, see \
1014 <https://github.com/rust-lang/cargo/issues/9562>.",
1015 syntax_prefix, key, pkg_descr
1016 ),
1017 ));
1018 }
1019 linker_args.push((LinkArgTarget::Cdylib, value))
1020 }
1021 "rustc-link-arg-bins" => {
1022 check_and_add_target!("bin", Target::is_bin, LinkArgTarget::Bin);
1023 }
1024 "rustc-link-arg-bin" => {
1025 let (bin_name, arg) = value.split_once('=').ok_or_else(|| {
1026 anyhow::format_err!(
1027 "invalid instruction `{}{}={}` from {}\n\
1028 The instruction should have the form {}{}=BIN=ARG",
1029 syntax_prefix,
1030 key,
1031 value,
1032 whence,
1033 syntax_prefix,
1034 key
1035 )
1036 })?;
1037 if !targets
1038 .iter()
1039 .any(|target| target.is_bin() && target.name() == bin_name)
1040 {
1041 bail!(
1042 "invalid instruction `{}{}` from {}\n\
1043 The package {} does not have a bin target with the name `{}`.",
1044 syntax_prefix,
1045 key,
1046 whence,
1047 pkg_descr,
1048 bin_name
1049 );
1050 }
1051 linker_args.push((
1052 LinkArgTarget::SingleBin(bin_name.to_owned()),
1053 arg.to_string(),
1054 ));
1055 }
1056 "rustc-link-arg-tests" => {
1057 check_and_add_target!("test", Target::is_test, LinkArgTarget::Test);
1058 }
1059 "rustc-link-arg-benches" => {
1060 check_and_add_target!("benchmark", Target::is_bench, LinkArgTarget::Bench);
1061 }
1062 "rustc-link-arg-examples" => {
1063 check_and_add_target!("example", Target::is_example, LinkArgTarget::Example);
1064 }
1065 "rustc-link-arg" => {
1066 linker_args.push((LinkArgTarget::All, value));
1067 }
1068 "rustc-cfg" => cfgs.push(value.to_string()),
1069 "rustc-check-cfg" => check_cfgs.push(value.to_string()),
1070 "rustc-env" => {
1071 let (key, val) = BuildOutput::parse_rustc_env(&value, &whence)?;
1072 if key == "RUSTC_BOOTSTRAP" {
1075 let rustc_bootstrap_allows = |name: Option<&str>| {
1085 let name = match name {
1086 None => return false,
1090 Some(n) => n,
1091 };
1092 #[expect(
1093 clippy::disallowed_methods,
1094 reason = "consistency with rustc, not specified behavior"
1095 )]
1096 std::env::var("RUSTC_BOOTSTRAP")
1097 .map_or(false, |var| var.split(',').any(|s| s == name))
1098 };
1099 if nightly_features_allowed
1100 || rustc_bootstrap_allows(library_name.as_deref())
1101 {
1102 log_messages.push((Severity::Warning, format!("cannot set `RUSTC_BOOTSTRAP={}` from {}.\n\
1103 note: crates cannot set `RUSTC_BOOTSTRAP` themselves, as doing so would subvert the stability guarantees of Rust for your project.",
1104 val, whence
1105 )));
1106 } else {
1107 bail!(
1110 "cannot set `RUSTC_BOOTSTRAP={}` from {}.\n\
1111 note: crates cannot set `RUSTC_BOOTSTRAP` themselves, as doing so would subvert the stability guarantees of Rust for your project.\n\
1112 help: If you're sure you want to do this in your project, set the environment variable `RUSTC_BOOTSTRAP={}` before running cargo instead.",
1113 val,
1114 whence,
1115 library_name.as_deref().unwrap_or("1"),
1116 );
1117 }
1118 } else {
1119 env.push((key, val));
1120 }
1121 }
1122 "error" => log_messages.push((Severity::Error, value.to_string())),
1123 "warning" => log_messages.push((Severity::Warning, value.to_string())),
1124 "rerun-if-changed" => rerun_if_changed.push(PathBuf::from(value)),
1125 "rerun-if-env-changed" => rerun_if_env_changed.push(value.to_string()),
1126 "metadata" => {
1127 let (key, value) = parse_metadata(whence.as_str(), line, &value, old_syntax)?;
1128 metadata.push((key.to_owned(), value.to_owned()));
1129 }
1130 _ => bail!(
1131 "invalid output in {whence}: `{line}`\n\
1132 Unknown key: `{key}`.\n\
1133 {DOCS_LINK_SUGGESTION}",
1134 ),
1135 }
1136 }
1137
1138 Ok(BuildOutput {
1139 library_paths,
1140 library_links,
1141 linker_args,
1142 cfgs,
1143 check_cfgs,
1144 env,
1145 metadata,
1146 rerun_if_changed,
1147 rerun_if_env_changed,
1148 log_messages,
1149 })
1150 }
1151
1152 pub fn parse_rustc_flags(
1156 value: &str,
1157 whence: &str,
1158 ) -> CargoResult<(Vec<PathBuf>, Vec<String>)> {
1159 let value = value.trim();
1160 let mut flags_iter = value
1161 .split(|c: char| c.is_whitespace())
1162 .filter(|w| w.chars().any(|c| !c.is_whitespace()));
1163 let (mut library_paths, mut library_links) = (Vec::new(), Vec::new());
1164
1165 while let Some(flag) = flags_iter.next() {
1166 if flag.starts_with("-l") || flag.starts_with("-L") {
1167 let (flag, mut value) = flag.split_at(2);
1171 if value.is_empty() {
1172 value = match flags_iter.next() {
1173 Some(v) => v,
1174 None => bail! {
1175 "flag in rustc-flags has no value in {}: {}",
1176 whence,
1177 value
1178 },
1179 }
1180 }
1181
1182 match flag {
1183 "-l" => library_links.push(value.to_string()),
1184 "-L" => library_paths.push(PathBuf::from(value)),
1185
1186 _ => unreachable!(),
1188 };
1189 } else {
1190 bail!(
1191 "only `-l` and `-L` flags are allowed in {}: `{}`",
1192 whence,
1193 value
1194 )
1195 }
1196 }
1197 Ok((library_paths, library_links))
1198 }
1199
1200 pub fn parse_rustc_env(value: &str, whence: &str) -> CargoResult<(String, String)> {
1204 match value.split_once('=') {
1205 Some((n, v)) => Ok((n.to_owned(), v.to_owned())),
1206 _ => bail!("Variable rustc-env has no value in {whence}: {value}"),
1207 }
1208 }
1209}
1210
1211fn prepare_metabuild(
1215 build_runner: &BuildRunner<'_, '_>,
1216 unit: &Unit,
1217 deps: &[String],
1218) -> CargoResult<()> {
1219 let mut output = Vec::new();
1220 let available_deps = build_runner.unit_deps(unit);
1221 let meta_deps: Vec<_> = deps
1223 .iter()
1224 .filter_map(|name| {
1225 available_deps
1226 .iter()
1227 .find(|d| d.unit.pkg.name().as_str() == name.as_str())
1228 .map(|d| d.unit.target.crate_name())
1229 })
1230 .collect();
1231 output.push("fn main() {\n".to_string());
1232 for dep in &meta_deps {
1233 output.push(format!(" {}::metabuild();\n", dep));
1234 }
1235 output.push("}\n".to_string());
1236 let output = output.join("");
1237 let path = unit
1238 .pkg
1239 .manifest()
1240 .metabuild_path(build_runner.bcx.ws.build_dir());
1241 paths::create_dir_all(path.parent().unwrap())?;
1242 paths::write_if_changed(path, &output)?;
1243 Ok(())
1244}
1245
1246impl BuildDeps {
1247 pub fn new(output_file: &Path, output: Option<&BuildOutput>) -> BuildDeps {
1250 BuildDeps {
1251 build_script_output: output_file.to_path_buf(),
1252 rerun_if_changed: output
1253 .map(|p| &p.rerun_if_changed)
1254 .cloned()
1255 .unwrap_or_default(),
1256 rerun_if_env_changed: output
1257 .map(|p| &p.rerun_if_env_changed)
1258 .cloned()
1259 .unwrap_or_default(),
1260 }
1261 }
1262}
1263
1264pub fn build_map(build_runner: &mut BuildRunner<'_, '_>) -> CargoResult<()> {
1286 let mut ret = HashMap::default();
1287 for unit in &build_runner.bcx.roots {
1288 build(&mut ret, build_runner, unit)?;
1289 }
1290 build_runner
1291 .build_scripts
1292 .extend(ret.into_iter().map(|(k, v)| (k, Arc::new(v))));
1293 return Ok(());
1294
1295 fn build<'a>(
1298 out: &'a mut HashMap<Unit, BuildScripts>,
1299 build_runner: &mut BuildRunner<'_, '_>,
1300 unit: &Unit,
1301 ) -> CargoResult<&'a BuildScripts> {
1302 if out.contains_key(unit) {
1305 return Ok(&out[unit]);
1306 }
1307
1308 if unit.mode.is_run_custom_build() {
1310 if let Some(links) = unit.pkg.manifest().links() {
1311 if let Some(output) = unit.links_overrides.get(links) {
1312 let metadata = build_runner.get_run_build_script_metadata(unit);
1313 build_runner.build_script_outputs.lock().unwrap().insert(
1314 unit.pkg.package_id(),
1315 metadata,
1316 output.clone(),
1317 );
1318 }
1319 }
1320 }
1321
1322 let mut ret = BuildScripts::default();
1323
1324 if !unit.target.is_custom_build() && unit.pkg.has_custom_build() {
1326 let script_metas = build_runner
1327 .find_build_script_metadatas(unit)
1328 .expect("has_custom_build should have RunCustomBuild");
1329 for script_meta in script_metas {
1330 add_to_link(&mut ret, unit.pkg.package_id(), script_meta);
1331 }
1332 }
1333
1334 if unit.mode.is_run_custom_build() {
1335 parse_previous_explicit_deps(build_runner, unit);
1336 }
1337
1338 let mut dependencies: Vec<Unit> = build_runner
1343 .unit_deps(unit)
1344 .iter()
1345 .map(|d| d.unit.clone())
1346 .collect();
1347 dependencies.sort_by_key(|u| u.pkg.package_id());
1348
1349 for dep_unit in dependencies.iter() {
1350 let dep_scripts = build(out, build_runner, dep_unit)?;
1351
1352 if dep_unit.target.for_host() {
1353 ret.plugins.extend(dep_scripts.to_link.iter().cloned());
1354 } else if dep_unit.target.is_linkable() {
1355 for &(pkg, metadata) in dep_scripts.to_link.iter() {
1356 add_to_link(&mut ret, pkg, metadata);
1357 }
1358 }
1359 }
1360
1361 match out.entry(unit.clone()) {
1362 Entry::Vacant(entry) => Ok(entry.insert(ret)),
1363 Entry::Occupied(_) => panic!("cyclic dependencies in `build_map`"),
1364 }
1365 }
1366
1367 fn add_to_link(scripts: &mut BuildScripts, pkg: PackageId, metadata: UnitHash) {
1370 if scripts.seen_to_link.insert((pkg, metadata)) {
1371 scripts.to_link.push((pkg, metadata));
1372 }
1373 }
1374
1375 fn parse_previous_explicit_deps(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) {
1377 let run_files = BuildScriptRunFiles::for_unit(build_runner, unit);
1378 let (prev_output, _) = prev_build_output(build_runner, unit);
1379 let deps = BuildDeps::new(&run_files.stdout, prev_output.as_ref());
1380 build_runner.build_explicit_deps.insert(unit.clone(), deps);
1381 }
1382}
1383
1384fn prev_build_output(
1390 build_runner: &mut BuildRunner<'_, '_>,
1391 unit: &Unit,
1392) -> (Option<BuildOutput>, PathBuf) {
1393 let script_out_dir = if build_runner.bcx.gctx.cli_unstable().build_dir_new_layout {
1394 build_runner.files().out_dir_new_layout(unit)
1395 } else {
1396 build_runner.files().build_script_out_dir(unit)
1397 };
1398 let run_files = BuildScriptRunFiles::for_unit(build_runner, unit);
1399
1400 let prev_script_out_dir = paths::read_bytes(&run_files.root_output)
1401 .and_then(|bytes| paths::bytes2path(&bytes))
1402 .unwrap_or_else(|_| script_out_dir.clone());
1403
1404 (
1405 BuildOutput::parse_file(
1406 &run_files.stdout,
1407 unit.pkg.library().map(|t| t.crate_name()),
1408 &unit.pkg.to_string(),
1409 &prev_script_out_dir,
1410 &script_out_dir,
1411 build_runner.bcx.gctx.nightly_features_allowed,
1412 unit.pkg.targets(),
1413 &unit.pkg.rust_version().cloned(),
1414 )
1415 .ok(),
1416 prev_script_out_dir,
1417 )
1418}
1419
1420impl BuildScriptOutputs {
1421 fn insert(&mut self, pkg_id: PackageId, metadata: UnitHash, parsed_output: BuildOutput) {
1423 match self.outputs.entry(metadata) {
1424 Entry::Vacant(entry) => {
1425 entry.insert(parsed_output);
1426 }
1427 Entry::Occupied(entry) => panic!(
1428 "build script output collision for {}/{}\n\
1429 old={:?}\nnew={:?}",
1430 pkg_id,
1431 metadata,
1432 entry.get(),
1433 parsed_output
1434 ),
1435 }
1436 }
1437
1438 fn contains_key(&self, metadata: UnitHash) -> bool {
1440 self.outputs.contains_key(&metadata)
1441 }
1442
1443 pub fn get(&self, meta: UnitHash) -> Option<&BuildOutput> {
1445 self.outputs.get(&meta)
1446 }
1447
1448 pub fn iter(&self) -> impl Iterator<Item = (&UnitHash, &BuildOutput)> {
1450 self.outputs.iter()
1451 }
1452}
1453
1454struct BuildScriptRunFiles {
1456 root: PathBuf,
1458 stdout: PathBuf,
1460 stderr: PathBuf,
1462 root_output: PathBuf,
1465}
1466
1467impl BuildScriptRunFiles {
1468 pub fn for_unit(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> Self {
1469 let root = build_runner.files().build_script_run_dir(unit);
1470 let stdout = if build_runner.bcx.gctx.cli_unstable().build_dir_new_layout {
1471 root.join("stdout")
1472 } else {
1473 root.join("output")
1474 };
1475 let stderr = root.join("stderr");
1476 let root_output = root.join("root-output");
1477 Self {
1478 root,
1479 stdout,
1480 stderr,
1481 root_output,
1482 }
1483 }
1484}