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