Skip to main content

cargo/core/compiler/
compilation.rs

1//! Type definitions for the result of a compilation.
2
3use std::collections::{BTreeSet, HashMap};
4use std::ffi::{OsStr, OsString};
5use std::path::Path;
6use std::path::PathBuf;
7
8use cargo_platform::CfgExpr;
9use cargo_util::{ProcessBuilder, paths};
10
11use crate::core::Package;
12use crate::core::compiler::BuildContext;
13use crate::core::compiler::CompileTarget;
14use crate::core::compiler::RustdocFingerprint;
15use crate::core::compiler::apply_env_config;
16use crate::core::compiler::{CompileKind, Unit, UnitHash};
17use crate::util::{CargoResult, GlobalContext};
18
19/// Represents the kind of process we are creating.
20#[derive(Debug)]
21enum ToolKind {
22    /// See [`Compilation::rustc_process`].
23    Rustc,
24    /// See [`Compilation::rustdoc_process`].
25    Rustdoc,
26    /// See [`Compilation::host_process`].
27    HostProcess,
28    /// See [`Compilation::target_process`].
29    TargetProcess,
30}
31
32impl ToolKind {
33    fn is_rustc_tool(&self) -> bool {
34        matches!(self, ToolKind::Rustc | ToolKind::Rustdoc)
35    }
36}
37
38/// Structure with enough information to run `rustdoc --test`.
39pub struct Doctest {
40    /// What's being doctested
41    pub unit: Unit,
42    /// Arguments needed to pass to rustdoc to run this test.
43    pub args: Vec<OsString>,
44    /// Whether or not -Zunstable-options is needed.
45    pub unstable_opts: bool,
46    /// The -Clinker value to use.
47    pub linker: Option<PathBuf>,
48    /// The script metadata, if this unit's package has a build script.
49    ///
50    /// This is used for indexing [`Compilation::extra_env`].
51    pub script_metas: Option<Vec<UnitHash>>,
52
53    /// Environment variables to set in the rustdoc process.
54    pub env: HashMap<String, OsString>,
55}
56
57/// Information about the output of a unit.
58pub struct UnitOutput {
59    /// The unit that generated this output.
60    pub unit: Unit,
61    /// Path to the unit's primary output (an executable or cdylib).
62    pub path: PathBuf,
63    /// The script metadata, if this unit's package has a build script.
64    ///
65    /// This is used for indexing [`Compilation::extra_env`].
66    pub script_metas: Option<Vec<UnitHash>>,
67
68    /// Environment variables to set in the unit's process.
69    pub env: HashMap<String, OsString>,
70}
71
72/// A structure returning the result of a compilation.
73pub struct Compilation<'gctx> {
74    /// An array of all tests created during this compilation.
75    pub tests: Vec<UnitOutput>,
76
77    /// An array of all binaries created.
78    pub binaries: Vec<UnitOutput>,
79
80    /// An array of all cdylibs created.
81    pub cdylibs: Vec<UnitOutput>,
82
83    /// The crate names of the root units specified on the command-line.
84    pub root_crate_names: Vec<String>,
85
86    /// All directories for the output of native build commands.
87    ///
88    /// This is currently used to drive some entries which are added to the
89    /// `LD_LIBRARY_PATH` as appropriate.
90    ///
91    /// The order should be deterministic.
92    pub native_dirs: BTreeSet<PathBuf>,
93
94    /// Root output directory (for the local package's artifacts)
95    pub root_output: HashMap<CompileKind, PathBuf>,
96
97    /// Output directory for rust dependencies.
98    /// May be for the host or for a specific target.
99    pub deps_output: HashMap<CompileKind, PathBuf>,
100
101    /// The path to libstd for each target
102    sysroot_target_libdir: HashMap<CompileKind, PathBuf>,
103
104    /// Extra environment variables that were passed to compilations and should
105    /// be passed to future invocations of programs.
106    ///
107    /// The key is the build script metadata for uniquely identifying the
108    /// `RunCustomBuild` unit that generated these env vars.
109    pub extra_env: HashMap<UnitHash, Vec<(String, String)>>,
110
111    /// Libraries to test with rustdoc.
112    pub to_doc_test: Vec<Doctest>,
113
114    /// Rustdoc fingerprint files to determine whether we need to run `rustdoc --merge=finalize`.
115    ///
116    /// See `-Zrustdoc-mergeable-info` for more.
117    pub rustdoc_fingerprints: Option<HashMap<CompileKind, RustdocFingerprint>>,
118
119    /// The target host triple.
120    pub host: String,
121
122    gctx: &'gctx GlobalContext,
123
124    /// Rustc process to be used by default
125    rustc_process: ProcessBuilder,
126    /// Rustc process to be used for workspace crates instead of `rustc_process`
127    rustc_workspace_wrapper_process: ProcessBuilder,
128    /// Optional rustc process to be used for primary crates instead of either `rustc_process` or
129    /// `rustc_workspace_wrapper_process`
130    primary_rustc_process: Option<ProcessBuilder>,
131
132    /// The runner to use for each host or target process.
133    runners: HashMap<CompileKind, Option<(PathBuf, Vec<String>)>>,
134    /// The linker to use for each host or target.
135    linkers: HashMap<CompileKind, Option<PathBuf>>,
136
137    /// The total number of lint warnings emitted by the compilation.
138    pub lint_warning_count: usize,
139}
140
141impl<'gctx> Compilation<'gctx> {
142    pub fn new<'a>(bcx: &BuildContext<'a, 'gctx>) -> CargoResult<Compilation<'gctx>> {
143        let rustc_process = bcx.rustc().process();
144        let primary_rustc_process = bcx.build_config.primary_unit_rustc.clone();
145        let rustc_workspace_wrapper_process = bcx.rustc().workspace_process();
146        let host = bcx.host_triple().to_string();
147
148        // When `target-applies-to-host=false`, and without `--target`,
149        // there will be only `CompileKind::Host` in requested_kinds.
150        // Need to insert target config explicitly for target-applies-to-host=false
151        // to find the correct configs.
152        let insert_explicit_host_runner = !bcx.gctx.target_applies_to_host()?
153            && bcx
154                .build_config
155                .requested_kinds
156                .iter()
157                .any(CompileKind::is_host);
158        let mut runners = bcx
159            .build_config
160            .requested_kinds
161            .iter()
162            .chain(Some(&CompileKind::Host))
163            .map(|kind| Ok((*kind, target_runner(bcx, *kind)?)))
164            .collect::<CargoResult<HashMap<_, _>>>()?;
165        if insert_explicit_host_runner {
166            let kind = explicit_host_kind(&host);
167            runners.insert(kind, target_runner(bcx, kind)?);
168        }
169
170        let mut linkers = bcx
171            .build_config
172            .requested_kinds
173            .iter()
174            .chain(Some(&CompileKind::Host))
175            .map(|kind| Ok((*kind, target_linker(bcx, *kind)?)))
176            .collect::<CargoResult<HashMap<_, _>>>()?;
177        if insert_explicit_host_runner {
178            let kind = explicit_host_kind(&host);
179            linkers.insert(kind, target_linker(bcx, kind)?);
180        }
181        Ok(Compilation {
182            native_dirs: BTreeSet::new(),
183            root_output: HashMap::new(),
184            deps_output: HashMap::new(),
185            sysroot_target_libdir: get_sysroot_target_libdir(bcx)?,
186            tests: Vec::new(),
187            binaries: Vec::new(),
188            cdylibs: Vec::new(),
189            root_crate_names: Vec::new(),
190            extra_env: HashMap::new(),
191            to_doc_test: Vec::new(),
192            rustdoc_fingerprints: None,
193            gctx: bcx.gctx,
194            host,
195            rustc_process,
196            rustc_workspace_wrapper_process,
197            primary_rustc_process,
198            runners,
199            linkers,
200            lint_warning_count: 0,
201        })
202    }
203
204    /// Returns a [`ProcessBuilder`] for running `rustc`.
205    ///
206    /// `is_primary` is true if this is a "primary package", which means it
207    /// was selected by the user on the command-line (such as with a `-p`
208    /// flag), see [`crate::core::compiler::BuildRunner::primary_packages`].
209    ///
210    /// `is_workspace` is true if this is a workspace member.
211    pub fn rustc_process(
212        &self,
213        unit: &Unit,
214        is_primary: bool,
215        is_workspace: bool,
216    ) -> CargoResult<ProcessBuilder> {
217        let mut rustc = if is_primary && self.primary_rustc_process.is_some() {
218            self.primary_rustc_process.clone().unwrap()
219        } else if is_workspace {
220            self.rustc_workspace_wrapper_process.clone()
221        } else {
222            self.rustc_process.clone()
223        };
224        if self.gctx.extra_verbose() {
225            rustc.display_env_vars();
226        }
227        let cmd = fill_rustc_tool_env(rustc, unit);
228        self.fill_env(cmd, &unit.pkg, None, unit.kind, ToolKind::Rustc)
229    }
230
231    /// Returns a [`ProcessBuilder`] for running `rustdoc`.
232    pub fn rustdoc_process(
233        &self,
234        unit: &Unit,
235        script_metas: Option<&Vec<UnitHash>>,
236    ) -> CargoResult<ProcessBuilder> {
237        let mut rustdoc = ProcessBuilder::new(&*self.gctx.rustdoc()?);
238        if self.gctx.extra_verbose() {
239            rustdoc.display_env_vars();
240        }
241        let cmd = fill_rustc_tool_env(rustdoc, unit);
242        let mut cmd = self.fill_env(cmd, &unit.pkg, script_metas, unit.kind, ToolKind::Rustdoc)?;
243        cmd.retry_with_argfile(true);
244        unit.target.edition().cmd_edition_arg(&mut cmd);
245
246        for crate_type in unit.target.rustc_crate_types() {
247            cmd.arg("--crate-type").arg(crate_type.as_str());
248        }
249
250        Ok(cmd)
251    }
252
253    /// Returns a [`ProcessBuilder`] appropriate for running a process for the
254    /// host platform.
255    ///
256    /// This is currently only used for running build scripts. If you use this
257    /// for anything else, please be extra careful on how environment
258    /// variables are set!
259    pub fn host_process<T: AsRef<OsStr>>(
260        &self,
261        cmd: T,
262        pkg: &Package,
263    ) -> CargoResult<ProcessBuilder> {
264        // Only use host runner when -Zhost-config is enabled
265        // to ensure `target.<host>.runner` does not wrap build scripts.
266        let builder = if !self.gctx.target_applies_to_host()?
267            && let Some((runner, args)) = self
268                .runners
269                .get(&CompileKind::Host)
270                .and_then(|x| x.as_ref())
271        {
272            let mut builder = ProcessBuilder::new(runner);
273            builder.args(args);
274            builder.arg(cmd);
275            builder
276        } else {
277            ProcessBuilder::new(cmd)
278        };
279        self.fill_env(builder, pkg, None, CompileKind::Host, ToolKind::HostProcess)
280    }
281
282    pub fn target_runner(&self, kind: CompileKind) -> Option<&(PathBuf, Vec<String>)> {
283        let target_applies_to_host = self.gctx.target_applies_to_host().unwrap_or(true);
284        let kind = if !target_applies_to_host && kind.is_host() {
285            // Use explicit host target triple when `target-applies-to-host=false`
286            // This ensures `host.runner` won't be accidentally applied to `cargo run` / `cargo test`.
287            explicit_host_kind(&self.host)
288        } else {
289            kind
290        };
291        self.runners.get(&kind).and_then(|x| x.as_ref())
292    }
293
294    /// Gets the `[host.linker]` for host build target (build scripts and proc macros).
295    pub fn host_linker(&self) -> Option<&Path> {
296        self.linkers
297            .get(&CompileKind::Host)
298            .and_then(|x| x.as_ref())
299            .map(|x| x.as_path())
300    }
301
302    /// Gets the user-specified linker for a particular host or target.
303    pub fn target_linker(&self, kind: CompileKind) -> Option<&Path> {
304        let target_applies_to_host = self.gctx.target_applies_to_host().unwrap_or(true);
305        let kind = if !target_applies_to_host && kind.is_host() {
306            // Use explicit host target triple when `target-applies-to-host=false`
307            // This ensures `host.linker` won't be accidentally applied to normal builds
308            explicit_host_kind(&self.host)
309        } else {
310            kind
311        };
312        self.linkers
313            .get(&kind)
314            .and_then(|x| x.as_ref())
315            .map(|x| x.as_path())
316    }
317
318    /// Returns a [`ProcessBuilder`] appropriate for running a process for the
319    /// target platform. This is typically used for `cargo run` and `cargo
320    /// test`.
321    ///
322    /// `script_metas` is the metadata for the `RunCustomBuild` unit that this
323    /// unit used for its build script. Use `None` if the package did not have
324    /// a build script.
325    pub fn target_process<T: AsRef<OsStr>>(
326        &self,
327        cmd: T,
328        kind: CompileKind,
329        pkg: &Package,
330        script_metas: Option<&Vec<UnitHash>>,
331    ) -> CargoResult<ProcessBuilder> {
332        let builder = if let Some((runner, args)) = self.target_runner(kind) {
333            let mut builder = ProcessBuilder::new(runner);
334            builder.args(args);
335            builder.arg(cmd);
336            builder
337        } else {
338            ProcessBuilder::new(cmd)
339        };
340        let tool_kind = ToolKind::TargetProcess;
341        let mut builder = self.fill_env(builder, pkg, script_metas, kind, tool_kind)?;
342
343        if let Some(client) = self.gctx.jobserver_from_env() {
344            builder.inherit_jobserver(client);
345        }
346
347        Ok(builder)
348    }
349
350    /// Prepares a new process with an appropriate environment to run against
351    /// the artifacts produced by the build process.
352    ///
353    /// The package argument is also used to configure environment variables as
354    /// well as the working directory of the child process.
355    fn fill_env(
356        &self,
357        mut cmd: ProcessBuilder,
358        pkg: &Package,
359        script_metas: Option<&Vec<UnitHash>>,
360        kind: CompileKind,
361        tool_kind: ToolKind,
362    ) -> CargoResult<ProcessBuilder> {
363        let mut search_path = Vec::new();
364        if tool_kind.is_rustc_tool() {
365            if matches!(tool_kind, ToolKind::Rustdoc) {
366                // HACK: `rustdoc --test` not only compiles but executes doctests.
367                // Ideally only execution phase should have search paths appended,
368                // so the executions can find native libs just like other tests.
369                // However, there is no way to separate these two phase, so this
370                // hack is added for both phases.
371                // TODO: handle doctest-xcompile
372                search_path.extend(super::filter_dynamic_search_path(
373                    self.native_dirs.iter(),
374                    &self.root_output[&CompileKind::Host],
375                ));
376            }
377            search_path.push(self.deps_output[&CompileKind::Host].clone());
378        } else {
379            if let Some(path) = self.root_output.get(&kind) {
380                search_path.extend(super::filter_dynamic_search_path(
381                    self.native_dirs.iter(),
382                    path,
383                ));
384                search_path.push(path.clone());
385            }
386            search_path.push(self.deps_output[&kind].clone());
387            // For build-std, we don't want to accidentally pull in any shared
388            // libs from the sysroot that ships with rustc. This may not be
389            // required (at least I cannot craft a situation where it
390            // matters), but is here to be safe.
391            if self.gctx.cli_unstable().build_std.is_none() ||
392                // Proc macros dynamically link to std, so set it anyway.
393                pkg.proc_macro()
394            {
395                search_path.push(self.sysroot_target_libdir[&kind].clone());
396            }
397        }
398
399        let dylib_path = paths::dylib_path();
400        let dylib_path_is_empty = dylib_path.is_empty();
401        if dylib_path.starts_with(&search_path) {
402            search_path = dylib_path;
403        } else {
404            search_path.extend(dylib_path.into_iter());
405        }
406        if cfg!(target_os = "macos") && dylib_path_is_empty {
407            // These are the defaults when DYLD_FALLBACK_LIBRARY_PATH isn't
408            // set or set to an empty string. Since Cargo is explicitly setting
409            // the value, make sure the defaults still work.
410            if let Some(home) = self.gctx.get_env_os("HOME") {
411                search_path.push(PathBuf::from(home).join("lib"));
412            }
413            search_path.push(PathBuf::from("/usr/local/lib"));
414            search_path.push(PathBuf::from("/usr/lib"));
415        }
416        let search_path = paths::join_paths(&search_path, paths::dylib_path_envvar())?;
417
418        cmd.env(paths::dylib_path_envvar(), &search_path);
419        if let Some(meta_vec) = script_metas {
420            for meta in meta_vec {
421                if let Some(env) = self.extra_env.get(meta) {
422                    for (k, v) in env {
423                        cmd.env(k, v);
424                    }
425                }
426            }
427        }
428
429        let cargo_exe = self.gctx.cargo_exe()?;
430        cmd.env(crate::CARGO_ENV, cargo_exe);
431
432        // When adding new environment variables depending on
433        // crate properties which might require rebuild upon change
434        // consider adding the corresponding properties to the hash
435        // in BuildContext::target_metadata()
436        cmd.env("CARGO_MANIFEST_DIR", pkg.root())
437            .env("CARGO_MANIFEST_PATH", pkg.manifest_path())
438            .env("CARGO_PKG_VERSION_MAJOR", &pkg.version().major.to_string())
439            .env("CARGO_PKG_VERSION_MINOR", &pkg.version().minor.to_string())
440            .env("CARGO_PKG_VERSION_PATCH", &pkg.version().patch.to_string())
441            .env("CARGO_PKG_VERSION_PRE", pkg.version().pre.as_str())
442            .env("CARGO_PKG_VERSION", &pkg.version().to_string())
443            .env("CARGO_PKG_NAME", &*pkg.name());
444
445        for (key, value) in pkg.manifest().metadata().env_vars() {
446            cmd.env(key, value.as_ref());
447        }
448
449        cmd.cwd(pkg.root());
450
451        apply_env_config(self.gctx, &mut cmd)?;
452
453        Ok(cmd)
454    }
455}
456
457/// Prepares a `rustc_tool` process with additional environment variables
458/// that are only relevant in a context that has a unit
459fn fill_rustc_tool_env(mut cmd: ProcessBuilder, unit: &Unit) -> ProcessBuilder {
460    if unit.target.is_executable() {
461        let name = unit
462            .target
463            .binary_filename()
464            .unwrap_or(unit.target.name().to_string());
465
466        cmd.env("CARGO_BIN_NAME", name);
467    }
468    cmd.env("CARGO_CRATE_NAME", unit.target.crate_name());
469    cmd
470}
471
472fn get_sysroot_target_libdir(
473    bcx: &BuildContext<'_, '_>,
474) -> CargoResult<HashMap<CompileKind, PathBuf>> {
475    bcx.all_kinds
476        .iter()
477        .map(|&kind| {
478            let Some(info) = bcx.target_data.get_info(kind) else {
479                let target = match kind {
480                    CompileKind::Host => "host".to_owned(),
481                    CompileKind::Target(s) => s.short_name().to_owned(),
482                };
483
484                let dependency = bcx
485                    .unit_graph
486                    .iter()
487                    .find_map(|(u, _)| (u.kind == kind).then_some(u.pkg.summary().package_id()))
488                    .unwrap();
489
490                anyhow::bail!(
491                    "could not find specification for target `{target}`.\n  \
492                    Dependency `{dependency}` requires to build for target `{target}`."
493                )
494            };
495
496            Ok((kind, info.sysroot_target_libdir.clone()))
497        })
498        .collect()
499}
500
501fn target_runner(
502    bcx: &BuildContext<'_, '_>,
503    kind: CompileKind,
504) -> CargoResult<Option<(PathBuf, Vec<String>)>> {
505    if let Some(runner) = bcx.target_data.target_config(kind).runner.as_ref() {
506        let path = runner.val.path.clone().resolve_program(bcx.gctx);
507        return Ok(Some((path, runner.val.args.clone())));
508    }
509
510    // try target.'cfg(...)'.runner
511    let target_cfg = bcx.target_data.info(kind).cfg();
512    let mut cfgs = bcx
513        .gctx
514        .target_cfgs()?
515        .iter()
516        .filter_map(|(key, cfg)| cfg.runner.as_ref().map(|runner| (key, runner)))
517        .filter(|(key, _runner)| CfgExpr::matches_key(key, target_cfg));
518    let matching_runner = cfgs.next();
519    if let Some((key, runner)) = cfgs.next() {
520        anyhow::bail!(
521            "several matching instances of `target.'cfg(..)'.runner` in configurations\n\
522             first match `{}` located in {}\n\
523             second match `{}` located in {}",
524            matching_runner.unwrap().0,
525            matching_runner.unwrap().1.definition,
526            key,
527            runner.definition
528        );
529    }
530    Ok(matching_runner.map(|(_k, runner)| {
531        (
532            runner.val.path.clone().resolve_program(bcx.gctx),
533            runner.val.args.clone(),
534        )
535    }))
536}
537
538/// Gets the user-specified linker for a particular host or target from the configuration.
539fn target_linker(bcx: &BuildContext<'_, '_>, kind: CompileKind) -> CargoResult<Option<PathBuf>> {
540    // Try host.linker and target.{}.linker.
541    if let Some(path) = bcx
542        .target_data
543        .target_config(kind)
544        .linker
545        .as_ref()
546        .map(|l| l.val.clone().resolve_program(bcx.gctx))
547    {
548        return Ok(Some(path));
549    }
550
551    // Try target.'cfg(...)'.linker.
552    let target_cfg = bcx.target_data.info(kind).cfg();
553    let mut cfgs = bcx
554        .gctx
555        .target_cfgs()?
556        .iter()
557        .filter_map(|(key, cfg)| cfg.linker.as_ref().map(|linker| (key, linker)))
558        .filter(|(key, _linker)| CfgExpr::matches_key(key, target_cfg));
559    let matching_linker = cfgs.next();
560    if let Some((key, linker)) = cfgs.next() {
561        anyhow::bail!(
562            "several matching instances of `target.'cfg(..)'.linker` in configurations\n\
563             first match `{}` located in {}\n\
564             second match `{}` located in {}",
565            matching_linker.unwrap().0,
566            matching_linker.unwrap().1.definition,
567            key,
568            linker.definition
569        );
570    }
571    Ok(matching_linker.map(|(_k, linker)| linker.val.clone().resolve_program(bcx.gctx)))
572}
573
574fn explicit_host_kind(host: &str) -> CompileKind {
575    let target = CompileTarget::new(host, false).expect("must be a host tuple");
576    CompileKind::Target(target)
577}