Skip to main content

cargo/core/compiler/
custom_build.rs

1use super::job::{Freshness, Job, Work};
2use super::{fingerprint, Context, Unit};
3use crate::core::compiler::context::Metadata;
4use crate::core::compiler::job_queue::JobState;
5use crate::core::{profiles::ProfileRoot, PackageId};
6use crate::util::errors::{CargoResult, CargoResultExt};
7use crate::util::machine_message::{self, Message};
8use crate::util::{self, internal, paths, profile};
9use cargo_platform::Cfg;
10use std::collections::hash_map::{Entry, HashMap};
11use std::collections::{BTreeSet, HashSet};
12use std::path::{Path, PathBuf};
13use std::str;
14use std::sync::{Arc, Mutex};
15
16const CARGO_WARNING: &str = "cargo:warning=";
17
18/// Contains the parsed output of a custom build script.
19#[derive(Clone, Debug, Hash, Default)]
20pub struct BuildOutput {
21    /// Paths to pass to rustc with the `-L` flag.
22    pub library_paths: Vec<PathBuf>,
23    /// Names and link kinds of libraries, suitable for the `-l` flag.
24    pub library_links: Vec<String>,
25    /// Linker arguments suitable to be passed to `-C link-arg=<args>`
26    pub linker_args: Vec<String>,
27    /// Various `--cfg` flags to pass to the compiler.
28    pub cfgs: Vec<String>,
29    /// Additional environment variables to run the compiler with.
30    pub env: Vec<(String, String)>,
31    /// Metadata to pass to the immediate dependencies.
32    pub metadata: Vec<(String, String)>,
33    /// Paths to trigger a rerun of this build script.
34    /// May be absolute or relative paths (relative to package root).
35    pub rerun_if_changed: Vec<PathBuf>,
36    /// Environment variables which, when changed, will cause a rebuild.
37    pub rerun_if_env_changed: Vec<String>,
38    /// Warnings generated by this build.
39    pub warnings: Vec<String>,
40}
41
42/// Map of packages to build script output.
43///
44/// This initially starts out as empty. Overridden build scripts get
45/// inserted during `build_map`. The rest of the entries are added
46/// immediately after each build script runs.
47///
48/// The `Metadata` is the unique metadata hash for the RunCustomBuild Unit of
49/// the package. It needs a unique key, since the build script can be run
50/// multiple times with different profiles or features. We can't embed a
51/// `Unit` because this structure needs to be shareable between threads.
52#[derive(Default)]
53pub struct BuildScriptOutputs {
54    outputs: HashMap<(PackageId, Metadata), BuildOutput>,
55}
56
57/// Linking information for a `Unit`.
58///
59/// See `build_map` for more details.
60#[derive(Default)]
61pub struct BuildScripts {
62    /// List of build script outputs this Unit needs to include for linking. Each
63    /// element is an index into `BuildScriptOutputs`.
64    ///
65    /// Cargo will use this `to_link` vector to add `-L` flags to compiles as we
66    /// propagate them upwards towards the final build. Note, however, that we
67    /// need to preserve the ordering of `to_link` to be topologically sorted.
68    /// This will ensure that build scripts which print their paths properly will
69    /// correctly pick up the files they generated (if there are duplicates
70    /// elsewhere).
71    ///
72    /// To preserve this ordering, the (id, metadata) is stored in two places, once
73    /// in the `Vec` and once in `seen_to_link` for a fast lookup. We maintain
74    /// this as we're building interactively below to ensure that the memory
75    /// usage here doesn't blow up too much.
76    ///
77    /// For more information, see #2354.
78    pub to_link: Vec<(PackageId, Metadata)>,
79    /// This is only used while constructing `to_link` to avoid duplicates.
80    seen_to_link: HashSet<(PackageId, Metadata)>,
81    /// Host-only dependencies that have build scripts. Each element is an
82    /// index into `BuildScriptOutputs`.
83    ///
84    /// This is the set of transitive dependencies that are host-only
85    /// (proc-macro, plugin, build-dependency) that contain a build script.
86    /// Any `BuildOutput::library_paths` path relative to `target` will be
87    /// added to LD_LIBRARY_PATH so that the compiler can find any dynamic
88    /// libraries a build script may have generated.
89    pub plugins: BTreeSet<(PackageId, Metadata)>,
90}
91
92/// Dependency information as declared by a build script.
93#[derive(Debug)]
94pub struct BuildDeps {
95    /// Absolute path to the file in the target directory that stores the
96    /// output of the build script.
97    pub build_script_output: PathBuf,
98    /// Files that trigger a rebuild if they change.
99    pub rerun_if_changed: Vec<PathBuf>,
100    /// Environment variables that trigger a rebuild if they change.
101    pub rerun_if_env_changed: Vec<String>,
102}
103
104/// Prepares a `Work` that executes the target as a custom build script.
105pub fn prepare<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoResult<Job> {
106    let _p = profile::start(format!(
107        "build script prepare: {}/{}",
108        unit.pkg,
109        unit.target.name()
110    ));
111
112    let metadata = cx.get_run_build_script_metadata(unit);
113    if cx
114        .build_script_outputs
115        .lock()
116        .unwrap()
117        .contains_key(unit.pkg.package_id(), metadata)
118    {
119        // The output is already set, thus the build script is overridden.
120        fingerprint::prepare_target(cx, unit, false)
121    } else {
122        build_work(cx, unit)
123    }
124}
125
126fn emit_build_output(
127    state: &JobState<'_>,
128    output: &BuildOutput,
129    out_dir: &Path,
130    package_id: PackageId,
131) {
132    let library_paths = output
133        .library_paths
134        .iter()
135        .map(|l| l.display().to_string())
136        .collect::<Vec<_>>();
137
138    let msg = machine_message::BuildScript {
139        package_id,
140        linked_libs: &output.library_links,
141        linked_paths: &library_paths,
142        cfgs: &output.cfgs,
143        env: &output.env,
144        out_dir,
145    }
146    .to_json_string();
147    state.stdout(msg);
148}
149
150fn build_work<'a, 'cfg>(cx: &mut Context<'a, 'cfg>, unit: &Unit<'a>) -> CargoResult<Job> {
151    assert!(unit.mode.is_run_custom_build());
152    let bcx = &cx.bcx;
153    let dependencies = cx.unit_deps(unit);
154    let build_script_unit = dependencies
155        .iter()
156        .find(|d| !d.unit.mode.is_run_custom_build() && d.unit.target.is_custom_build())
157        .map(|d| &d.unit)
158        .expect("running a script not depending on an actual script");
159    let script_dir = cx.files().build_script_dir(build_script_unit);
160    let script_out_dir = cx.files().build_script_out_dir(unit);
161    let script_run_dir = cx.files().build_script_run_dir(unit);
162    let build_plan = bcx.build_config.build_plan;
163    let invocation_name = unit.buildkey();
164
165    if let Some(deps) = unit.pkg.manifest().metabuild() {
166        prepare_metabuild(cx, build_script_unit, deps)?;
167    }
168
169    // Building the command to execute
170    let to_exec = script_dir.join(unit.target.name());
171
172    // Start preparing the process to execute, starting out with some
173    // environment variables. Note that the profile-related environment
174    // variables are not set with this the build script's profile but rather the
175    // package's library profile.
176    // NOTE: if you add any profile flags, be sure to update
177    // `Profiles::get_profile_run_custom_build` so that those flags get
178    // carried over.
179    let to_exec = to_exec.into_os_string();
180    let mut cmd = cx.compilation.host_process(to_exec, unit.pkg)?;
181    let debug = unit.profile.debuginfo.unwrap_or(0) != 0;
182    cmd.env("OUT_DIR", &script_out_dir)
183        .env("CARGO_MANIFEST_DIR", unit.pkg.root())
184        .env("NUM_JOBS", &bcx.jobs().to_string())
185        .env("TARGET", bcx.target_data.short_name(&unit.kind))
186        .env("DEBUG", debug.to_string())
187        .env("OPT_LEVEL", &unit.profile.opt_level.to_string())
188        .env(
189            "PROFILE",
190            match unit.profile.root {
191                ProfileRoot::Release => "release",
192                ProfileRoot::Debug => "debug",
193            },
194        )
195        .env("HOST", &bcx.host_triple())
196        .env("RUSTC", &bcx.rustc().path)
197        .env("RUSTDOC", &*bcx.config.rustdoc()?)
198        .inherit_jobserver(&cx.jobserver);
199
200    if let Some(linker) = &bcx.target_data.target_config(unit.kind).linker {
201        cmd.env(
202            "RUSTC_LINKER",
203            linker.val.clone().resolve_program(bcx.config),
204        );
205    }
206
207    if let Some(links) = unit.pkg.manifest().links() {
208        cmd.env("CARGO_MANIFEST_LINKS", links);
209    }
210
211    // Be sure to pass along all enabled features for this package, this is the
212    // last piece of statically known information that we have.
213    for feat in &unit.features {
214        cmd.env(&format!("CARGO_FEATURE_{}", super::envify(feat)), "1");
215    }
216
217    let mut cfg_map = HashMap::new();
218    for cfg in bcx.target_data.cfg(unit.kind) {
219        match *cfg {
220            Cfg::Name(ref n) => {
221                cfg_map.insert(n.clone(), None);
222            }
223            Cfg::KeyPair(ref k, ref v) => {
224                if let Some(ref mut values) =
225                    *cfg_map.entry(k.clone()).or_insert_with(|| Some(Vec::new()))
226                {
227                    values.push(v.clone())
228                }
229            }
230        }
231    }
232    for (k, v) in cfg_map {
233        if k == "debug_assertions" {
234            // This cfg is always true and misleading, so avoid setting it.
235            // That is because Cargo queries rustc without any profile settings.
236            continue;
237        }
238        let k = format!("CARGO_CFG_{}", super::envify(&k));
239        match v {
240            Some(list) => {
241                cmd.env(&k, list.join(","));
242            }
243            None => {
244                cmd.env(&k, "");
245            }
246        }
247    }
248
249    // Gather the set of native dependencies that this package has along with
250    // some other variables to close over.
251    //
252    // This information will be used at build-time later on to figure out which
253    // sorts of variables need to be discovered at that time.
254    let lib_deps = dependencies
255        .iter()
256        .filter_map(|dep| {
257            if dep.unit.mode.is_run_custom_build() {
258                let dep_metadata = cx.get_run_build_script_metadata(&dep.unit);
259                Some((
260                    dep.unit.pkg.manifest().links().unwrap().to_string(),
261                    dep.unit.pkg.package_id(),
262                    dep_metadata,
263                ))
264            } else {
265                None
266            }
267        })
268        .collect::<Vec<_>>();
269    let pkg_name = unit.pkg.to_string();
270    let build_script_outputs = Arc::clone(&cx.build_script_outputs);
271    let id = unit.pkg.package_id();
272    let output_file = script_run_dir.join("output");
273    let err_file = script_run_dir.join("stderr");
274    let root_output_file = script_run_dir.join("root-output");
275    let host_target_root = cx.files().host_root().to_path_buf();
276    let all = (
277        id,
278        pkg_name.clone(),
279        Arc::clone(&build_script_outputs),
280        output_file.clone(),
281        script_out_dir.clone(),
282    );
283    let build_scripts = cx.build_scripts.get(unit).cloned();
284    let json_messages = bcx.build_config.emit_json();
285    let extra_verbose = bcx.config.extra_verbose();
286    let (prev_output, prev_script_out_dir) = prev_build_output(cx, unit);
287    let metadata_hash = cx.get_run_build_script_metadata(unit);
288
289    paths::create_dir_all(&script_dir)?;
290    paths::create_dir_all(&script_out_dir)?;
291
292    // Prepare the unit of "dirty work" which will actually run the custom build
293    // command.
294    //
295    // Note that this has to do some extra work just before running the command
296    // to determine extra environment variables and such.
297    let dirty = Work::new(move |state| {
298        // Make sure that OUT_DIR exists.
299        //
300        // If we have an old build directory, then just move it into place,
301        // otherwise create it!
302        paths::create_dir_all(&script_out_dir)
303            .chain_err(|| "failed to create script output directory for build command")?;
304
305        // For all our native lib dependencies, pick up their metadata to pass
306        // along to this custom build command. We're also careful to augment our
307        // dynamic library search path in case the build script depended on any
308        // native dynamic libraries.
309        if !build_plan {
310            let build_script_outputs = build_script_outputs.lock().unwrap();
311            for (name, dep_id, dep_metadata) in lib_deps {
312                let script_output =
313                    build_script_outputs
314                        .get(dep_id, dep_metadata)
315                        .ok_or_else(|| {
316                            internal(format!(
317                                "failed to locate build state for env vars: {}/{}",
318                                dep_id, dep_metadata
319                            ))
320                        })?;
321                let data = &script_output.metadata;
322                for &(ref key, ref value) in data.iter() {
323                    cmd.env(
324                        &format!("DEP_{}_{}", super::envify(&name), super::envify(key)),
325                        value,
326                    );
327                }
328            }
329            if let Some(build_scripts) = build_scripts {
330                super::add_plugin_deps(
331                    &mut cmd,
332                    &build_script_outputs,
333                    &build_scripts,
334                    &host_target_root,
335                )?;
336            }
337        }
338
339        if build_plan {
340            state.build_plan(invocation_name, cmd.clone(), Arc::new(Vec::new()));
341            return Ok(());
342        }
343
344        // And now finally, run the build command itself!
345        state.running(&cmd);
346        let timestamp = paths::set_invocation_time(&script_run_dir)?;
347        let prefix = format!("[{} {}] ", id.name(), id.version());
348        let mut warnings_in_case_of_panic = Vec::new();
349        let output = cmd
350            .exec_with_streaming(
351                &mut |stdout| {
352                    if stdout.starts_with(CARGO_WARNING) {
353                        warnings_in_case_of_panic.push(stdout[CARGO_WARNING.len()..].to_owned());
354                    }
355                    if extra_verbose {
356                        state.stdout(format!("{}{}", prefix, stdout));
357                    }
358                    Ok(())
359                },
360                &mut |stderr| {
361                    if extra_verbose {
362                        state.stderr(format!("{}{}", prefix, stderr));
363                    }
364                    Ok(())
365                },
366                true,
367            )
368            .chain_err(|| format!("failed to run custom build command for `{}`", pkg_name));
369
370        if let Err(error) = output {
371            insert_warnings_in_build_outputs(
372                build_script_outputs,
373                id,
374                metadata_hash,
375                warnings_in_case_of_panic,
376            );
377            return Err(error);
378        }
379
380        let output = output.unwrap();
381
382        // After the build command has finished running, we need to be sure to
383        // remember all of its output so we can later discover precisely what it
384        // was, even if we don't run the build command again (due to freshness).
385        //
386        // This is also the location where we provide feedback into the build
387        // state informing what variables were discovered via our script as
388        // well.
389        paths::write(&output_file, &output.stdout)?;
390        log::debug!(
391            "rewinding custom script output mtime {:?} to {}",
392            output_file,
393            timestamp
394        );
395        filetime::set_file_times(output_file, timestamp, timestamp)?;
396        paths::write(&err_file, &output.stderr)?;
397        paths::write(&root_output_file, util::path2bytes(&script_out_dir)?)?;
398        let parsed_output =
399            BuildOutput::parse(&output.stdout, &pkg_name, &script_out_dir, &script_out_dir)?;
400
401        if json_messages {
402            emit_build_output(state, &parsed_output, script_out_dir.as_path(), id);
403        }
404        build_script_outputs
405            .lock()
406            .unwrap()
407            .insert(id, metadata_hash, parsed_output);
408        Ok(())
409    });
410
411    // Now that we've prepared our work-to-do, we need to prepare the fresh work
412    // itself to run when we actually end up just discarding what we calculated
413    // above.
414    let fresh = Work::new(move |state| {
415        let (id, pkg_name, build_script_outputs, output_file, script_out_dir) = all;
416        let output = match prev_output {
417            Some(output) => output,
418            None => BuildOutput::parse_file(
419                &output_file,
420                &pkg_name,
421                &prev_script_out_dir,
422                &script_out_dir,
423            )?,
424        };
425
426        if json_messages {
427            emit_build_output(state, &output, script_out_dir.as_path(), id);
428        }
429
430        build_script_outputs
431            .lock()
432            .unwrap()
433            .insert(id, metadata_hash, output);
434        Ok(())
435    });
436
437    let mut job = if cx.bcx.build_config.build_plan {
438        Job::new(Work::noop(), Freshness::Dirty)
439    } else {
440        fingerprint::prepare_target(cx, unit, false)?
441    };
442    if job.freshness() == Freshness::Dirty {
443        job.before(dirty);
444    } else {
445        job.before(fresh);
446    }
447    Ok(job)
448}
449
450fn insert_warnings_in_build_outputs(
451    build_script_outputs: Arc<Mutex<BuildScriptOutputs>>,
452    id: PackageId,
453    metadata_hash: Metadata,
454    warnings: Vec<String>,
455) {
456    let build_output_with_only_warnings = BuildOutput {
457        warnings,
458        ..BuildOutput::default()
459    };
460    build_script_outputs
461        .lock()
462        .unwrap()
463        .insert(id, metadata_hash, build_output_with_only_warnings);
464}
465
466impl BuildOutput {
467    pub fn parse_file(
468        path: &Path,
469        pkg_name: &str,
470        script_out_dir_when_generated: &Path,
471        script_out_dir: &Path,
472    ) -> CargoResult<BuildOutput> {
473        let contents = paths::read_bytes(path)?;
474        BuildOutput::parse(
475            &contents,
476            pkg_name,
477            script_out_dir_when_generated,
478            script_out_dir,
479        )
480    }
481
482    // Parses the output of a script.
483    // The `pkg_name` is used for error messages.
484    pub fn parse(
485        input: &[u8],
486        pkg_name: &str,
487        script_out_dir_when_generated: &Path,
488        script_out_dir: &Path,
489    ) -> CargoResult<BuildOutput> {
490        let mut library_paths = Vec::new();
491        let mut library_links = Vec::new();
492        let mut linker_args = Vec::new();
493        let mut cfgs = Vec::new();
494        let mut env = Vec::new();
495        let mut metadata = Vec::new();
496        let mut rerun_if_changed = Vec::new();
497        let mut rerun_if_env_changed = Vec::new();
498        let mut warnings = Vec::new();
499        let whence = format!("build script of `{}`", pkg_name);
500
501        for line in input.split(|b| *b == b'\n') {
502            let line = match str::from_utf8(line) {
503                Ok(line) => line.trim(),
504                Err(..) => continue,
505            };
506            let mut iter = line.splitn(2, ':');
507            if iter.next() != Some("cargo") {
508                // skip this line since it doesn't start with "cargo:"
509                continue;
510            }
511            let data = match iter.next() {
512                Some(val) => val,
513                None => continue,
514            };
515
516            // getting the `key=value` part of the line
517            let mut iter = data.splitn(2, '=');
518            let key = iter.next();
519            let value = iter.next();
520            let (key, value) = match (key, value) {
521                (Some(a), Some(b)) => (a, b.trim_end()),
522                // Line started with `cargo:` but didn't match `key=value`.
523                _ => anyhow::bail!("Wrong output in {}: `{}`", whence, line),
524            };
525
526            // This will rewrite paths if the target directory has been moved.
527            let value = value.replace(
528                script_out_dir_when_generated.to_str().unwrap(),
529                script_out_dir.to_str().unwrap(),
530            );
531
532            // Keep in sync with TargetConfig::new.
533            match key {
534                "rustc-flags" => {
535                    let (paths, links) = BuildOutput::parse_rustc_flags(&value, &whence)?;
536                    library_links.extend(links.into_iter());
537                    library_paths.extend(paths.into_iter());
538                }
539                "rustc-link-lib" => library_links.push(value.to_string()),
540                "rustc-link-search" => library_paths.push(PathBuf::from(value)),
541                "rustc-cdylib-link-arg" => linker_args.push(value.to_string()),
542                "rustc-cfg" => cfgs.push(value.to_string()),
543                "rustc-env" => env.push(BuildOutput::parse_rustc_env(&value, &whence)?),
544                "warning" => warnings.push(value.to_string()),
545                "rerun-if-changed" => rerun_if_changed.push(PathBuf::from(value)),
546                "rerun-if-env-changed" => rerun_if_env_changed.push(value.to_string()),
547                _ => metadata.push((key.to_string(), value.to_string())),
548            }
549        }
550
551        Ok(BuildOutput {
552            library_paths,
553            library_links,
554            linker_args,
555            cfgs,
556            env,
557            metadata,
558            rerun_if_changed,
559            rerun_if_env_changed,
560            warnings,
561        })
562    }
563
564    pub fn parse_rustc_flags(
565        value: &str,
566        whence: &str,
567    ) -> CargoResult<(Vec<PathBuf>, Vec<String>)> {
568        let value = value.trim();
569        let mut flags_iter = value
570            .split(|c: char| c.is_whitespace())
571            .filter(|w| w.chars().any(|c| !c.is_whitespace()));
572        let (mut library_paths, mut library_links) = (Vec::new(), Vec::new());
573
574        while let Some(flag) = flags_iter.next() {
575            if flag.starts_with("-l") || flag.starts_with("-L") {
576                // Check if this flag has no space before the value as is
577                // common with tools like pkg-config
578                // e.g. -L/some/dir/local/lib or -licui18n
579                let (flag, mut value) = flag.split_at(2);
580                if value.is_empty() {
581                    value = match flags_iter.next() {
582                        Some(v) => v,
583                        None => anyhow::bail! {
584                            "Flag in rustc-flags has no value in {}: {}",
585                            whence,
586                            value
587                        },
588                    }
589                }
590
591                match flag {
592                    "-l" => library_links.push(value.to_string()),
593                    "-L" => library_paths.push(PathBuf::from(value)),
594
595                    // This was already checked above
596                    _ => unreachable!(),
597                };
598            } else {
599                anyhow::bail!(
600                    "Only `-l` and `-L` flags are allowed in {}: `{}`",
601                    whence,
602                    value
603                )
604            }
605        }
606        Ok((library_paths, library_links))
607    }
608
609    pub fn parse_rustc_env(value: &str, whence: &str) -> CargoResult<(String, String)> {
610        let mut iter = value.splitn(2, '=');
611        let name = iter.next();
612        let val = iter.next();
613        match (name, val) {
614            (Some(n), Some(v)) => Ok((n.to_owned(), v.to_owned())),
615            _ => anyhow::bail!("Variable rustc-env has no value in {}: {}", whence, value),
616        }
617    }
618}
619
620fn prepare_metabuild<'a, 'cfg>(
621    cx: &Context<'a, 'cfg>,
622    unit: &Unit<'a>,
623    deps: &[String],
624) -> CargoResult<()> {
625    let mut output = Vec::new();
626    let available_deps = cx.unit_deps(unit);
627    // Filter out optional dependencies, and look up the actual lib name.
628    let meta_deps: Vec<_> = deps
629        .iter()
630        .filter_map(|name| {
631            available_deps
632                .iter()
633                .find(|d| d.unit.pkg.name().as_str() == name.as_str())
634                .map(|d| d.unit.target.crate_name())
635        })
636        .collect();
637    for dep in &meta_deps {
638        output.push(format!("use {};\n", dep));
639    }
640    output.push("fn main() {\n".to_string());
641    for dep in &meta_deps {
642        output.push(format!("    {}::metabuild();\n", dep));
643    }
644    output.push("}\n".to_string());
645    let output = output.join("");
646    let path = unit.pkg.manifest().metabuild_path(cx.bcx.ws.target_dir());
647    paths::create_dir_all(path.parent().unwrap())?;
648    paths::write_if_changed(path, &output)?;
649    Ok(())
650}
651
652impl BuildDeps {
653    pub fn new(output_file: &Path, output: Option<&BuildOutput>) -> BuildDeps {
654        BuildDeps {
655            build_script_output: output_file.to_path_buf(),
656            rerun_if_changed: output
657                .map(|p| &p.rerun_if_changed)
658                .cloned()
659                .unwrap_or_default(),
660            rerun_if_env_changed: output
661                .map(|p| &p.rerun_if_env_changed)
662                .cloned()
663                .unwrap_or_default(),
664        }
665    }
666}
667
668/// Computes several maps in `Context`:
669/// - `build_scripts`: A map that tracks which build scripts each package
670///   depends on.
671/// - `build_explicit_deps`: Dependency statements emitted by build scripts
672///   from a previous run.
673/// - `build_script_outputs`: Pre-populates this with any overridden build
674///   scripts.
675///
676/// The important one here is `build_scripts`, which for each `(package,
677/// metadata)` stores a `BuildScripts` object which contains a list of
678/// dependencies with build scripts that the unit should consider when
679/// linking. For example this lists all dependencies' `-L` flags which need to
680/// be propagated transitively.
681///
682/// The given set of units to this function is the initial set of
683/// targets/profiles which are being built.
684pub fn build_map<'b, 'cfg>(cx: &mut Context<'b, 'cfg>, units: &[Unit<'b>]) -> CargoResult<()> {
685    let mut ret = HashMap::new();
686    for unit in units {
687        build(&mut ret, cx, unit)?;
688    }
689    cx.build_scripts
690        .extend(ret.into_iter().map(|(k, v)| (k, Arc::new(v))));
691    return Ok(());
692
693    // Recursive function to build up the map we're constructing. This function
694    // memoizes all of its return values as it goes along.
695    fn build<'a, 'b, 'cfg>(
696        out: &'a mut HashMap<Unit<'b>, BuildScripts>,
697        cx: &mut Context<'b, 'cfg>,
698        unit: &Unit<'b>,
699    ) -> CargoResult<&'a BuildScripts> {
700        // Do a quick pre-flight check to see if we've already calculated the
701        // set of dependencies.
702        if out.contains_key(unit) {
703            return Ok(&out[unit]);
704        }
705
706        // If there is a build script override, pre-fill the build output.
707        if unit.mode.is_run_custom_build() {
708            if let Some(links) = unit.pkg.manifest().links() {
709                if let Some(output) = cx.bcx.script_override(links, unit.kind) {
710                    let metadata = cx.get_run_build_script_metadata(unit);
711                    cx.build_script_outputs.lock().unwrap().insert(
712                        unit.pkg.package_id(),
713                        metadata,
714                        output.clone(),
715                    );
716                }
717            }
718        }
719
720        let mut ret = BuildScripts::default();
721
722        // If a package has a build script, add itself as something to inspect for linking.
723        if !unit.target.is_custom_build() && unit.pkg.has_custom_build() {
724            let script_meta = cx
725                .find_build_script_metadata(*unit)
726                .expect("has_custom_build should have RunCustomBuild");
727            add_to_link(&mut ret, unit.pkg.package_id(), script_meta);
728        }
729
730        // Load any dependency declarations from a previous run.
731        if unit.mode.is_run_custom_build() {
732            parse_previous_explicit_deps(cx, unit)?;
733        }
734
735        // We want to invoke the compiler deterministically to be cache-friendly
736        // to rustc invocation caching schemes, so be sure to generate the same
737        // set of build script dependency orderings via sorting the targets that
738        // come out of the `Context`.
739        let mut dependencies: Vec<Unit<'_>> = cx.unit_deps(unit).iter().map(|d| d.unit).collect();
740        dependencies.sort_by_key(|u| u.pkg.package_id());
741
742        for dep_unit in dependencies.iter() {
743            let dep_scripts = build(out, cx, dep_unit)?;
744
745            if dep_unit.target.for_host() {
746                ret.plugins.extend(dep_scripts.to_link.iter().cloned());
747            } else if dep_unit.target.linkable() {
748                for &(pkg, metadata) in dep_scripts.to_link.iter() {
749                    add_to_link(&mut ret, pkg, metadata);
750                }
751            }
752        }
753
754        match out.entry(*unit) {
755            Entry::Vacant(entry) => Ok(entry.insert(ret)),
756            Entry::Occupied(_) => panic!("cyclic dependencies in `build_map`"),
757        }
758    }
759
760    // When adding an entry to 'to_link' we only actually push it on if the
761    // script hasn't seen it yet (e.g., we don't push on duplicates).
762    fn add_to_link(scripts: &mut BuildScripts, pkg: PackageId, metadata: Metadata) {
763        if scripts.seen_to_link.insert((pkg, metadata)) {
764            scripts.to_link.push((pkg, metadata));
765        }
766    }
767
768    fn parse_previous_explicit_deps<'a, 'cfg>(
769        cx: &mut Context<'a, 'cfg>,
770        unit: &Unit<'a>,
771    ) -> CargoResult<()> {
772        let script_run_dir = cx.files().build_script_run_dir(unit);
773        let output_file = script_run_dir.join("output");
774        let (prev_output, _) = prev_build_output(cx, unit);
775        let deps = BuildDeps::new(&output_file, prev_output.as_ref());
776        cx.build_explicit_deps.insert(*unit, deps);
777        Ok(())
778    }
779}
780
781/// Returns the previous parsed `BuildOutput`, if any, from a previous
782/// execution.
783///
784/// Also returns the directory containing the output, typically used later in
785/// processing.
786fn prev_build_output<'a, 'cfg>(
787    cx: &mut Context<'a, 'cfg>,
788    unit: &Unit<'a>,
789) -> (Option<BuildOutput>, PathBuf) {
790    let script_out_dir = cx.files().build_script_out_dir(unit);
791    let script_run_dir = cx.files().build_script_run_dir(unit);
792    let root_output_file = script_run_dir.join("root-output");
793    let output_file = script_run_dir.join("output");
794
795    let prev_script_out_dir = paths::read_bytes(&root_output_file)
796        .and_then(|bytes| util::bytes2path(&bytes))
797        .unwrap_or_else(|_| script_out_dir.clone());
798
799    (
800        BuildOutput::parse_file(
801            &output_file,
802            &unit.pkg.to_string(),
803            &prev_script_out_dir,
804            &script_out_dir,
805        )
806        .ok(),
807        prev_script_out_dir,
808    )
809}
810
811impl BuildScriptOutputs {
812    /// Inserts a new entry into the map.
813    fn insert(&mut self, pkg_id: PackageId, metadata: Metadata, parsed_output: BuildOutput) {
814        match self.outputs.entry((pkg_id, metadata)) {
815            Entry::Vacant(entry) => {
816                entry.insert(parsed_output);
817            }
818            Entry::Occupied(entry) => panic!(
819                "build script output collision for {}/{}\n\
820                old={:?}\nnew={:?}",
821                pkg_id,
822                metadata,
823                entry.get(),
824                parsed_output
825            ),
826        }
827    }
828
829    /// Returns `true` if the given key already exists.
830    fn contains_key(&self, pkg_id: PackageId, metadata: Metadata) -> bool {
831        self.outputs.contains_key(&(pkg_id, metadata))
832    }
833
834    /// Gets the build output for the given key.
835    pub fn get(&self, pkg_id: PackageId, meta: Metadata) -> Option<&BuildOutput> {
836        self.outputs.get(&(pkg_id, meta))
837    }
838
839    /// Returns an iterator over all entries.
840    pub fn iter(&self) -> impl Iterator<Item = (PackageId, &BuildOutput)> {
841        self.outputs.iter().map(|(key, value)| (key.0, value))
842    }
843}