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