Skip to main content

cargo/core/compiler/
custom_build.rs

1//! How to execute a build script and parse its output.
2//!
3//! ## Preparing a build script run
4//!
5//! A [build script] is an optional Rust script Cargo will run before building
6//! your package. As of this writing, two kinds of special [`Unit`]s will be
7//! constructed when there is a build script in a package.
8//!
9//! * Build script compilation --- This unit is generally the same as units
10//!   that would compile other Cargo targets. It will recursively creates units
11//!   of its dependencies. One biggest difference is that the [`Unit`] of
12//!   compiling a build script is flagged as [`TargetKind::CustomBuild`].
13//! * Build script execution --- During the construction of the [`UnitGraph`],
14//!   Cargo inserts a [`Unit`] with [`CompileMode::RunCustomBuild`]. This unit
15//!   depends on the unit of compiling the associated build script, to ensure
16//!   the executable is available before running. The [`Work`] of running the
17//!   build script is prepared in the function [`prepare`].
18//!
19//! ## Running a build script
20//!
21//! When running a build script, Cargo is aware of the progress and the result
22//! of a build script. Standard output is the chosen interprocess communication
23//! between Cargo and build script processes. A set of strings is defined for
24//! that purpose. These strings, a.k.a. instructions, are interpreted by
25//! [`BuildOutput::parse`] and stored in [`BuildRunner::build_script_outputs`].
26//! The entire execution work is constructed by [`build_work`].
27//!
28//! [build script]: https://doc.rust-lang.org/nightly/cargo/reference/build-scripts.html
29//! [`TargetKind::CustomBuild`]: crate::core::manifest::TargetKind::CustomBuild
30//! [`UnitGraph`]: super::unit_graph::UnitGraph
31//! [`CompileMode::RunCustomBuild`]: crate::core::compiler::CompileMode::RunCustomBuild
32//! [instructions]: https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script
33
34use 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
53/// A build script instruction that tells Cargo to display an error after the
54/// build script has finished running. Read [the doc] for more.
55///
56/// [the doc]: https://doc.rust-lang.org/nightly/cargo/reference/build-scripts.html#cargo-error
57const CARGO_ERROR_SYNTAX: &str = "cargo::error=";
58/// Deprecated: A build script instruction that tells Cargo to display a warning after the
59/// build script has finished running. Read [the doc] for more.
60///
61/// [the doc]: https://doc.rust-lang.org/nightly/cargo/reference/build-scripts.html#cargo-warning
62const OLD_CARGO_WARNING_SYNTAX: &str = "cargo:warning=";
63/// A build script instruction that tells Cargo to display a warning after the
64/// build script has finished running. Read [the doc] for more.
65///
66/// [the doc]: https://doc.rust-lang.org/nightly/cargo/reference/build-scripts.html#cargo-warning
67const 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/// Represents a path added to the library search path.
78///
79/// We need to keep track of requests to add search paths within the cargo build directory
80/// separately from paths outside of Cargo. The reason is that we want to give precedence to linking
81/// against libraries within the Cargo build directory even if a similar library exists in the
82/// system (e.g. crate A adds `/usr/lib` to the search path and then a later build of crate B adds
83/// `target/debug/...` to satisfy its request to link against the library B that it built, but B is
84/// also found in `/usr/lib`).
85///
86/// There's some nuance here because we want to preserve relative order of paths of the same type.
87/// For example, if the build process would in declaration order emit the following linker line:
88/// ```bash
89/// -L/usr/lib -Ltarget/debug/build/crate1/libs -L/lib -Ltarget/debug/build/crate2/libs)
90/// ```
91///
92/// we want the linker to actually receive:
93/// ```bash
94/// -Ltarget/debug/build/crate1/libs -Ltarget/debug/build/crate2/libs) -L/usr/lib -L/lib
95/// ```
96///
97/// so that the library search paths within the crate artifacts directory come first but retain
98/// relative ordering while the system library paths come after while still retaining relative
99/// ordering among them; ordering is the order they are emitted within the build process,
100/// not lexicographic order.
101///
102/// WARNING: Even though this type implements PartialOrd + Ord, this is a lexicographic ordering.
103/// The linker line will require an explicit sorting algorithm. PartialOrd + Ord is derived because
104/// BuildOutput requires it but that ordering is different from the one for the linker search path,
105/// at least today. It may be worth reconsidering & perhaps it's ok if BuildOutput doesn't have
106/// a lexicographic ordering for the library_paths? I'm not sure the consequence of that.
107#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
108pub enum LibraryPath {
109    /// The path is pointing within the output folder of the crate and takes priority over
110    /// external paths when passed to the linker.
111    CargoArtifact(PathBuf),
112    /// The path is pointing outside of the crate's build location. The linker will always
113    /// receive such paths after `CargoArtifact`.
114    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/// Contains the parsed output of a custom build script.
143#[derive(Clone, Debug, Hash, Default, PartialEq, Eq, PartialOrd, Ord)]
144pub struct BuildOutput {
145    /// Paths to pass to rustc with the `-L` flag.
146    pub library_paths: Vec<LibraryPath>,
147    /// Names and link kinds of libraries, suitable for the `-l` flag.
148    pub library_links: Vec<String>,
149    /// Linker arguments suitable to be passed to `-C link-arg=<args>`
150    pub linker_args: Vec<(LinkArgTarget, String)>,
151    /// Various `--cfg` flags to pass to the compiler.
152    pub cfgs: Vec<String>,
153    /// Various `--check-cfg` flags to pass to the compiler.
154    pub check_cfgs: Vec<String>,
155    /// Additional environment variables to run the compiler with.
156    pub env: Vec<(String, String)>,
157    /// Metadata to pass to the immediate dependencies.
158    pub metadata: Vec<(String, String)>,
159    /// Paths to trigger a rerun of this build script.
160    /// May be absolute or relative paths (relative to package root).
161    pub rerun_if_changed: Vec<PathBuf>,
162    /// Environment variables which, when changed, will cause a rebuild.
163    pub rerun_if_env_changed: Vec<String>,
164    /// Errors and warnings generated by this build.
165    ///
166    /// These are only displayed if this is a "local" package, `-vv` is used, or
167    /// there is a build error for any target in this package. Note that any log
168    /// message of severity `Error` will by itself cause a build error, and will
169    /// cause all log messages to be displayed.
170    pub log_messages: Vec<LogMessage>,
171}
172
173/// Map of packages to build script output.
174///
175/// This initially starts out as empty. Overridden build scripts get
176/// inserted during `build_map`. The rest of the entries are added
177/// immediately after each build script runs.
178///
179/// The [`UnitHash`] is the unique metadata hash for the `RunCustomBuild` Unit of
180/// the package. It needs a unique key, since the build script can be run
181/// multiple times with different profiles or features. We can't embed a
182/// `Unit` because this structure needs to be shareable between threads.
183#[derive(Default)]
184pub struct BuildScriptOutputs {
185    outputs: HashMap<UnitHash, BuildOutput>,
186}
187
188/// Linking information for a `Unit`.
189///
190/// See [`build_map`] for more details.
191#[derive(Default)]
192pub struct BuildScripts {
193    /// List of build script outputs this Unit needs to include for linking. Each
194    /// element is an index into `BuildScriptOutputs`.
195    ///
196    /// Cargo will use this `to_link` vector to add `-L` flags to compiles as we
197    /// propagate them upwards towards the final build. Note, however, that we
198    /// need to preserve the ordering of `to_link` to be topologically sorted.
199    /// This will ensure that build scripts which print their paths properly will
200    /// correctly pick up the files they generated (if there are duplicates
201    /// elsewhere).
202    ///
203    /// To preserve this ordering, the (id, metadata) is stored in two places, once
204    /// in the `Vec` and once in `seen_to_link` for a fast lookup. We maintain
205    /// this as we're building interactively below to ensure that the memory
206    /// usage here doesn't blow up too much.
207    ///
208    /// For more information, see #2354.
209    pub to_link: Vec<(PackageId, UnitHash)>,
210    /// This is only used while constructing `to_link` to avoid duplicates.
211    seen_to_link: HashSet<(PackageId, UnitHash)>,
212    /// Host-only dependencies that have build scripts. Each element is an
213    /// index into `BuildScriptOutputs`.
214    ///
215    /// This is the set of transitive dependencies that are host-only
216    /// (proc-macro, plugin, build-dependency) that contain a build script.
217    /// Any `BuildOutput::library_paths` path relative to `target` will be
218    /// added to `LD_LIBRARY_PATH` so that the compiler can find any dynamic
219    /// libraries a build script may have generated.
220    pub plugins: BTreeSet<(PackageId, UnitHash)>,
221}
222
223/// Dependency information as declared by a build script that might trigger
224/// a recompile of itself.
225#[derive(Debug)]
226pub struct BuildDeps {
227    /// Absolute path to the file in the target directory that stores the
228    /// output of the build script.
229    pub build_script_output: PathBuf,
230    /// Files that trigger a rebuild if they change.
231    pub rerun_if_changed: Vec<PathBuf>,
232    /// Environment variables that trigger a rebuild if they change.
233    pub rerun_if_env_changed: Vec<String>,
234}
235
236/// Represents one of the instructions from `cargo::rustc-link-arg-*` build
237/// script instruction family.
238///
239/// In other words, indicates targets that custom linker arguments applies to.
240///
241/// See the [build script documentation][1] for more.
242///
243/// [1]: https://doc.rust-lang.org/nightly/cargo/reference/build-scripts.html#cargorustc-link-argflag
244#[derive(Clone, Hash, Debug, PartialEq, Eq, PartialOrd, Ord)]
245pub enum LinkArgTarget {
246    /// Represents `cargo::rustc-link-arg=FLAG`.
247    All,
248    /// Represents `cargo::rustc-cdylib-link-arg=FLAG`.
249    Cdylib,
250    /// Represents `cargo::rustc-link-arg-bins=FLAG`.
251    Bin,
252    /// Represents `cargo::rustc-link-arg-bin=BIN=FLAG`.
253    SingleBin(String),
254    /// Represents `cargo::rustc-link-arg-tests=FLAG`.
255    Test,
256    /// Represents `cargo::rustc-link-arg-benches=FLAG`.
257    Bench,
258    /// Represents `cargo::rustc-link-arg-examples=FLAG`.
259    Example,
260}
261
262impl LinkArgTarget {
263    /// Checks if this link type applies to a given [`Target`].
264    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/// Prepares a `Work` that executes the target as a custom build script.
279#[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        // The output is already set, thus the build script is overridden.
289        fingerprint::prepare_target(build_runner, unit, false)
290    } else {
291        build_work(build_runner, unit)
292    }
293}
294
295/// Emits the output of a build script as a [`machine_message::BuildScript`]
296/// JSON string to standard output.
297fn 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
322/// Constructs the unit of work of running a build script.
323///
324/// The construction includes:
325///
326/// * Set environment variables for the build script run.
327/// * Create the output dir (`OUT_DIR`) for the build script output.
328/// * Determine if the build script needs a re-run.
329/// * Run the build script and store its output.
330fn build_work(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResult<Job> {
331    assert!(unit.mode.is_run_custom_build());
332    let bcx = &build_runner.bcx;
333    let dependencies = build_runner.unit_deps(unit);
334    let build_script_unit = dependencies
335        .iter()
336        .find(|d| !d.unit.mode.is_run_custom_build() && d.unit.target.is_custom_build())
337        .map(|d| &d.unit)
338        .expect("running a script not depending on an actual script");
339    let script_dir = build_runner.files().build_script_dir(build_script_unit);
340
341    let script_out_dir = if bcx.gctx.cli_unstable().build_dir_new_layout {
342        build_runner.files().out_dir_new_layout(unit)
343    } else {
344        build_runner.files().build_script_out_dir(unit)
345    };
346
347    if let Some(deps) = unit.pkg.manifest().metabuild() {
348        prepare_metabuild(build_runner, build_script_unit, deps)?;
349    }
350
351    // Building the command to execute
352    let bin_name = if bcx.gctx.cli_unstable().build_dir_new_layout {
353        unit.target.crate_name()
354    } else {
355        unit.target.name().to_string()
356    };
357    let to_exec = script_dir.join(bin_name);
358
359    // Start preparing the process to execute, starting out with some
360    // environment variables. Note that the profile-related environment
361    // variables are not set with this the build script's profile but rather the
362    // package's library profile.
363    // NOTE: if you add any profile flags, be sure to update
364    // `Profiles::get_profile_run_custom_build` so that those flags get
365    // carried over.
366    let to_exec = to_exec.into_os_string();
367    let mut cmd = build_runner.compilation.host_process(to_exec, &unit.pkg)?;
368    let debug = unit.profile.debuginfo.is_turned_on();
369    cmd.env("OUT_DIR", &script_out_dir)
370        .env("CARGO_MANIFEST_DIR", unit.pkg.root())
371        .env("CARGO_MANIFEST_PATH", unit.pkg.manifest_path())
372        .env("NUM_JOBS", &bcx.jobs().to_string())
373        .env("TARGET", bcx.target_data.short_name(&unit.kind))
374        .env("DEBUG", debug.to_string())
375        .env("OPT_LEVEL", &unit.profile.opt_level)
376        .env(
377            "PROFILE",
378            match unit.profile.root {
379                ProfileRoot::Release => "release",
380                ProfileRoot::Debug => "debug",
381            },
382        )
383        .env("HOST", &bcx.host_triple())
384        .env("RUSTC", &bcx.rustc().path)
385        .env("RUSTDOC", &*bcx.gctx.rustdoc()?)
386        .inherit_jobserver(&build_runner.jobserver);
387
388    // Find all artifact dependencies and make their file and containing directory discoverable using environment variables.
389    for (var, value) in artifact::get_env(build_runner, unit, dependencies)? {
390        cmd.env(&var, value);
391    }
392
393    if let Some(linker) = &build_runner.compilation.target_linker(unit.kind) {
394        cmd.env("RUSTC_LINKER", linker);
395    }
396
397    if let Some(links) = unit.pkg.manifest().links() {
398        cmd.env("CARGO_MANIFEST_LINKS", links);
399    }
400
401    if let Some(trim_paths) = unit.profile.trim_paths.as_ref() {
402        cmd.env("CARGO_TRIM_PATHS", trim_paths.to_string());
403    }
404
405    // Be sure to pass along all enabled features for this package, this is the
406    // last piece of statically known information that we have.
407    for feat in &unit.features {
408        cmd.env(&format!("CARGO_FEATURE_{}", super::envify(feat)), "1");
409    }
410
411    let mut cfg_map = HashMap::new();
412    cfg_map.insert(
413        "feature",
414        unit.features.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
415    );
416    // Manually inject debug_assertions based on the profile setting.
417    // The cfg query from rustc doesn't include profile settings and would always be true,
418    // so we override it with the actual profile setting.
419    if unit.profile.debug_assertions {
420        cfg_map.insert("debug_assertions", Vec::new());
421    }
422    for cfg in bcx.target_data.cfg(unit.kind) {
423        match *cfg {
424            Cfg::Name(ref n) => {
425                // Skip debug_assertions from rustc query; we use the profile setting instead
426                if n.as_str() == "debug_assertions" {
427                    continue;
428                }
429                cfg_map.insert(n.as_str(), Vec::new());
430            }
431            Cfg::KeyPair(ref k, ref v) => {
432                let values = cfg_map.entry(k.as_str()).or_default();
433                values.push(v.as_str());
434            }
435        }
436    }
437    for (k, v) in cfg_map {
438        // FIXME: We should handle raw-idents somehow instead of pretending they
439        // don't exist here
440        let k = format!("CARGO_CFG_{}", super::envify(k));
441        cmd.env(&k, v.join(","));
442    }
443
444    // Also inform the build script of the rustc compiler context.
445    if let Some(wrapper) = bcx.rustc().wrapper.as_ref() {
446        cmd.env("RUSTC_WRAPPER", wrapper);
447    } else {
448        cmd.env_remove("RUSTC_WRAPPER");
449    }
450    cmd.env_remove("RUSTC_WORKSPACE_WRAPPER");
451    if build_runner.bcx.ws.is_member(&unit.pkg) {
452        if let Some(wrapper) = bcx.rustc().workspace_wrapper.as_ref() {
453            cmd.env("RUSTC_WORKSPACE_WRAPPER", wrapper);
454        }
455    }
456    cmd.env("CARGO_ENCODED_RUSTFLAGS", unit.rustflags.join("\x1f"));
457    cmd.env_remove("RUSTFLAGS");
458
459    if build_runner.bcx.ws.gctx().extra_verbose() {
460        cmd.display_env_vars();
461    }
462
463    let any_build_script_metadata = bcx.gctx.cli_unstable().any_build_script_metadata;
464
465    // Gather the set of native dependencies that this package has along with
466    // some other variables to close over.
467    //
468    // This information will be used at build-time later on to figure out which
469    // sorts of variables need to be discovered at that time.
470    let lib_deps = dependencies
471        .iter()
472        .filter_map(|dep| {
473            if dep.unit.mode.is_run_custom_build() {
474                let dep_metadata = build_runner.get_run_build_script_metadata(&dep.unit);
475
476                let dep_name = dep.dep_name.unwrap_or(dep.unit.pkg.name());
477
478                Some((
479                    dep_name,
480                    dep.unit
481                        .pkg
482                        .manifest()
483                        .links()
484                        .map(|links| links.to_string()),
485                    dep.unit.pkg.package_id(),
486                    dep_metadata,
487                ))
488            } else {
489                None
490            }
491        })
492        .collect::<Vec<_>>();
493    let library_name = unit.pkg.library().map(|t| t.crate_name());
494    let pkg_descr = unit.pkg.to_string();
495    let build_script_outputs = Arc::clone(&build_runner.build_script_outputs);
496    let id = unit.pkg.package_id();
497    let run_files = BuildScriptRunFiles::for_unit(build_runner, unit);
498    let host_target_root = build_runner.files().host_dest().map(|v| v.to_path_buf());
499    let all = (
500        id,
501        library_name.clone(),
502        pkg_descr.clone(),
503        Arc::clone(&build_script_outputs),
504        run_files.stdout.clone(),
505        script_out_dir.clone(),
506    );
507    let build_scripts = build_runner.build_scripts.get(unit).cloned();
508    let json_messages = bcx.build_config.emit_json();
509    let extra_verbose = bcx.gctx.extra_verbose();
510    let (prev_output, prev_script_out_dir) = prev_build_output(build_runner, unit);
511    let metadata_hash = build_runner.get_run_build_script_metadata(unit);
512
513    paths::create_dir_all(&script_dir)?;
514    paths::create_dir_all(&script_out_dir)?;
515    paths::create_dir_all(&run_files.root)?;
516
517    let nightly_features_allowed = build_runner.bcx.gctx.nightly_features_allowed;
518    let targets: Vec<Target> = unit.pkg.targets().to_vec();
519    let msrv = unit.pkg.rust_version().cloned();
520    // Need a separate copy for the fresh closure.
521    let targets_fresh = targets.clone();
522    let msrv_fresh = msrv.clone();
523
524    let env_profile_name = unit.profile.name.to_uppercase();
525    let built_with_debuginfo = build_runner
526        .bcx
527        .unit_graph
528        .get(unit)
529        .and_then(|deps| deps.iter().find(|dep| dep.unit.target == unit.target))
530        .map(|dep| dep.unit.profile.debuginfo.is_turned_on())
531        .unwrap_or(false);
532
533    // Prepare the unit of "dirty work" which will actually run the custom build
534    // command.
535    //
536    // Note that this has to do some extra work just before running the command
537    // to determine extra environment variables and such.
538    let dirty = Work::new(move |state| {
539        // Make sure that OUT_DIR exists.
540        //
541        // If we have an old build directory, then just move it into place,
542        // otherwise create it!
543        paths::create_dir_all(&script_out_dir)
544            .context("failed to create script output directory for build command")?;
545
546        // For all our native lib dependencies, pick up their metadata to pass
547        // along to this custom build command. We're also careful to augment our
548        // dynamic library search path in case the build script depended on any
549        // native dynamic libraries.
550        {
551            let build_script_outputs = build_script_outputs.lock().unwrap();
552            for (name, links, dep_id, dep_metadata) in lib_deps {
553                let script_output = build_script_outputs.get(dep_metadata).ok_or_else(|| {
554                    internal(format!(
555                        "failed to locate build state for env vars: {}/{}",
556                        dep_id, dep_metadata
557                    ))
558                })?;
559                let data = &script_output.metadata;
560                for (key, value) in data.iter() {
561                    if let Some(ref links) = links {
562                        cmd.env(
563                            &format!("DEP_{}_{}", super::envify(&links), super::envify(key)),
564                            value,
565                        );
566                    }
567                    if any_build_script_metadata {
568                        cmd.env(
569                            &format!("CARGO_DEP_{}_{}", super::envify(&name), super::envify(key)),
570                            value,
571                        );
572                    }
573                }
574            }
575            if let Some(build_scripts) = build_scripts
576                && let Some(ref host_target_root) = host_target_root
577            {
578                super::add_plugin_deps(
579                    &mut cmd,
580                    &build_script_outputs,
581                    &build_scripts,
582                    host_target_root,
583                )?;
584            }
585        }
586
587        // And now finally, run the build command itself!
588        state.running(&cmd);
589        let timestamp = paths::set_invocation_time(&run_files.root)?;
590        let prefix = format!("[{} {}] ", id.name(), id.version());
591        let mut log_messages_in_case_of_panic = Vec::new();
592        let span = tracing::debug_span!("build_script", process = cmd.to_string());
593        let output = span.in_scope(|| {
594            cmd.exec_with_streaming(
595                &mut |stdout| {
596                    if let Some(error) = stdout.strip_prefix(CARGO_ERROR_SYNTAX) {
597                        log_messages_in_case_of_panic.push((Severity::Error, error.to_owned()));
598                    }
599                    if let Some(warning) = stdout
600                        .strip_prefix(OLD_CARGO_WARNING_SYNTAX)
601                        .or(stdout.strip_prefix(NEW_CARGO_WARNING_SYNTAX))
602                    {
603                        log_messages_in_case_of_panic.push((Severity::Warning, warning.to_owned()));
604                    }
605                    if extra_verbose {
606                        state.stdout(format!("{}{}", prefix, stdout))?;
607                    }
608                    Ok(())
609                },
610                &mut |stderr| {
611                    if extra_verbose {
612                        state.stderr(format!("{}{}", prefix, stderr))?;
613                    }
614                    Ok(())
615                },
616                true,
617            )
618            .with_context(|| {
619                let mut build_error_context =
620                    format!("failed to run custom build command for `{}`", pkg_descr);
621
622                // If we're opting into backtraces, mention that build dependencies' backtraces can
623                // be improved by requesting debuginfo to be built, if we're not building with
624                // debuginfo already.
625                #[expect(clippy::disallowed_methods, reason = "consistency with rustc")]
626                if let Ok(show_backtraces) = std::env::var("RUST_BACKTRACE") {
627                    if !built_with_debuginfo && show_backtraces != "0" {
628                        build_error_context.push_str(&format!(
629                            "\n\
630                            note: To improve backtraces for build dependencies, set the \
631                            CARGO_PROFILE_{env_profile_name}_BUILD_OVERRIDE_DEBUG=true environment \
632                            variable to enable debug information generation.",
633                        ));
634                    }
635                }
636
637                build_error_context
638            })
639        });
640
641        // If the build failed
642        if let Err(error) = output {
643            insert_log_messages_in_build_outputs(
644                build_script_outputs,
645                id,
646                metadata_hash,
647                log_messages_in_case_of_panic,
648            );
649            return Err(error);
650        }
651        // ... or it logged any errors
652        else if log_messages_in_case_of_panic
653            .iter()
654            .any(|(severity, _)| *severity == Severity::Error)
655        {
656            insert_log_messages_in_build_outputs(
657                build_script_outputs,
658                id,
659                metadata_hash,
660                log_messages_in_case_of_panic,
661            );
662            anyhow::bail!("build script logged errors");
663        }
664
665        let output = output.unwrap();
666
667        // After the build command has finished running, we need to be sure to
668        // remember all of its output so we can later discover precisely what it
669        // was, even if we don't run the build command again (due to freshness).
670        //
671        // This is also the location where we provide feedback into the build
672        // state informing what variables were discovered via our script as
673        // well.
674        paths::write(&run_files.stdout, &output.stdout)?;
675        // This mtime shift allows Cargo to detect if a source file was
676        // modified in the middle of the build.
677        paths::set_file_time_no_err(run_files.stdout, timestamp);
678        paths::write(&run_files.stderr, &output.stderr)?;
679        paths::write(&run_files.root_output, paths::path2bytes(&script_out_dir)?)?;
680        let parsed_output = BuildOutput::parse(
681            &output.stdout,
682            library_name,
683            &pkg_descr,
684            &script_out_dir,
685            &script_out_dir,
686            nightly_features_allowed,
687            &targets,
688            &msrv,
689        )?;
690
691        if json_messages {
692            emit_build_output(state, &parsed_output, script_out_dir.as_path(), id)?;
693        }
694        build_script_outputs
695            .lock()
696            .unwrap()
697            .insert(id, metadata_hash, parsed_output);
698        Ok(())
699    });
700
701    // Now that we've prepared our work-to-do, we need to prepare the fresh work
702    // itself to run when we actually end up just discarding what we calculated
703    // above.
704    let fresh = Work::new(move |state| {
705        let (id, library_name, pkg_descr, build_script_outputs, output_file, script_out_dir) = all;
706        let output = match prev_output {
707            Some(output) => output,
708            None => BuildOutput::parse_file(
709                &output_file,
710                library_name,
711                &pkg_descr,
712                &prev_script_out_dir,
713                &script_out_dir,
714                nightly_features_allowed,
715                &targets_fresh,
716                &msrv_fresh,
717            )?,
718        };
719
720        if json_messages {
721            emit_build_output(state, &output, script_out_dir.as_path(), id)?;
722        }
723
724        build_script_outputs
725            .lock()
726            .unwrap()
727            .insert(id, metadata_hash, output);
728        Ok(())
729    });
730
731    let mut job = fingerprint::prepare_target(build_runner, unit, false)?;
732    if job.freshness().is_dirty() {
733        job.before(dirty);
734    } else {
735        job.before(fresh);
736    }
737    Ok(job)
738}
739
740/// When a build script run fails, store only log messages, and nuke other
741/// outputs, as they are likely broken.
742fn insert_log_messages_in_build_outputs(
743    build_script_outputs: Arc<Mutex<BuildScriptOutputs>>,
744    id: PackageId,
745    metadata_hash: UnitHash,
746    log_messages: Vec<LogMessage>,
747) {
748    let build_output_with_only_log_messages = BuildOutput {
749        log_messages,
750        ..BuildOutput::default()
751    };
752    build_script_outputs.lock().unwrap().insert(
753        id,
754        metadata_hash,
755        build_output_with_only_log_messages,
756    );
757}
758
759impl BuildOutput {
760    /// Like [`BuildOutput::parse`] but from a file path.
761    pub fn parse_file(
762        path: &Path,
763        library_name: Option<String>,
764        pkg_descr: &str,
765        script_out_dir_when_generated: &Path,
766        script_out_dir: &Path,
767        nightly_features_allowed: bool,
768        targets: &[Target],
769        msrv: &Option<RustVersion>,
770    ) -> CargoResult<BuildOutput> {
771        let contents = paths::read_bytes(path)?;
772        BuildOutput::parse(
773            &contents,
774            library_name,
775            pkg_descr,
776            script_out_dir_when_generated,
777            script_out_dir,
778            nightly_features_allowed,
779            targets,
780            msrv,
781        )
782    }
783
784    /// Parses the output instructions of a build script.
785    ///
786    /// * `pkg_descr` --- for error messages
787    /// * `library_name` --- for determining if `RUSTC_BOOTSTRAP` should be allowed
788    pub fn parse(
789        input: &[u8],
790        // Takes String instead of InternedString so passing `unit.pkg.name()` will give a compile error.
791        library_name: Option<String>,
792        pkg_descr: &str,
793        script_out_dir_when_generated: &Path,
794        script_out_dir: &Path,
795        nightly_features_allowed: bool,
796        targets: &[Target],
797        msrv: &Option<RustVersion>,
798    ) -> CargoResult<BuildOutput> {
799        let mut library_paths = Vec::new();
800        let mut library_links = Vec::new();
801        let mut linker_args = Vec::new();
802        let mut cfgs = Vec::new();
803        let mut check_cfgs = Vec::new();
804        let mut env = Vec::new();
805        let mut metadata = Vec::new();
806        let mut rerun_if_changed = Vec::new();
807        let mut rerun_if_env_changed = Vec::new();
808        let mut log_messages = Vec::new();
809        let whence = format!("build script of `{}`", pkg_descr);
810        // Old syntax:
811        //    cargo:rustc-flags=VALUE
812        //    cargo:KEY=VALUE (for other unreserved keys)
813        // New syntax:
814        //    cargo::rustc-flags=VALUE
815        //    cargo::metadata=KEY=VALUE (for other unreserved keys)
816        // Due to backwards compatibility, no new keys can be added to this old format.
817        const RESERVED_PREFIXES: &[&str] = &[
818            "rustc-flags=",
819            "rustc-link-lib=",
820            "rustc-link-search=",
821            "rustc-link-arg-cdylib=",
822            "rustc-cdylib-link-arg=",
823            "rustc-link-arg-bins=",
824            "rustc-link-arg-bin=",
825            "rustc-link-arg-tests=",
826            "rustc-link-arg-benches=",
827            "rustc-link-arg-examples=",
828            "rustc-link-arg=",
829            "rustc-cfg=",
830            "rustc-check-cfg=",
831            "rustc-env=",
832            "warning=",
833            "rerun-if-changed=",
834            "rerun-if-env-changed=",
835        ];
836        const DOCS_LINK_SUGGESTION: &str = "See https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script \
837                for more information about build script outputs.";
838
839        fn has_reserved_prefix(flag: &str) -> bool {
840            RESERVED_PREFIXES
841                .iter()
842                .any(|reserved_prefix| flag.starts_with(reserved_prefix))
843        }
844
845        fn check_minimum_supported_rust_version_for_new_syntax(
846            pkg_descr: &str,
847            msrv: &Option<RustVersion>,
848            flag: &str,
849        ) -> CargoResult<()> {
850            if let Some(msrv) = msrv {
851                let new_syntax_added_in = RustVersion::new(1, 77, 0);
852                if !new_syntax_added_in.is_compatible_with(&msrv.to_partial()) {
853                    let old_syntax_suggestion = if has_reserved_prefix(flag) {
854                        format!(
855                            "Switch to the old `cargo:{flag}` syntax (note the single colon).\n"
856                        )
857                    } else if flag.starts_with("metadata=") {
858                        let old_format_flag = flag.strip_prefix("metadata=").unwrap();
859                        format!(
860                            "Switch to the old `cargo:{old_format_flag}` syntax instead of `cargo::{flag}` (note the single colon).\n"
861                        )
862                    } else {
863                        String::new()
864                    };
865
866                    bail!(
867                        "the `cargo::` syntax for build script output instructions was added in \
868                        Rust 1.77.0, but the minimum supported Rust version of `{pkg_descr}` is {msrv}.\n\
869                        {old_syntax_suggestion}\
870                        {DOCS_LINK_SUGGESTION}"
871                    );
872                }
873            }
874
875            Ok(())
876        }
877
878        fn parse_directive<'a>(
879            whence: &str,
880            line: &str,
881            data: &'a str,
882            old_syntax: bool,
883        ) -> CargoResult<(&'a str, &'a str)> {
884            let mut iter = data.splitn(2, "=");
885            let key = iter.next();
886            let value = iter.next();
887            match (key, value) {
888                (Some(a), Some(b)) => Ok((a, b.trim_end())),
889                _ => bail!(
890                    "invalid output in {whence}: `{line}`\n\
891                    Expected a line with `{syntax}KEY=VALUE` with an `=` character, \
892                    but none was found.\n\
893                    {DOCS_LINK_SUGGESTION}",
894                    syntax = if old_syntax { "cargo:" } else { "cargo::" },
895                ),
896            }
897        }
898
899        fn parse_metadata<'a>(
900            whence: &str,
901            line: &str,
902            data: &'a str,
903            old_syntax: bool,
904        ) -> CargoResult<(&'a str, &'a str)> {
905            let mut iter = data.splitn(2, "=");
906            let key = iter.next();
907            let value = iter.next();
908            match (key, value) {
909                (Some(a), Some(b)) => Ok((a, b.trim_end())),
910                _ => bail!(
911                    "invalid output in {whence}: `{line}`\n\
912                    Expected a line with `{syntax}KEY=VALUE` with an `=` character, \
913                    but none was found.\n\
914                    {DOCS_LINK_SUGGESTION}",
915                    syntax = if old_syntax {
916                        "cargo:"
917                    } else {
918                        "cargo::metadata="
919                    },
920                ),
921            }
922        }
923
924        for line in input.split(|b| *b == b'\n') {
925            let line = match str::from_utf8(line) {
926                Ok(line) => line.trim(),
927                Err(..) => continue,
928            };
929            let mut old_syntax = false;
930            let (key, value) = if let Some(data) = line.strip_prefix("cargo::") {
931                check_minimum_supported_rust_version_for_new_syntax(pkg_descr, msrv, data)?;
932                // For instance, `cargo::rustc-flags=foo` or `cargo::metadata=foo=bar`.
933                parse_directive(whence.as_str(), line, data, old_syntax)?
934            } else if let Some(data) = line.strip_prefix("cargo:") {
935                old_syntax = true;
936                // For instance, `cargo:rustc-flags=foo`.
937                if has_reserved_prefix(data) {
938                    parse_directive(whence.as_str(), line, data, old_syntax)?
939                } else {
940                    // For instance, `cargo:foo=bar`.
941                    ("metadata", data)
942                }
943            } else {
944                // Skip this line since it doesn't start with "cargo:" or "cargo::".
945                continue;
946            };
947            // This will rewrite paths if the target directory has been moved.
948            let value = value.replace(
949                script_out_dir_when_generated.to_str().unwrap(),
950                script_out_dir.to_str().unwrap(),
951            );
952
953            let syntax_prefix = if old_syntax { "cargo:" } else { "cargo::" };
954            macro_rules! check_and_add_target {
955                ($target_kind: expr, $is_target_kind: expr, $link_type: expr) => {
956                    if !targets.iter().any(|target| $is_target_kind(target)) {
957                        bail!(
958                            "invalid instruction `{}{}` from {}\n\
959                                The package {} does not have a {} target.",
960                            syntax_prefix,
961                            key,
962                            whence,
963                            pkg_descr,
964                            $target_kind
965                        );
966                    }
967                    linker_args.push(($link_type, value));
968                };
969            }
970
971            // Keep in sync with TargetConfig::parse_links_overrides.
972            match key {
973                "rustc-flags" => {
974                    let (paths, links) = BuildOutput::parse_rustc_flags(&value, &whence)?;
975                    library_links.extend(links.into_iter());
976                    library_paths.extend(
977                        paths
978                            .into_iter()
979                            .map(|p| LibraryPath::new(p, script_out_dir)),
980                    );
981                }
982                "rustc-link-lib" => library_links.push(value.to_string()),
983                "rustc-link-search" => {
984                    library_paths.push(LibraryPath::new(PathBuf::from(value), script_out_dir))
985                }
986                "rustc-link-arg-cdylib" | "rustc-cdylib-link-arg" => {
987                    if !targets.iter().any(|target| target.is_cdylib()) {
988                        log_messages.push((
989                            Severity::Warning,
990                            format!(
991                                "{}{} was specified in the build script of {}, \
992                             but that package does not contain a cdylib target\n\
993                             \n\
994                             Allowing this was an unintended change in the 1.50 \
995                             release, and may become an error in the future. \
996                             For more information, see \
997                             <https://github.com/rust-lang/cargo/issues/9562>.",
998                                syntax_prefix, key, pkg_descr
999                            ),
1000                        ));
1001                    }
1002                    linker_args.push((LinkArgTarget::Cdylib, value))
1003                }
1004                "rustc-link-arg-bins" => {
1005                    check_and_add_target!("bin", Target::is_bin, LinkArgTarget::Bin);
1006                }
1007                "rustc-link-arg-bin" => {
1008                    let (bin_name, arg) = value.split_once('=').ok_or_else(|| {
1009                        anyhow::format_err!(
1010                            "invalid instruction `{}{}={}` from {}\n\
1011                                The instruction should have the form {}{}=BIN=ARG",
1012                            syntax_prefix,
1013                            key,
1014                            value,
1015                            whence,
1016                            syntax_prefix,
1017                            key
1018                        )
1019                    })?;
1020                    if !targets
1021                        .iter()
1022                        .any(|target| target.is_bin() && target.name() == bin_name)
1023                    {
1024                        bail!(
1025                            "invalid instruction `{}{}` from {}\n\
1026                                The package {} does not have a bin target with the name `{}`.",
1027                            syntax_prefix,
1028                            key,
1029                            whence,
1030                            pkg_descr,
1031                            bin_name
1032                        );
1033                    }
1034                    linker_args.push((
1035                        LinkArgTarget::SingleBin(bin_name.to_owned()),
1036                        arg.to_string(),
1037                    ));
1038                }
1039                "rustc-link-arg-tests" => {
1040                    check_and_add_target!("test", Target::is_test, LinkArgTarget::Test);
1041                }
1042                "rustc-link-arg-benches" => {
1043                    check_and_add_target!("benchmark", Target::is_bench, LinkArgTarget::Bench);
1044                }
1045                "rustc-link-arg-examples" => {
1046                    check_and_add_target!("example", Target::is_example, LinkArgTarget::Example);
1047                }
1048                "rustc-link-arg" => {
1049                    linker_args.push((LinkArgTarget::All, value));
1050                }
1051                "rustc-cfg" => cfgs.push(value.to_string()),
1052                "rustc-check-cfg" => check_cfgs.push(value.to_string()),
1053                "rustc-env" => {
1054                    let (key, val) = BuildOutput::parse_rustc_env(&value, &whence)?;
1055                    // Build scripts aren't allowed to set RUSTC_BOOTSTRAP.
1056                    // See https://github.com/rust-lang/cargo/issues/7088.
1057                    if key == "RUSTC_BOOTSTRAP" {
1058                        // If RUSTC_BOOTSTRAP is already set, the user of Cargo knows about
1059                        // bootstrap and still wants to override the channel. Give them a way to do
1060                        // so, but still emit a warning that the current crate shouldn't be trying
1061                        // to set RUSTC_BOOTSTRAP.
1062                        // If this is a nightly build, setting RUSTC_BOOTSTRAP wouldn't affect the
1063                        // behavior, so still only give a warning.
1064                        // NOTE: cargo only allows nightly features on RUSTC_BOOTSTRAP=1, but we
1065                        // want setting any value of RUSTC_BOOTSTRAP to downgrade this to a warning
1066                        // (so that `RUSTC_BOOTSTRAP=library_name` will work)
1067                        let rustc_bootstrap_allows = |name: Option<&str>| {
1068                            let name = match name {
1069                                // as of 2021, no binaries on crates.io use RUSTC_BOOTSTRAP, so
1070                                // fine-grained opt-outs aren't needed. end-users can always use
1071                                // RUSTC_BOOTSTRAP=1 from the top-level if it's really a problem.
1072                                None => return false,
1073                                Some(n) => n,
1074                            };
1075                            #[expect(
1076                                clippy::disallowed_methods,
1077                                reason = "consistency with rustc, not specified behavior"
1078                            )]
1079                            std::env::var("RUSTC_BOOTSTRAP")
1080                                .map_or(false, |var| var.split(',').any(|s| s == name))
1081                        };
1082                        if nightly_features_allowed
1083                            || rustc_bootstrap_allows(library_name.as_deref())
1084                        {
1085                            log_messages.push((Severity::Warning, format!("cannot set `RUSTC_BOOTSTRAP={}` from {}.\n\
1086                                note: crates cannot set `RUSTC_BOOTSTRAP` themselves, as doing so would subvert the stability guarantees of Rust for your project.",
1087                                val, whence
1088                            )));
1089                        } else {
1090                            // Setting RUSTC_BOOTSTRAP would change the behavior of the crate.
1091                            // Abort with an error.
1092                            bail!(
1093                                "cannot set `RUSTC_BOOTSTRAP={}` from {}.\n\
1094                                note: crates cannot set `RUSTC_BOOTSTRAP` themselves, as doing so would subvert the stability guarantees of Rust for your project.\n\
1095                                help: If you're sure you want to do this in your project, set the environment variable `RUSTC_BOOTSTRAP={}` before running cargo instead.",
1096                                val,
1097                                whence,
1098                                library_name.as_deref().unwrap_or("1"),
1099                            );
1100                        }
1101                    } else {
1102                        env.push((key, val));
1103                    }
1104                }
1105                "error" => log_messages.push((Severity::Error, value.to_string())),
1106                "warning" => log_messages.push((Severity::Warning, value.to_string())),
1107                "rerun-if-changed" => rerun_if_changed.push(PathBuf::from(value)),
1108                "rerun-if-env-changed" => rerun_if_env_changed.push(value.to_string()),
1109                "metadata" => {
1110                    let (key, value) = parse_metadata(whence.as_str(), line, &value, old_syntax)?;
1111                    metadata.push((key.to_owned(), value.to_owned()));
1112                }
1113                _ => bail!(
1114                    "invalid output in {whence}: `{line}`\n\
1115                    Unknown key: `{key}`.\n\
1116                    {DOCS_LINK_SUGGESTION}",
1117                ),
1118            }
1119        }
1120
1121        Ok(BuildOutput {
1122            library_paths,
1123            library_links,
1124            linker_args,
1125            cfgs,
1126            check_cfgs,
1127            env,
1128            metadata,
1129            rerun_if_changed,
1130            rerun_if_env_changed,
1131            log_messages,
1132        })
1133    }
1134
1135    /// Parses [`cargo::rustc-flags`] instruction.
1136    ///
1137    /// [`cargo::rustc-flags`]: https://doc.rust-lang.org/nightly/cargo/reference/build-scripts.html#cargorustc-flagsflags
1138    pub fn parse_rustc_flags(
1139        value: &str,
1140        whence: &str,
1141    ) -> CargoResult<(Vec<PathBuf>, Vec<String>)> {
1142        let value = value.trim();
1143        let mut flags_iter = value
1144            .split(|c: char| c.is_whitespace())
1145            .filter(|w| w.chars().any(|c| !c.is_whitespace()));
1146        let (mut library_paths, mut library_links) = (Vec::new(), Vec::new());
1147
1148        while let Some(flag) = flags_iter.next() {
1149            if flag.starts_with("-l") || flag.starts_with("-L") {
1150                // Check if this flag has no space before the value as is
1151                // common with tools like pkg-config
1152                // e.g. -L/some/dir/local/lib or -licui18n
1153                let (flag, mut value) = flag.split_at(2);
1154                if value.is_empty() {
1155                    value = match flags_iter.next() {
1156                        Some(v) => v,
1157                        None => bail! {
1158                            "flag in rustc-flags has no value in {}: {}",
1159                            whence,
1160                            value
1161                        },
1162                    }
1163                }
1164
1165                match flag {
1166                    "-l" => library_links.push(value.to_string()),
1167                    "-L" => library_paths.push(PathBuf::from(value)),
1168
1169                    // This was already checked above
1170                    _ => unreachable!(),
1171                };
1172            } else {
1173                bail!(
1174                    "only `-l` and `-L` flags are allowed in {}: `{}`",
1175                    whence,
1176                    value
1177                )
1178            }
1179        }
1180        Ok((library_paths, library_links))
1181    }
1182
1183    /// Parses [`cargo::rustc-env`] instruction.
1184    ///
1185    /// [`cargo::rustc-env`]: https://doc.rust-lang.org/nightly/cargo/reference/build-scripts.html#rustc-env
1186    pub fn parse_rustc_env(value: &str, whence: &str) -> CargoResult<(String, String)> {
1187        match value.split_once('=') {
1188            Some((n, v)) => Ok((n.to_owned(), v.to_owned())),
1189            _ => bail!("Variable rustc-env has no value in {whence}: {value}"),
1190        }
1191    }
1192}
1193
1194/// Prepares the Rust script for the unstable feature [metabuild].
1195///
1196/// [metabuild]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#metabuild
1197fn prepare_metabuild(
1198    build_runner: &BuildRunner<'_, '_>,
1199    unit: &Unit,
1200    deps: &[String],
1201) -> CargoResult<()> {
1202    let mut output = Vec::new();
1203    let available_deps = build_runner.unit_deps(unit);
1204    // Filter out optional dependencies, and look up the actual lib name.
1205    let meta_deps: Vec<_> = deps
1206        .iter()
1207        .filter_map(|name| {
1208            available_deps
1209                .iter()
1210                .find(|d| d.unit.pkg.name().as_str() == name.as_str())
1211                .map(|d| d.unit.target.crate_name())
1212        })
1213        .collect();
1214    output.push("fn main() {\n".to_string());
1215    for dep in &meta_deps {
1216        output.push(format!("    {}::metabuild();\n", dep));
1217    }
1218    output.push("}\n".to_string());
1219    let output = output.join("");
1220    let path = unit
1221        .pkg
1222        .manifest()
1223        .metabuild_path(build_runner.bcx.ws.build_dir());
1224    paths::create_dir_all(path.parent().unwrap())?;
1225    paths::write_if_changed(path, &output)?;
1226    Ok(())
1227}
1228
1229impl BuildDeps {
1230    /// Creates a build script dependency information from a previous
1231    /// build script output path and the content.
1232    pub fn new(output_file: &Path, output: Option<&BuildOutput>) -> BuildDeps {
1233        BuildDeps {
1234            build_script_output: output_file.to_path_buf(),
1235            rerun_if_changed: output
1236                .map(|p| &p.rerun_if_changed)
1237                .cloned()
1238                .unwrap_or_default(),
1239            rerun_if_env_changed: output
1240                .map(|p| &p.rerun_if_env_changed)
1241                .cloned()
1242                .unwrap_or_default(),
1243        }
1244    }
1245}
1246
1247/// Computes several maps in [`BuildRunner`].
1248///
1249/// - [`build_scripts`]: A map that tracks which build scripts each package
1250///   depends on.
1251/// - [`build_explicit_deps`]: Dependency statements emitted by build scripts
1252///   from a previous run.
1253/// - [`build_script_outputs`]: Pre-populates this with any overridden build
1254///   scripts.
1255///
1256/// The important one here is [`build_scripts`], which for each `(package,
1257/// metadata)` stores a [`BuildScripts`] object which contains a list of
1258/// dependencies with build scripts that the unit should consider when linking.
1259/// For example this lists all dependencies' `-L` flags which need to be
1260/// propagated transitively.
1261///
1262/// The given set of units to this function is the initial set of
1263/// targets/profiles which are being built.
1264///
1265/// [`build_scripts`]: BuildRunner::build_scripts
1266/// [`build_explicit_deps`]: BuildRunner::build_explicit_deps
1267/// [`build_script_outputs`]: BuildRunner::build_script_outputs
1268pub fn build_map(build_runner: &mut BuildRunner<'_, '_>) -> CargoResult<()> {
1269    let mut ret = HashMap::new();
1270    for unit in &build_runner.bcx.roots {
1271        build(&mut ret, build_runner, unit)?;
1272    }
1273    build_runner
1274        .build_scripts
1275        .extend(ret.into_iter().map(|(k, v)| (k, Arc::new(v))));
1276    return Ok(());
1277
1278    // Recursive function to build up the map we're constructing. This function
1279    // memoizes all of its return values as it goes along.
1280    fn build<'a>(
1281        out: &'a mut HashMap<Unit, BuildScripts>,
1282        build_runner: &mut BuildRunner<'_, '_>,
1283        unit: &Unit,
1284    ) -> CargoResult<&'a BuildScripts> {
1285        // Do a quick pre-flight check to see if we've already calculated the
1286        // set of dependencies.
1287        if out.contains_key(unit) {
1288            return Ok(&out[unit]);
1289        }
1290
1291        // If there is a build script override, pre-fill the build output.
1292        if unit.mode.is_run_custom_build() {
1293            if let Some(links) = unit.pkg.manifest().links() {
1294                if let Some(output) = unit.links_overrides.get(links) {
1295                    let metadata = build_runner.get_run_build_script_metadata(unit);
1296                    build_runner.build_script_outputs.lock().unwrap().insert(
1297                        unit.pkg.package_id(),
1298                        metadata,
1299                        output.clone(),
1300                    );
1301                }
1302            }
1303        }
1304
1305        let mut ret = BuildScripts::default();
1306
1307        // If a package has a build script, add itself as something to inspect for linking.
1308        if !unit.target.is_custom_build() && unit.pkg.has_custom_build() {
1309            let script_metas = build_runner
1310                .find_build_script_metadatas(unit)
1311                .expect("has_custom_build should have RunCustomBuild");
1312            for script_meta in script_metas {
1313                add_to_link(&mut ret, unit.pkg.package_id(), script_meta);
1314            }
1315        }
1316
1317        if unit.mode.is_run_custom_build() {
1318            parse_previous_explicit_deps(build_runner, unit);
1319        }
1320
1321        // We want to invoke the compiler deterministically to be cache-friendly
1322        // to rustc invocation caching schemes, so be sure to generate the same
1323        // set of build script dependency orderings via sorting the targets that
1324        // come out of the `Context`.
1325        let mut dependencies: Vec<Unit> = build_runner
1326            .unit_deps(unit)
1327            .iter()
1328            .map(|d| d.unit.clone())
1329            .collect();
1330        dependencies.sort_by_key(|u| u.pkg.package_id());
1331
1332        for dep_unit in dependencies.iter() {
1333            let dep_scripts = build(out, build_runner, dep_unit)?;
1334
1335            if dep_unit.target.for_host() {
1336                ret.plugins.extend(dep_scripts.to_link.iter().cloned());
1337            } else if dep_unit.target.is_linkable() {
1338                for &(pkg, metadata) in dep_scripts.to_link.iter() {
1339                    add_to_link(&mut ret, pkg, metadata);
1340                }
1341            }
1342        }
1343
1344        match out.entry(unit.clone()) {
1345            Entry::Vacant(entry) => Ok(entry.insert(ret)),
1346            Entry::Occupied(_) => panic!("cyclic dependencies in `build_map`"),
1347        }
1348    }
1349
1350    // When adding an entry to 'to_link' we only actually push it on if the
1351    // script hasn't seen it yet (e.g., we don't push on duplicates).
1352    fn add_to_link(scripts: &mut BuildScripts, pkg: PackageId, metadata: UnitHash) {
1353        if scripts.seen_to_link.insert((pkg, metadata)) {
1354            scripts.to_link.push((pkg, metadata));
1355        }
1356    }
1357
1358    /// Load any dependency declarations from a previous build script run.
1359    fn parse_previous_explicit_deps(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) {
1360        let run_files = BuildScriptRunFiles::for_unit(build_runner, unit);
1361        let (prev_output, _) = prev_build_output(build_runner, unit);
1362        let deps = BuildDeps::new(&run_files.stdout, prev_output.as_ref());
1363        build_runner.build_explicit_deps.insert(unit.clone(), deps);
1364    }
1365}
1366
1367/// Returns the previous parsed `BuildOutput`, if any, from a previous
1368/// execution.
1369///
1370/// Also returns the directory containing the output, typically used later in
1371/// processing.
1372fn prev_build_output(
1373    build_runner: &mut BuildRunner<'_, '_>,
1374    unit: &Unit,
1375) -> (Option<BuildOutput>, PathBuf) {
1376    let script_out_dir = if build_runner.bcx.gctx.cli_unstable().build_dir_new_layout {
1377        build_runner.files().out_dir_new_layout(unit)
1378    } else {
1379        build_runner.files().build_script_out_dir(unit)
1380    };
1381    let run_files = BuildScriptRunFiles::for_unit(build_runner, unit);
1382
1383    let prev_script_out_dir = paths::read_bytes(&run_files.root_output)
1384        .and_then(|bytes| paths::bytes2path(&bytes))
1385        .unwrap_or_else(|_| script_out_dir.clone());
1386
1387    (
1388        BuildOutput::parse_file(
1389            &run_files.stdout,
1390            unit.pkg.library().map(|t| t.crate_name()),
1391            &unit.pkg.to_string(),
1392            &prev_script_out_dir,
1393            &script_out_dir,
1394            build_runner.bcx.gctx.nightly_features_allowed,
1395            unit.pkg.targets(),
1396            &unit.pkg.rust_version().cloned(),
1397        )
1398        .ok(),
1399        prev_script_out_dir,
1400    )
1401}
1402
1403impl BuildScriptOutputs {
1404    /// Inserts a new entry into the map.
1405    fn insert(&mut self, pkg_id: PackageId, metadata: UnitHash, parsed_output: BuildOutput) {
1406        match self.outputs.entry(metadata) {
1407            Entry::Vacant(entry) => {
1408                entry.insert(parsed_output);
1409            }
1410            Entry::Occupied(entry) => panic!(
1411                "build script output collision for {}/{}\n\
1412                old={:?}\nnew={:?}",
1413                pkg_id,
1414                metadata,
1415                entry.get(),
1416                parsed_output
1417            ),
1418        }
1419    }
1420
1421    /// Returns `true` if the given key already exists.
1422    fn contains_key(&self, metadata: UnitHash) -> bool {
1423        self.outputs.contains_key(&metadata)
1424    }
1425
1426    /// Gets the build output for the given key.
1427    pub fn get(&self, meta: UnitHash) -> Option<&BuildOutput> {
1428        self.outputs.get(&meta)
1429    }
1430
1431    /// Returns an iterator over all entries.
1432    pub fn iter(&self) -> impl Iterator<Item = (&UnitHash, &BuildOutput)> {
1433        self.outputs.iter()
1434    }
1435}
1436
1437/// Files with information about a running build script.
1438struct BuildScriptRunFiles {
1439    /// The directory containing files related to running a build script.
1440    root: PathBuf,
1441    /// The stdout produced by the build script
1442    stdout: PathBuf,
1443    /// The stderr produced by the build script
1444    stderr: PathBuf,
1445    /// A file that contains the path to the `out` dir of the build script.
1446    /// This is used for detect if the directory was moved since the previous run.
1447    root_output: PathBuf,
1448}
1449
1450impl BuildScriptRunFiles {
1451    pub fn for_unit(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> Self {
1452        let root = build_runner.files().build_script_run_dir(unit);
1453        let stdout = if build_runner.bcx.gctx.cli_unstable().build_dir_new_layout {
1454            root.join("stdout")
1455        } else {
1456            root.join("output")
1457        };
1458        let stderr = root.join("stderr");
1459        let root_output = root.join("root-output");
1460        Self {
1461            root,
1462            stdout,
1463            stderr,
1464            root_output,
1465        }
1466    }
1467}