#
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::{paths, ProcessBuilder};
    9
    10use crate::core::compiler::apply_env_config;
    11use crate::core::compiler::BuildContext;
    12use crate::core::compiler::{CompileKind, Unit, UnitHash};
    13use crate::core::Package;
    14use crate::util::{context, CargoResult, GlobalContext};
    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_meta: Option<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_meta: Option<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 warnings emitted by the compilation.
    127    pub 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            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_meta: Option<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_meta, 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_meta` 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_meta: Option<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_meta, 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_meta: Option<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            search_path.extend(super::filter_dynamic_search_path(
    309                self.native_dirs.iter(),
    310                &self.root_output[&kind],
    311            ));
    312            search_path.push(self.deps_output[&kind].clone());
    313            search_path.push(self.root_output[&kind].clone());
    314            // For build-std, we don't want to accidentally pull in any shared
    315            // libs from the sysroot that ships with rustc. This may not be
    316            // required (at least I cannot craft a situation where it
    317            // matters), but is here to be safe.
    318            if self.gctx.cli_unstable().build_std.is_none() ||
    319                // Proc macros dynamically link to std, so set it anyway.
    320                pkg.proc_macro()
    321            {
    322                search_path.push(self.sysroot_target_libdir[&kind].clone());
    323            }
    324        }
    325
    326        let dylib_path = paths::dylib_path();
    327        let dylib_path_is_empty = dylib_path.is_empty();
    328        if dylib_path.starts_with(&search_path) {
    329            search_path = dylib_path;
    330        } else {
    331            search_path.extend(dylib_path.into_iter());
    332        }
    333        if cfg!(target_os = "macos") && dylib_path_is_empty {
    334            // These are the defaults when DYLD_FALLBACK_LIBRARY_PATH isn't
    335            // set or set to an empty string. Since Cargo is explicitly setting
    336            // the value, make sure the defaults still work.
    337            if let Some(home) = self.gctx.get_env_os("HOME") {
    338                search_path.push(PathBuf::from(home).join("lib"));
    339            }
    340            search_path.push(PathBuf::from("/usr/local/lib"));
    341            search_path.push(PathBuf::from("/usr/lib"));
    342        }
    343        let search_path = paths::join_paths(&search_path, paths::dylib_path_envvar())?;
    344
    345        cmd.env(paths::dylib_path_envvar(), &search_path);
    346        if let Some(meta) = script_meta {
    347            if let Some(env) = self.extra_env.get(&meta) {
    348                for (k, v) in env {
    349                    cmd.env(k, v);
    350                }
    351            }
    352        }
    353
    354        let cargo_exe = self.gctx.cargo_exe()?;
    355        cmd.env(crate::CARGO_ENV, cargo_exe);
    356
    357        // When adding new environment variables depending on
    358        // crate properties which might require rebuild upon change
    359        // consider adding the corresponding properties to the hash
    360        // in BuildContext::target_metadata()
    361        cmd.env("CARGO_MANIFEST_DIR", pkg.root())
    362            .env("CARGO_MANIFEST_PATH", pkg.manifest_path())
    363            .env("CARGO_PKG_VERSION_MAJOR", &pkg.version().major.to_string())
    364            .env("CARGO_PKG_VERSION_MINOR", &pkg.version().minor.to_string())
    365            .env("CARGO_PKG_VERSION_PATCH", &pkg.version().patch.to_string())
    366            .env("CARGO_PKG_VERSION_PRE", pkg.version().pre.as_str())
    367            .env("CARGO_PKG_VERSION", &pkg.version().to_string())
    368            .env("CARGO_PKG_NAME", &*pkg.name());
    369
    370        for (key, value) in pkg.manifest().metadata().env_vars() {
    371            cmd.env(key, value.as_ref());
    372        }
    373
    374        cmd.cwd(pkg.root());
    375
    376        apply_env_config(self.gctx, &mut cmd)?;
    377
    378        Ok(cmd)
    379    }
    380}
    381
    382/// Prepares a `rustc_tool` process with additional environment variables
    383/// that are only relevant in a context that has a unit
    384fn fill_rustc_tool_env(mut cmd: ProcessBuilder, unit: &Unit) -> ProcessBuilder {
    385    if unit.target.is_executable() {
    386        let name = unit
    387            .target
    388            .binary_filename()
    389            .unwrap_or(unit.target.name().to_string());
    390
    391        cmd.env("CARGO_BIN_NAME", name);
    392    }
    393    cmd.env("CARGO_CRATE_NAME", unit.target.crate_name());
    394    cmd
    395}
    396
    397fn get_sysroot_target_libdir(
    398    bcx: &BuildContext<'_, '_>,
    399) -> CargoResult<HashMap<CompileKind, PathBuf>> {
    400    bcx.all_kinds
    401        .iter()
    402        .map(|&kind| {
    403            let Some(info) = bcx.target_data.get_info(kind) else {
    404                let target = match kind {
    405                    CompileKind::Host => "host".to_owned(),
    406                    CompileKind::Target(s) => s.short_name().to_owned(),
    407                };
    408
    409                let dependency = bcx
    410                    .unit_graph
    411                    .iter()
    412                    .find_map(|(u, _)| (u.kind == kind).then_some(u.pkg.summary().package_id()))
    413                    .unwrap();
    414
    415                anyhow::bail!(
    416                    "could not find specification for target `{target}`.\n  \
    417                    Dependency `{dependency}` requires to build for target `{target}`."
    418                )
    419            };
    420
    421            Ok((kind, info.sysroot_target_libdir.clone()))
    422        })
    423        .collect()
    424}
    425
    426fn target_runner(
    427    bcx: &BuildContext<'_, '_>,
    428    kind: CompileKind,
    429) -> CargoResult<Option<(PathBuf, Vec<String>)>> {
    430    let target = bcx.target_data.short_name(&kind);
    431
    432    // try target.{}.runner
    433    let key = format!("target.{}.runner", target);
    434
    435    if let Some(v) = bcx.gctx.get::<Option<context::PathAndArgs>>(&key)? {
    436        let path = v.path.resolve_program(bcx.gctx);
    437        return Ok(Some((path, v.args)));
    438    }
    439
    440    // try target.'cfg(...)'.runner
    441    let target_cfg = bcx.target_data.info(kind).cfg();
    442    let mut cfgs = bcx
    443        .gctx
    444        .target_cfgs()?
    445        .iter()
    446        .filter_map(|(key, cfg)| cfg.runner.as_ref().map(|runner| (key, runner)))
    447        .filter(|(key, _runner)| CfgExpr::matches_key(key, target_cfg));
    448    let matching_runner = cfgs.next();
    449    if let Some((key, runner)) = cfgs.next() {
    450        anyhow::bail!(
    451            "several matching instances of `target.'cfg(..)'.runner` in configurations\n\
    452             first match `{}` located in {}\n\
    453             second match `{}` located in {}",
    454            matching_runner.unwrap().0,
    455            matching_runner.unwrap().1.definition,
    456            key,
    457            runner.definition
    458        );
    459    }
    460    Ok(matching_runner.map(|(_k, runner)| {
    461        (
    462            runner.val.path.clone().resolve_program(bcx.gctx),
    463            runner.val.args.clone(),
    464        )
    465    }))
    466}
    467
    468/// Gets the user-specified linker for a particular host or target from the configuration.
    469fn target_linker(bcx: &BuildContext<'_, '_>, kind: CompileKind) -> CargoResult<Option<PathBuf>> {
    470    // Try host.linker and target.{}.linker.
    471    if let Some(path) = bcx
    472        .target_data
    473        .target_config(kind)
    474        .linker
    475        .as_ref()
    476        .map(|l| l.val.clone().resolve_program(bcx.gctx))
    477    {
    478        return Ok(Some(path));
    479    }
    480
    481    // Try target.'cfg(...)'.linker.
    482    let target_cfg = bcx.target_data.info(kind).cfg();
    483    let mut cfgs = bcx
    484        .gctx
    485        .target_cfgs()?
    486        .iter()
    487        .filter_map(|(key, cfg)| cfg.linker.as_ref().map(|linker| (key, linker)))
    488        .filter(|(key, _linker)| CfgExpr::matches_key(key, target_cfg));
    489    let matching_linker = cfgs.next();
    490    if let Some((key, linker)) = cfgs.next() {
    491        anyhow::bail!(
    492            "several matching instances of `target.'cfg(..)'.linker` in configurations\n\
    493             first match `{}` located in {}\n\
    494             second match `{}` located in {}",
    495            matching_linker.unwrap().0,
    496            matching_linker.unwrap().1.definition,
    497            key,
    498            linker.definition
    499        );
    500    }
    501    Ok(matching_linker.map(|(_k, linker)| linker.val.clone().resolve_program(bcx.gctx)))
    502}