use std::collections::HashMap;
use std::env;
use std::fmt;
use std::hash::{Hash, Hasher, SipHasher};
use std::path::{Path, PathBuf};
use std::sync::Arc;
use lazycell::LazyCell;
use log::info;
use super::{BuildContext, CompileKind, Context, FileFlavor, Layout};
use crate::core::compiler::{CompileMode, CompileTarget, Unit};
use crate::core::{Target, TargetKind, Workspace};
use crate::util::{self, CargoResult};
#[derive(Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)]
pub struct Metadata(u64);
impl fmt::Display for Metadata {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:016x}", self.0)
}
}
impl fmt::Debug for Metadata {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Metadata({:016x})", self.0)
}
}
pub struct CompilationFiles<'a, 'cfg> {
pub(super) host: Layout,
pub(super) target: HashMap<CompileTarget, Layout>,
export_dir: Option<PathBuf>,
roots: Vec<Unit<'a>>,
ws: &'a Workspace<'cfg>,
metas: HashMap<Unit<'a>, Option<Metadata>>,
outputs: HashMap<Unit<'a>, LazyCell<Arc<Vec<OutputFile>>>>,
}
#[derive(Debug)]
pub struct OutputFile {
pub path: PathBuf,
pub hardlink: Option<PathBuf>,
pub export_path: Option<PathBuf>,
pub flavor: FileFlavor,
}
impl OutputFile {
pub fn bin_dst(&self) -> &PathBuf {
match self.hardlink {
Some(ref link_dst) => link_dst,
None => &self.path,
}
}
}
impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> {
pub(super) fn new(
roots: &[Unit<'a>],
host: Layout,
target: HashMap<CompileTarget, Layout>,
export_dir: Option<PathBuf>,
ws: &'a Workspace<'cfg>,
cx: &Context<'a, 'cfg>,
) -> CompilationFiles<'a, 'cfg> {
let mut metas = HashMap::new();
for unit in roots {
metadata_of(unit, cx, &mut metas);
}
let outputs = metas
.keys()
.cloned()
.map(|unit| (unit, LazyCell::new()))
.collect();
CompilationFiles {
ws,
host,
target,
export_dir,
roots: roots.to_vec(),
metas,
outputs,
}
}
pub fn layout(&self, kind: CompileKind) -> &Layout {
match kind {
CompileKind::Host => &self.host,
CompileKind::Target(target) => &self.target[&target],
}
}
pub fn metadata(&self, unit: &Unit<'a>) -> Option<Metadata> {
self.metas[unit]
}
pub fn target_short_hash(&self, unit: &Unit<'_>) -> String {
let hashable = unit.pkg.package_id().stable_hash(self.ws.root());
util::short_hash(&hashable)
}
pub fn out_dir(&self, unit: &Unit<'a>) -> PathBuf {
if unit.mode.is_doc() {
self.layout(unit.kind).doc().to_path_buf()
} else if unit.mode.is_doc_test() {
panic!("doc tests do not have an out dir");
} else if unit.target.is_custom_build() {
self.build_script_dir(unit)
} else if unit.target.is_example() {
self.layout(unit.kind).examples().to_path_buf()
} else {
self.deps_dir(unit).to_path_buf()
}
}
pub fn export_dir(&self) -> Option<PathBuf> {
self.export_dir.clone()
}
pub fn pkg_dir(&self, unit: &Unit<'a>) -> String {
let name = unit.pkg.package_id().name();
match self.metas[unit] {
Some(ref meta) => format!("{}-{}", name, meta),
None => format!("{}-{}", name, self.target_short_hash(unit)),
}
}
pub fn host_root(&self) -> &Path {
self.host.dest()
}
pub fn host_deps(&self) -> &Path {
self.host.deps()
}
pub fn deps_dir(&self, unit: &Unit<'_>) -> &Path {
self.layout(unit.kind).deps()
}
pub fn fingerprint_dir(&self, unit: &Unit<'a>) -> PathBuf {
let dir = self.pkg_dir(unit);
self.layout(unit.kind).fingerprint().join(dir)
}
pub fn message_cache_path(&self, unit: &Unit<'a>) -> PathBuf {
self.fingerprint_dir(unit).join("output")
}
pub fn build_script_dir(&self, unit: &Unit<'a>) -> PathBuf {
assert!(unit.target.is_custom_build());
assert!(!unit.mode.is_run_custom_build());
assert!(self.metas.contains_key(unit));
let dir = self.pkg_dir(unit);
self.layout(CompileKind::Host).build().join(dir)
}
pub fn build_script_run_dir(&self, unit: &Unit<'a>) -> PathBuf {
assert!(unit.target.is_custom_build());
assert!(unit.mode.is_run_custom_build());
let dir = self.pkg_dir(unit);
self.layout(unit.kind).build().join(dir)
}
pub fn build_script_out_dir(&self, unit: &Unit<'a>) -> PathBuf {
self.build_script_run_dir(unit).join("out")
}
pub fn file_stem(&self, unit: &Unit<'a>) -> String {
match self.metas[unit] {
Some(ref metadata) => format!("{}-{}", unit.target.crate_name(), metadata),
None => self.bin_stem(unit),
}
}
pub fn bin_link_for_target(
&self,
target: &Target,
kind: CompileKind,
bcx: &BuildContext<'_, '_>,
) -> CargoResult<PathBuf> {
assert!(target.is_bin());
let dest = self.layout(kind).dest();
let info = bcx.target_data.info(kind);
let file_types = info
.file_types(
"bin",
FileFlavor::Normal,
&TargetKind::Bin,
bcx.target_data.short_name(&kind),
)?
.expect("target must support `bin`");
let file_type = file_types
.iter()
.find(|file_type| file_type.flavor == FileFlavor::Normal)
.expect("target must support `bin`");
Ok(dest.join(file_type.filename(target.name())))
}
pub(super) fn outputs(
&self,
unit: &Unit<'a>,
bcx: &BuildContext<'a, 'cfg>,
) -> CargoResult<Arc<Vec<OutputFile>>> {
self.outputs[unit]
.try_borrow_with(|| self.calc_outputs(unit, bcx))
.map(Arc::clone)
}
fn bin_stem(&self, unit: &Unit<'_>) -> String {
if unit.target.allows_underscores() {
unit.target.name().to_string()
} else {
unit.target.crate_name()
}
}
fn link_stem(&self, unit: &Unit<'a>) -> Option<(PathBuf, String)> {
let out_dir = self.out_dir(unit);
let bin_stem = self.bin_stem(unit); let file_stem = self.file_stem(unit);
if out_dir.ends_with("deps") {
if unit.target.is_bin() || self.roots.contains(unit) {
Some((
out_dir.parent().unwrap().to_owned(),
if unit.mode.is_any_test() {
file_stem
} else {
bin_stem
},
))
} else {
None
}
} else if bin_stem == file_stem {
None
} else if out_dir.ends_with("examples") || out_dir.parent().unwrap().ends_with("build") {
Some((out_dir, bin_stem))
} else {
None
}
}
fn calc_outputs(
&self,
unit: &Unit<'a>,
bcx: &BuildContext<'a, 'cfg>,
) -> CargoResult<Arc<Vec<OutputFile>>> {
let ret = match unit.mode {
CompileMode::Check { .. } => {
let file_stem = self.file_stem(unit);
let path = self.out_dir(unit).join(format!("lib{}.rmeta", file_stem));
vec![OutputFile {
path,
hardlink: None,
export_path: None,
flavor: FileFlavor::Linkable { rmeta: false },
}]
}
CompileMode::Doc { .. } => {
let path = self
.out_dir(unit)
.join(unit.target.crate_name())
.join("index.html");
vec![OutputFile {
path,
hardlink: None,
export_path: None,
flavor: FileFlavor::Normal,
}]
}
CompileMode::RunCustomBuild => {
vec![]
}
CompileMode::Doctest => {
vec![]
}
CompileMode::Test | CompileMode::Build | CompileMode::Bench => {
self.calc_outputs_rustc(unit, bcx)?
}
};
info!("Target filenames: {:?}", ret);
Ok(Arc::new(ret))
}
fn calc_outputs_rustc(
&self,
unit: &Unit<'a>,
bcx: &BuildContext<'a, 'cfg>,
) -> CargoResult<Vec<OutputFile>> {
let mut ret = Vec::new();
let mut unsupported = Vec::new();
let out_dir = self.out_dir(unit);
let link_stem = self.link_stem(unit);
let info = bcx.target_data.info(unit.kind);
let file_stem = self.file_stem(unit);
let mut add = |crate_type: &str, flavor: FileFlavor| -> CargoResult<()> {
let crate_type = if crate_type == "lib" {
"rlib"
} else {
crate_type
};
let file_types = info.file_types(
crate_type,
flavor,
unit.target.kind(),
bcx.target_data.short_name(&unit.kind),
)?;
match file_types {
Some(types) => {
for file_type in types {
let path = out_dir.join(file_type.filename(&file_stem));
let hardlink = if unit.mode.is_any_test() {
None
} else {
link_stem
.as_ref()
.map(|&(ref ld, ref ls)| ld.join(file_type.filename(ls)))
};
let export_path = if unit.target.is_custom_build() {
None
} else {
self.export_dir.as_ref().and_then(|export_dir| {
hardlink
.as_ref()
.map(|hardlink| export_dir.join(hardlink.file_name().unwrap()))
})
};
ret.push(OutputFile {
path,
hardlink,
export_path,
flavor: file_type.flavor,
});
}
}
None => {
unsupported.push(crate_type.to_string());
}
}
Ok(())
};
match *unit.target.kind() {
TargetKind::Bin
| TargetKind::CustomBuild
| TargetKind::ExampleBin
| TargetKind::Bench
| TargetKind::Test => {
add("bin", FileFlavor::Normal)?;
}
TargetKind::Lib(..) | TargetKind::ExampleLib(..) if unit.mode.is_any_test() => {
add("bin", FileFlavor::Normal)?;
}
TargetKind::ExampleLib(ref kinds) | TargetKind::Lib(ref kinds) => {
for kind in kinds {
add(
kind.crate_type(),
if kind.linkable() {
FileFlavor::Linkable { rmeta: false }
} else {
FileFlavor::Normal
},
)?;
}
let path = out_dir.join(format!("lib{}.rmeta", file_stem));
if !unit.requires_upstream_objects() {
ret.push(OutputFile {
path,
hardlink: None,
export_path: None,
flavor: FileFlavor::Linkable { rmeta: true },
});
}
}
}
if ret.is_empty() {
if !unsupported.is_empty() {
anyhow::bail!(
"cannot produce {} for `{}` as the target `{}` \
does not support these crate types",
unsupported.join(", "),
unit.pkg,
bcx.target_data.short_name(&unit.kind),
)
}
anyhow::bail!(
"cannot compile `{}` as the target `{}` does not \
support any of the output crate types",
unit.pkg,
bcx.target_data.short_name(&unit.kind),
);
}
Ok(ret)
}
}
fn metadata_of<'a, 'cfg>(
unit: &Unit<'a>,
cx: &Context<'a, 'cfg>,
metas: &mut HashMap<Unit<'a>, Option<Metadata>>,
) -> Option<Metadata> {
if !metas.contains_key(unit) {
let meta = compute_metadata(unit, cx, metas);
metas.insert(*unit, meta);
for dep in cx.unit_deps(unit) {
metadata_of(&dep.unit, cx, metas);
}
}
metas[unit]
}
fn compute_metadata<'a, 'cfg>(
unit: &Unit<'a>,
cx: &Context<'a, 'cfg>,
metas: &mut HashMap<Unit<'a>, Option<Metadata>>,
) -> Option<Metadata> {
if unit.mode.is_doc_test() {
return None;
}
let bcx = &cx.bcx;
let __cargo_default_lib_metadata = env::var("__CARGO_DEFAULT_LIB_METADATA");
let short_name = bcx.target_data.short_name(&unit.kind);
if !(unit.mode.is_any_test() || unit.mode.is_check())
&& (unit.target.is_dylib()
|| unit.target.is_cdylib()
|| (unit.target.is_executable() && short_name.starts_with("wasm32-"))
|| (unit.target.is_executable() && short_name.contains("msvc")))
&& unit.pkg.package_id().source_id().is_path()
&& __cargo_default_lib_metadata.is_err()
{
return None;
}
let mut hasher = SipHasher::new();
1.hash(&mut hasher);
unit.pkg
.package_id()
.stable_hash(bcx.ws.root())
.hash(&mut hasher);
unit.features.hash(&mut hasher);
let mut deps_metadata = cx
.unit_deps(unit)
.iter()
.map(|dep| metadata_of(&dep.unit, cx, metas))
.collect::<Vec<_>>();
deps_metadata.sort();
deps_metadata.hash(&mut hasher);
unit.profile.hash(&mut hasher);
unit.mode.hash(&mut hasher);
unit.kind.hash(&mut hasher);
unit.target.name().hash(&mut hasher);
unit.target.kind().hash(&mut hasher);
bcx.rustc().verbose_version.hash(&mut hasher);
if cx.bcx.ws.is_member(unit.pkg) {
if let Some(path) = &cx.bcx.rustc().workspace_wrapper {
path.hash(&mut hasher);
}
}
if let Ok(ref channel) = __cargo_default_lib_metadata {
channel.hash(&mut hasher);
}
unit.is_std.hash(&mut hasher);
Some(Metadata(hasher.finish()))
}