Skip to main content

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