#
cargo/core/compiler/
compilation.rs

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