Skip to main content

cargo/core/compiler/
compilation.rs

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