Skip to main content

cargo/compiler/
compilation.rs

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