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