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