use std::collections::{BTreeSet, HashMap}; use std::env; use std::ffi::{OsStr, OsString}; use std::path::PathBuf; use cargo_platform::CfgExpr; use cargo_util::{paths, ProcessBuilder}; use super::BuildContext; use crate::core::compiler::{CompileKind, Metadata, Unit}; use crate::core::Package; use crate::util::{config, CargoResult, Config}; /// Structure with enough information to run `rustdoc --test`. pub struct Doctest { /// What's being doctested pub unit: Unit, /// Arguments needed to pass to rustdoc to run this test. pub args: Vec, /// Whether or not -Zunstable-options is needed. pub unstable_opts: bool, /// The -Clinker value to use. pub linker: Option, /// The script metadata, if this unit's package has a build script. /// /// This is used for indexing [`Compilation::extra_env`]. pub script_meta: Option, } /// Information about the output of a unit. #[derive(Ord, PartialOrd, Eq, PartialEq)] pub struct UnitOutput { /// The unit that generated this output. pub unit: Unit, /// Path to the unit's primary output (an executable or cdylib). pub path: PathBuf, /// The script metadata, if this unit's package has a build script. /// /// This is used for indexing [`Compilation::extra_env`]. pub script_meta: Option, } /// A structure returning the result of a compilation. pub struct Compilation<'cfg> { /// An array of all tests created during this compilation. pub tests: Vec, /// An array of all binaries created. pub binaries: Vec, /// An array of all cdylibs created. pub cdylibs: Vec, /// The crate names of the root units specified on the command-line. pub root_crate_names: Vec, /// All directories for the output of native build commands. /// /// This is currently used to drive some entries which are added to the /// LD_LIBRARY_PATH as appropriate. /// /// The order should be deterministic. pub native_dirs: BTreeSet, /// Root output directory (for the local package's artifacts) pub root_output: HashMap, /// Output directory for rust dependencies. /// May be for the host or for a specific target. pub deps_output: HashMap, /// The path to the host libdir for the compiler used sysroot_host_libdir: PathBuf, /// The path to libstd for each target sysroot_target_libdir: HashMap, /// Extra environment variables that were passed to compilations and should /// be passed to future invocations of programs. /// /// The key is the build script metadata for uniquely identifying the /// `RunCustomBuild` unit that generated these env vars. pub extra_env: HashMap>, /// Libraries to test with rustdoc. pub to_doc_test: Vec, /// The target host triple. pub host: String, config: &'cfg Config, /// Rustc process to be used by default rustc_process: ProcessBuilder, /// Rustc process to be used for workspace crates instead of rustc_process rustc_workspace_wrapper_process: ProcessBuilder, /// Optional rustc process to be used for primary crates instead of either rustc_process or /// rustc_workspace_wrapper_process primary_rustc_process: Option, target_runners: HashMap)>>, } impl<'cfg> Compilation<'cfg> { pub fn new<'a>(bcx: &BuildContext<'a, 'cfg>) -> CargoResult> { let mut rustc = bcx.rustc().process(); let mut primary_rustc_process = bcx.build_config.primary_unit_rustc.clone(); let mut rustc_workspace_wrapper_process = bcx.rustc().workspace_process(); if bcx.config.extra_verbose() { rustc.display_env_vars(); rustc_workspace_wrapper_process.display_env_vars(); if let Some(rustc) = primary_rustc_process.as_mut() { rustc.display_env_vars(); } } Ok(Compilation { // TODO: deprecated; remove. native_dirs: BTreeSet::new(), root_output: HashMap::new(), deps_output: HashMap::new(), sysroot_host_libdir: bcx .target_data .info(CompileKind::Host) .sysroot_host_libdir .clone(), sysroot_target_libdir: bcx .all_kinds .iter() .map(|&kind| { ( kind, bcx.target_data.info(kind).sysroot_target_libdir.clone(), ) }) .collect(), tests: Vec::new(), binaries: Vec::new(), cdylibs: Vec::new(), root_crate_names: Vec::new(), extra_env: HashMap::new(), to_doc_test: Vec::new(), config: bcx.config, host: bcx.host_triple().to_string(), rustc_process: rustc, rustc_workspace_wrapper_process, primary_rustc_process, target_runners: bcx .build_config .requested_kinds .iter() .chain(Some(&CompileKind::Host)) .map(|kind| Ok((*kind, target_runner(bcx, *kind)?))) .collect::>>()?, }) } /// Returns a [`ProcessBuilder`] for running `rustc`. /// /// `is_primary` is true if this is a "primary package", which means it /// was selected by the user on the command-line (such as with a `-p` /// flag), see [`crate::core::compiler::Context::primary_packages`]. /// /// `is_workspace` is true if this is a workspace member. pub fn rustc_process( &self, unit: &Unit, is_primary: bool, is_workspace: bool, ) -> CargoResult { let rustc = if is_primary && self.primary_rustc_process.is_some() { self.primary_rustc_process.clone().unwrap() } else if is_workspace { self.rustc_workspace_wrapper_process.clone() } else { self.rustc_process.clone() }; let cmd = fill_rustc_tool_env(rustc, unit); self.fill_env(cmd, &unit.pkg, None, unit.kind, true) } /// Returns a [`ProcessBuilder`] for running `rustdoc`. pub fn rustdoc_process( &self, unit: &Unit, script_meta: Option, ) -> CargoResult { let rustdoc = ProcessBuilder::new(&*self.config.rustdoc()?); let cmd = fill_rustc_tool_env(rustdoc, unit); let mut p = self.fill_env(cmd, &unit.pkg, script_meta, unit.kind, true)?; unit.target.edition().cmd_edition_arg(&mut p); for crate_type in unit.target.rustc_crate_types() { p.arg("--crate-type").arg(crate_type.as_str()); } Ok(p) } /// Returns a [`ProcessBuilder`] appropriate for running a process for the /// host platform. /// /// This is currently only used for running build scripts. If you use this /// for anything else, please be extra careful on how environment /// variables are set! pub fn host_process>( &self, cmd: T, pkg: &Package, ) -> CargoResult { self.fill_env( ProcessBuilder::new(cmd), pkg, None, CompileKind::Host, false, ) } pub fn target_runner(&self, kind: CompileKind) -> Option<&(PathBuf, Vec)> { self.target_runners.get(&kind).and_then(|x| x.as_ref()) } /// Returns a [`ProcessBuilder`] appropriate for running a process for the /// target platform. This is typically used for `cargo run` and `cargo /// test`. /// /// `script_meta` is the metadata for the `RunCustomBuild` unit that this /// unit used for its build script. Use `None` if the package did not have /// a build script. pub fn target_process>( &self, cmd: T, kind: CompileKind, pkg: &Package, script_meta: Option, ) -> CargoResult { let builder = if let Some((runner, args)) = self.target_runner(kind) { let mut builder = ProcessBuilder::new(runner); builder.args(args); builder.arg(cmd); builder } else { ProcessBuilder::new(cmd) }; self.fill_env(builder, pkg, script_meta, kind, false) } /// Prepares a new process with an appropriate environment to run against /// the artifacts produced by the build process. /// /// The package argument is also used to configure environment variables as /// well as the working directory of the child process. fn fill_env( &self, mut cmd: ProcessBuilder, pkg: &Package, script_meta: Option, kind: CompileKind, is_rustc_tool: bool, ) -> CargoResult { let mut search_path = Vec::new(); if is_rustc_tool { search_path.push(self.deps_output[&CompileKind::Host].clone()); search_path.push(self.sysroot_host_libdir.clone()); } else { search_path.extend(super::filter_dynamic_search_path( self.native_dirs.iter(), &self.root_output[&kind], )); search_path.push(self.deps_output[&kind].clone()); search_path.push(self.root_output[&kind].clone()); // For build-std, we don't want to accidentally pull in any shared // libs from the sysroot that ships with rustc. This may not be // required (at least I cannot craft a situation where it // matters), but is here to be safe. if self.config.cli_unstable().build_std.is_none() { search_path.push(self.sysroot_target_libdir[&kind].clone()); } } let dylib_path = paths::dylib_path(); let dylib_path_is_empty = dylib_path.is_empty(); search_path.extend(dylib_path.into_iter()); if cfg!(target_os = "macos") && dylib_path_is_empty { // These are the defaults when DYLD_FALLBACK_LIBRARY_PATH isn't // set or set to an empty string. Since Cargo is explicitly setting // the value, make sure the defaults still work. if let Some(home) = env::var_os("HOME") { search_path.push(PathBuf::from(home).join("lib")); } search_path.push(PathBuf::from("/usr/local/lib")); search_path.push(PathBuf::from("/usr/lib")); } let search_path = paths::join_paths(&search_path, paths::dylib_path_envvar())?; cmd.env(paths::dylib_path_envvar(), &search_path); if let Some(meta) = script_meta { if let Some(env) = self.extra_env.get(&meta) { for (k, v) in env { cmd.env(k, v); } } } let metadata = pkg.manifest().metadata(); let cargo_exe = self.config.cargo_exe()?; cmd.env(crate::CARGO_ENV, cargo_exe); // When adding new environment variables depending on // crate properties which might require rebuild upon change // consider adding the corresponding properties to the hash // in BuildContext::target_metadata() cmd.env("CARGO_MANIFEST_DIR", pkg.root()) .env("CARGO_PKG_VERSION_MAJOR", &pkg.version().major.to_string()) .env("CARGO_PKG_VERSION_MINOR", &pkg.version().minor.to_string()) .env("CARGO_PKG_VERSION_PATCH", &pkg.version().patch.to_string()) .env("CARGO_PKG_VERSION_PRE", pkg.version().pre.as_str()) .env("CARGO_PKG_VERSION", &pkg.version().to_string()) .env("CARGO_PKG_NAME", &*pkg.name()) .env( "CARGO_PKG_DESCRIPTION", metadata.description.as_ref().unwrap_or(&String::new()), ) .env( "CARGO_PKG_HOMEPAGE", metadata.homepage.as_ref().unwrap_or(&String::new()), ) .env( "CARGO_PKG_REPOSITORY", metadata.repository.as_ref().unwrap_or(&String::new()), ) .env( "CARGO_PKG_LICENSE", metadata.license.as_ref().unwrap_or(&String::new()), ) .env( "CARGO_PKG_LICENSE_FILE", metadata.license_file.as_ref().unwrap_or(&String::new()), ) .env("CARGO_PKG_AUTHORS", &pkg.authors().join(":")) .cwd(pkg.root()); if self.config.cli_unstable().configurable_env { // Apply any environment variables from the config for (key, value) in self.config.env_config()?.iter() { if value.is_force() || cmd.get_env(key).is_none() { cmd.env(key, value.resolve(self.config)); } } } Ok(cmd) } } /// Prepares a rustc_tool process with additional environment variables /// that are only relevant in a context that has a unit fn fill_rustc_tool_env(mut cmd: ProcessBuilder, unit: &Unit) -> ProcessBuilder { if unit.target.is_bin() { cmd.env("CARGO_BIN_NAME", unit.target.name()); } cmd.env("CARGO_CRATE_NAME", unit.target.crate_name()); cmd } fn target_runner( bcx: &BuildContext<'_, '_>, kind: CompileKind, ) -> CargoResult)>> { let target = bcx.target_data.short_name(&kind); // try target.{}.runner let key = format!("target.{}.runner", target); if let Some(v) = bcx.config.get::>(&key)? { let path = v.path.resolve_program(bcx.config); return Ok(Some((path, v.args))); } // try target.'cfg(...)'.runner let target_cfg = bcx.target_data.info(kind).cfg(); let mut cfgs = bcx .config .target_cfgs()? .iter() .filter_map(|(key, cfg)| cfg.runner.as_ref().map(|runner| (key, runner))) .filter(|(key, _runner)| CfgExpr::matches_key(key, target_cfg)); let matching_runner = cfgs.next(); if let Some((key, runner)) = cfgs.next() { anyhow::bail!( "several matching instances of `target.'cfg(..)'.runner` in `.cargo/config`\n\ first match `{}` located in {}\n\ second match `{}` located in {}", matching_runner.unwrap().0, matching_runner.unwrap().1.definition, key, runner.definition ); } Ok(matching_runner.map(|(_k, runner)| { ( runner.val.path.clone().resolve_program(bcx.config), runner.val.args.clone(), ) })) }