blob: 1ac7f5be17232653a758edaa4341dd672455ac02 [file] [log] [blame]
use std::collections::{BTreeSet, HashMap, HashSet};
use std::env;
use std::ffi::{OsStr, OsString};
use std::path::PathBuf;
use cargo_platform::CfgExpr;
use semver::Version;
use super::BuildContext;
use crate::core::compiler::CompileKind;
use crate::core::{Edition, Package, PackageId, Target};
use crate::util::{self, config, join_paths, process, CargoResult, Config, ProcessBuilder};
/// Structure with enough information to run `rustdoc --test`.
pub struct Doctest {
/// The package being doc-tested.
pub package: Package,
/// The target being tested (currently always the package's lib).
pub target: Target,
/// Arguments needed to pass to rustdoc to run this test.
pub args: Vec<OsString>,
/// Whether or not -Zunstable-options is needed.
pub unstable_opts: bool,
}
/// A structure returning the result of a compilation.
pub struct Compilation<'cfg> {
/// An array of all tests created during this compilation.
/// `(package, target, path_to_test_exe)`
pub tests: Vec<(Package, Target, PathBuf)>,
/// An array of all binaries created.
pub binaries: Vec<PathBuf>,
/// 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<PathBuf>,
/// Root output directory (for the local package's artifacts)
pub root_output: PathBuf,
/// Output directory for rust dependencies.
/// May be for the host or for a specific target.
pub deps_output: PathBuf,
/// Output directory for the rust host dependencies.
pub host_deps_output: PathBuf,
/// The path to rustc's own libstd
pub host_dylib_path: PathBuf,
/// The path to libstd for the target
pub target_dylib_path: PathBuf,
/// Extra environment variables that were passed to compilations and should
/// be passed to future invocations of programs.
pub extra_env: HashMap<PackageId, Vec<(String, String)>>,
/// Libraries to test with rustdoc.
pub to_doc_test: Vec<Doctest>,
/// Features per package enabled during this compilation.
pub cfgs: HashMap<PackageId, HashSet<String>>,
/// Flags to pass to rustdoc when invoked from cargo test, per package.
pub rustdocflags: HashMap<PackageId, Vec<String>>,
pub host: String,
pub target: String,
config: &'cfg Config,
rustc_process: ProcessBuilder,
primary_unit_rustc_process: Option<ProcessBuilder>,
target_runner: Option<(PathBuf, Vec<String>)>,
}
impl<'cfg> Compilation<'cfg> {
pub fn new<'a>(
bcx: &BuildContext<'a, 'cfg>,
default_kind: CompileKind,
) -> CargoResult<Compilation<'cfg>> {
let mut rustc = bcx.rustc().process();
let mut primary_unit_rustc_process = bcx.build_config.primary_unit_rustc.clone();
if bcx.config.extra_verbose() {
rustc.display_env_vars();
if let Some(rustc) = primary_unit_rustc_process.as_mut() {
rustc.display_env_vars();
}
}
Ok(Compilation {
// TODO: deprecated; remove.
native_dirs: BTreeSet::new(),
root_output: PathBuf::from("/"),
deps_output: PathBuf::from("/"),
host_deps_output: PathBuf::from("/"),
host_dylib_path: bcx
.target_data
.info(CompileKind::Host)
.sysroot_host_libdir
.clone(),
target_dylib_path: bcx
.target_data
.info(default_kind)
.sysroot_target_libdir
.clone(),
tests: Vec::new(),
binaries: Vec::new(),
extra_env: HashMap::new(),
to_doc_test: Vec::new(),
cfgs: HashMap::new(),
rustdocflags: HashMap::new(),
config: bcx.config,
rustc_process: rustc,
primary_unit_rustc_process,
host: bcx.host_triple().to_string(),
target: bcx.target_data.short_name(&default_kind).to_string(),
target_runner: target_runner(bcx, default_kind)?,
})
}
/// See `process`.
pub fn rustc_process(&self, pkg: &Package, is_primary: bool) -> CargoResult<ProcessBuilder> {
let rustc = if is_primary {
self.primary_unit_rustc_process
.clone()
.unwrap_or_else(|| self.rustc_process.clone())
} else {
self.rustc_process.clone()
};
self.fill_env(rustc, pkg, true)
}
/// See `process`.
pub fn rustdoc_process(&self, pkg: &Package, target: &Target) -> CargoResult<ProcessBuilder> {
let mut p = self.fill_env(process(&*self.config.rustdoc()?), pkg, false)?;
if target.edition() != Edition::Edition2015 {
p.arg(format!("--edition={}", target.edition()));
}
for crate_type in target.rustc_crate_types() {
p.arg("--crate-type").arg(crate_type);
}
Ok(p)
}
/// See `process`.
pub fn host_process<T: AsRef<OsStr>>(
&self,
cmd: T,
pkg: &Package,
) -> CargoResult<ProcessBuilder> {
self.fill_env(process(cmd), pkg, true)
}
pub fn target_runner(&self) -> &Option<(PathBuf, Vec<String>)> {
&self.target_runner
}
/// See `process`.
pub fn target_process<T: AsRef<OsStr>>(
&self,
cmd: T,
pkg: &Package,
) -> CargoResult<ProcessBuilder> {
let builder = if let Some((ref runner, ref args)) = *self.target_runner() {
let mut builder = process(runner);
builder.args(args);
builder.arg(cmd);
builder
} else {
process(cmd)
};
self.fill_env(builder, pkg, 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,
is_host: bool,
) -> CargoResult<ProcessBuilder> {
let mut search_path = if is_host {
let mut search_path = vec![self.host_deps_output.clone()];
search_path.push(self.host_dylib_path.clone());
search_path
} else {
let mut search_path =
super::filter_dynamic_search_path(self.native_dirs.iter(), &self.root_output);
search_path.push(self.deps_output.clone());
search_path.push(self.root_output.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.target_dylib_path.clone());
}
search_path
};
let dylib_path = util::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 = join_paths(&search_path, util::dylib_path_envvar())?;
cmd.env(util::dylib_path_envvar(), &search_path);
if let Some(env) = self.extra_env.get(&pkg.package_id()) {
for &(ref k, ref 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",
&pre_version_component(pkg.version()),
)
.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_AUTHORS", &pkg.authors().join(":"))
.cwd(pkg.root());
Ok(cmd)
}
}
fn pre_version_component(v: &Version) -> String {
if v.pre.is_empty() {
return String::new();
}
let mut ret = String::new();
for (i, x) in v.pre.iter().enumerate() {
if i != 0 {
ret.push('.')
};
ret.push_str(&x.to_string());
}
ret
}
fn target_runner(
bcx: &BuildContext<'_, '_>,
kind: CompileKind,
) -> CargoResult<Option<(PathBuf, Vec<String>)>> {
let target = bcx.target_data.short_name(&kind);
// try target.{}.runner
let key = format!("target.{}.runner", target);
if let Some(v) = bcx.config.get::<Option<config::PathAndArgs>>(&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(),
)
}))
}