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