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