use std::collections::{BTreeSet, HashMap};
use std::ffi::{OsStr, OsString};
use std::path::PathBuf;
use cargo_platform::CfgExpr;
use cargo_util::{paths, ProcessBuilder};
use crate::core::compiler::apply_env_config;
use crate::core::compiler::BuildContext;
use crate::core::compiler::{CompileKind, Metadata, Unit};
use crate::core::Package;
use crate::util::{context, CargoResult, GlobalContext};
#[derive(Debug)]
enum ToolKind {
Rustc,
Rustdoc,
HostProcess,
TargetProcess,
}
impl ToolKind {
fn is_rustc_tool(&self) -> bool {
matches!(self, ToolKind::Rustc | ToolKind::Rustdoc)
}
}
pub struct Doctest {
pub unit: Unit,
pub args: Vec<OsString>,
pub unstable_opts: bool,
pub linker: Option<PathBuf>,
pub script_meta: Option<Metadata>,
pub env: HashMap<String, OsString>,
}
#[derive(Ord, PartialOrd, Eq, PartialEq)]
pub struct UnitOutput {
pub unit: Unit,
pub path: PathBuf,
pub script_meta: Option<Metadata>,
}
pub struct Compilation<'gctx> {
pub tests: Vec<UnitOutput>,
pub binaries: Vec<UnitOutput>,
pub cdylibs: Vec<UnitOutput>,
pub root_crate_names: Vec<String>,
pub native_dirs: BTreeSet<PathBuf>,
pub root_output: HashMap<CompileKind, PathBuf>,
pub deps_output: HashMap<CompileKind, PathBuf>,
sysroot_target_libdir: HashMap<CompileKind, PathBuf>,
pub extra_env: HashMap<Metadata, Vec<(String, String)>>,
pub to_doc_test: Vec<Doctest>,
pub host: String,
gctx: &'gctx GlobalContext,
rustc_process: ProcessBuilder,
rustc_workspace_wrapper_process: ProcessBuilder,
primary_rustc_process: Option<ProcessBuilder>,
target_runners: HashMap<CompileKind, Option<(PathBuf, Vec<String>)>>,
target_linkers: HashMap<CompileKind, Option<PathBuf>>,
}
impl<'gctx> Compilation<'gctx> {
pub fn new<'a>(bcx: &BuildContext<'a, 'gctx>) -> CargoResult<Compilation<'gctx>> {
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.gctx.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 {
native_dirs: BTreeSet::new(),
root_output: HashMap::new(),
deps_output: HashMap::new(),
sysroot_target_libdir: get_sysroot_target_libdir(bcx)?,
tests: Vec::new(),
binaries: Vec::new(),
cdylibs: Vec::new(),
root_crate_names: Vec::new(),
extra_env: HashMap::new(),
to_doc_test: Vec::new(),
gctx: bcx.gctx,
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::<CargoResult<HashMap<_, _>>>()?,
target_linkers: bcx
.build_config
.requested_kinds
.iter()
.chain(Some(&CompileKind::Host))
.map(|kind| Ok((*kind, target_linker(bcx, *kind)?)))
.collect::<CargoResult<HashMap<_, _>>>()?,
})
}
pub fn rustc_process(
&self,
unit: &Unit,
is_primary: bool,
is_workspace: bool,
) -> CargoResult<ProcessBuilder> {
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, ToolKind::Rustc)
}
pub fn rustdoc_process(
&self,
unit: &Unit,
script_meta: Option<Metadata>,
) -> CargoResult<ProcessBuilder> {
let rustdoc = ProcessBuilder::new(&*self.gctx.rustdoc()?);
let cmd = fill_rustc_tool_env(rustdoc, unit);
let mut cmd = self.fill_env(cmd, &unit.pkg, script_meta, unit.kind, ToolKind::Rustdoc)?;
cmd.retry_with_argfile(true);
unit.target.edition().cmd_edition_arg(&mut cmd);
for crate_type in unit.target.rustc_crate_types() {
cmd.arg("--crate-type").arg(crate_type.as_str());
}
Ok(cmd)
}
pub fn host_process<T: AsRef<OsStr>>(
&self,
cmd: T,
pkg: &Package,
) -> CargoResult<ProcessBuilder> {
self.fill_env(
ProcessBuilder::new(cmd),
pkg,
None,
CompileKind::Host,
ToolKind::HostProcess,
)
}
pub fn target_runner(&self, kind: CompileKind) -> Option<&(PathBuf, Vec<String>)> {
self.target_runners.get(&kind).and_then(|x| x.as_ref())
}
pub fn target_linker(&self, kind: CompileKind) -> Option<PathBuf> {
self.target_linkers.get(&kind).and_then(|x| x.clone())
}
pub fn target_process<T: AsRef<OsStr>>(
&self,
cmd: T,
kind: CompileKind,
pkg: &Package,
script_meta: Option<Metadata>,
) -> CargoResult<ProcessBuilder> {
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)
};
let tool_kind = ToolKind::TargetProcess;
let mut builder = self.fill_env(builder, pkg, script_meta, kind, tool_kind)?;
if let Some(client) = self.gctx.jobserver_from_env() {
builder.inherit_jobserver(client);
}
Ok(builder)
}
fn fill_env(
&self,
mut cmd: ProcessBuilder,
pkg: &Package,
script_meta: Option<Metadata>,
kind: CompileKind,
tool_kind: ToolKind,
) -> CargoResult<ProcessBuilder> {
let mut search_path = Vec::new();
if tool_kind.is_rustc_tool() {
if matches!(tool_kind, ToolKind::Rustdoc) {
search_path.extend(super::filter_dynamic_search_path(
self.native_dirs.iter(),
&self.root_output[&CompileKind::Host],
));
}
search_path.push(self.deps_output[&CompileKind::Host].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());
if self.gctx.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();
if dylib_path.starts_with(&search_path) {
search_path = dylib_path;
} else {
search_path.extend(dylib_path.into_iter());
}
if cfg!(target_os = "macos") && dylib_path_is_empty {
if let Some(home) = self.gctx.get_env_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.gctx.cargo_exe()?;
cmd.env(crate::CARGO_ENV, cargo_exe);
let rust_version = pkg.rust_version().as_ref().map(ToString::to_string);
cmd.env("CARGO_MANIFEST_DIR", pkg.root())
.env("CARGO_MANIFEST_PATH", pkg.manifest_path())
.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(":"))
.env(
"CARGO_PKG_RUST_VERSION",
&rust_version.as_deref().unwrap_or_default(),
)
.env(
"CARGO_PKG_README",
metadata.readme.as_ref().unwrap_or(&String::new()),
)
.cwd(pkg.root());
apply_env_config(self.gctx, &mut cmd)?;
Ok(cmd)
}
}
fn fill_rustc_tool_env(mut cmd: ProcessBuilder, unit: &Unit) -> ProcessBuilder {
if unit.target.is_executable() {
let name = unit
.target
.binary_filename()
.unwrap_or(unit.target.name().to_string());
cmd.env("CARGO_BIN_NAME", name);
}
cmd.env("CARGO_CRATE_NAME", unit.target.crate_name());
cmd
}
fn get_sysroot_target_libdir(
bcx: &BuildContext<'_, '_>,
) -> CargoResult<HashMap<CompileKind, PathBuf>> {
bcx.all_kinds
.iter()
.map(|&kind| {
let Some(info) = bcx.target_data.get_info(kind) else {
let target = match kind {
CompileKind::Host => "host".to_owned(),
CompileKind::Target(s) => s.short_name().to_owned(),
};
let dependency = bcx
.unit_graph
.iter()
.find_map(|(u, _)| (u.kind == kind).then_some(u.pkg.summary().package_id()))
.unwrap();
anyhow::bail!(
"could not find specification for target `{target}`.\n \
Dependency `{dependency}` requires to build for target `{target}`."
)
};
Ok((kind, info.sysroot_target_libdir.clone()))
})
.collect()
}
fn target_runner(
bcx: &BuildContext<'_, '_>,
kind: CompileKind,
) -> CargoResult<Option<(PathBuf, Vec<String>)>> {
let target = bcx.target_data.short_name(&kind);
let key = format!("target.{}.runner", target);
if let Some(v) = bcx.gctx.get::<Option<context::PathAndArgs>>(&key)? {
let path = v.path.resolve_program(bcx.gctx);
return Ok(Some((path, v.args)));
}
let target_cfg = bcx.target_data.info(kind).cfg();
let mut cfgs = bcx
.gctx
.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 configurations\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.gctx),
runner.val.args.clone(),
)
}))
}
fn target_linker(bcx: &BuildContext<'_, '_>, kind: CompileKind) -> CargoResult<Option<PathBuf>> {
if let Some(path) = bcx
.target_data
.target_config(kind)
.linker
.as_ref()
.map(|l| l.val.clone().resolve_program(bcx.gctx))
{
return Ok(Some(path));
}
let target_cfg = bcx.target_data.info(kind).cfg();
let mut cfgs = bcx
.gctx
.target_cfgs()?
.iter()
.filter_map(|(key, cfg)| cfg.linker.as_ref().map(|linker| (key, linker)))
.filter(|(key, _linker)| CfgExpr::matches_key(key, target_cfg));
let matching_linker = cfgs.next();
if let Some((key, linker)) = cfgs.next() {
anyhow::bail!(
"several matching instances of `target.'cfg(..)'.linker` in configurations\n\
first match `{}` located in {}\n\
second match `{}` located in {}",
matching_linker.unwrap().0,
matching_linker.unwrap().1.definition,
key,
linker.definition
);
}
Ok(matching_linker.map(|(_k, linker)| linker.val.clone().resolve_program(bcx.gctx)))
}