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