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