use std::collections::BTreeMap;
use std::collections::btree_map::Entry;
use std::ffi::OsString;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use cargo_util::ProcessBuilder;
use cargo_util_schemas::manifest::TomlTrimPaths;
use cargo_util_schemas::manifest::TomlTrimPathsValue;
use serde::Serialize;
use tracing::debug;
use super::BuildRunner;
use super::Unit;
use crate::util::data_structures::HashSet;
use crate::util::errors::CargoResult;
use crate::util::hex;
use crate::util::path_args;
const CURRENT_UNREMAP_VERSION: u8 = 1;
pub(crate) const UNREMAP_SUFFIX: &str = ".trim-paths.jsonl";
pub(crate) const WS_REMAP_ENV: &str = "__CARGO_RUSTC_BOOTSTRAP_WS_REMAP";
type RemapPair = (PathBuf, String);
pub(crate) fn trim_paths_args_rustdoc(
cmd: &mut ProcessBuilder,
build_runner: &BuildRunner<'_, '_>,
unit: &Unit,
trim_paths: &TomlTrimPaths,
) -> CargoResult<()> {
match trim_paths {
TomlTrimPaths::Values(values) if !values.contains(&TomlTrimPathsValue::Diagnostics) => {
return Ok(());
}
_ => {}
}
for pair in trim_paths_remap(build_runner, unit) {
let mut arg = OsString::from("--remap-path-prefix=");
arg.push(pair);
cmd.arg(arg);
}
Ok(())
}
pub(crate) fn trim_paths_args(
cmd: &mut ProcessBuilder,
build_runner: &BuildRunner<'_, '_>,
unit: &Unit,
trim_paths: &TomlTrimPaths,
) -> CargoResult<()> {
if trim_paths.is_none() {
return Ok(());
}
cmd.arg(format!("--remap-path-scope={trim_paths}"));
for pair in trim_paths_remap(build_runner, unit) {
let mut arg = OsString::from("--remap-path-prefix=");
arg.push(pair);
cmd.arg(arg);
}
Ok(())
}
pub(crate) fn trim_paths_remap(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> Vec<OsString> {
let mut remaps = Vec::with_capacity(4);
remaps.extend(
package_remap(build_runner, unit)
.into_iter()
.map(join_remap),
);
remaps.push(join_remap(build_dir_remap(build_runner)));
remaps.push(join_remap(sysroot_remap(build_runner, unit)));
remaps
}
fn join_remap((from, to): RemapPair) -> OsString {
let mut remap = OsString::with_capacity(from.as_os_str().len() + 1 + to.len());
remap.push(from);
remap.push("=");
remap.push(to);
remap
}
fn sysroot_remap(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> RemapPair {
let mut sysroot = build_runner.bcx.target_data.info(unit.kind).sysroot.clone();
sysroot.push("lib");
sysroot.push("rustlib");
sysroot.push("src");
sysroot.push("rust");
let rustc = build_runner.bcx.rustc();
let to = match rustc.commit_hash.as_ref() {
Some(commit_hash) => format!("/rustc/{commit_hash}"),
None => format!("/rustc/{}", rustc.version),
};
(sysroot, to)
}
fn package_remap(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> Vec<RemapPair> {
let pkg_root = unit.pkg.root();
let ws_root = build_runner.bcx.ws.root();
let source_id = unit.pkg.package_id().source_id();
if source_id.is_git() {
if let Some((from, rev)) = git_checkout(build_runner, pkg_root) {
const GIT_OID_LEN: usize = 7; let repo = hex::short_hash(source_id.canonical_url());
let rev = &rev[..rev.len().min(GIT_OID_LEN)];
return vec![(from.to_path_buf(), format!("/cargo/git/{repo}/{rev}"))];
}
} else if source_id.is_registry() {
let registry_src = build_runner.bcx.gctx.registry_source_path();
let registry_src = registry_src.as_path_unlocked();
let from = pkg_root.parent().unwrap();
if from.starts_with(registry_src) {
let registry = hex::short_hash(&source_id);
return vec![(from.to_path_buf(), format!("/cargo/registry/{registry}"))];
}
}
if pkg_root.strip_prefix(ws_root).is_ok() {
workspace_remap(build_runner, unit)
} else {
let from = pkg_root.to_path_buf();
let to = format!("/cargo/deps/{}-{}", unit.pkg.name(), unit.pkg.version());
vec![(from, to)]
}
}
fn workspace_remap(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> Vec<RemapPair> {
let ws_root = build_runner.bcx.ws.root();
let (src, rustc_workdir) = path_args(build_runner.bcx.ws, unit);
let custom_prefix = build_runner
.bcx
.gctx
.get_env(WS_REMAP_ENV)
.ok()
.filter(|prefix| !prefix.is_empty());
let relative_remap = if let Some(prefix) = custom_prefix
&& src.is_relative()
&& let Ok(rel) = unit.pkg.root().strip_prefix(&rustc_workdir)
&& !rel.as_os_str().is_empty()
{
let mut rel_to = prefix.to_owned();
for comp in rel.components() {
rel_to.push('/');
rel_to.push_str(&comp.as_os_str().to_string_lossy());
}
Some((rel.to_path_buf(), rel_to))
} else {
None
};
let from = if ws_root.starts_with(&rustc_workdir) {
rustc_workdir
} else {
ws_root.to_path_buf()
};
let absolute_remap = (from, custom_prefix.unwrap_or(".").to_owned());
let mut remaps = vec![absolute_remap];
remaps.extend(relative_remap);
remaps
}
fn git_checkout<'a>(
build_runner: &BuildRunner<'_, '_>,
pkg_root: &'a Path,
) -> Option<(&'a Path, &'a str)> {
let checkouts = build_runner.bcx.gctx.git_checkouts_path();
let checkouts = checkouts.as_path_unlocked();
let rel = pkg_root.strip_prefix(checkouts).ok()?;
let mut components = rel.components();
let (_repo, rev) = (components.next()?, components.next()?);
let rev = rev.as_os_str().to_str()?;
let checkout_root = pkg_root.ancestors().nth(components.count())?;
Some((checkout_root, rev))
}
fn build_dir_remap(build_runner: &BuildRunner<'_, '_>) -> RemapPair {
let from = build_runner.bcx.ws.build_dir().into_path_unlocked();
let to = "/cargo/build-dir".to_owned();
(from, to)
}
#[derive(Serialize)]
#[serde(rename_all = "snake_case")]
struct UnremapVersion {
v: u8,
}
#[derive(Serialize)]
#[serde(rename_all = "snake_case")]
struct UnremapMetadata<'a> {
rust_version: &'a str,
workspace_root: &'a Path,
}
#[derive(Serialize)]
#[serde(rename_all = "snake_case")]
struct Remap<'a> {
from: &'a str,
to: &'a Path,
}
pub(crate) fn should_emit_unremap_file(unit: &Unit) -> bool {
if !unit.profile.debuginfo.is_turned_on() {
return false;
}
match unit.profile.trim_paths.as_ref() {
None => false,
Some(TomlTrimPaths::All) => true,
Some(TomlTrimPaths::Values(values)) => values.contains(&TomlTrimPathsValue::Object),
}
}
pub(crate) fn write_unremap_file(
mut out: impl Write,
build_runner: &BuildRunner<'_, '_>,
unit: &Unit,
) -> CargoResult<()> {
let mut remaps = BTreeMap::new();
let mut insert = |(from, to): RemapPair| match remaps.entry(to) {
Entry::Vacant(entry) => {
entry.insert(from);
}
Entry::Occupied(entry) if *entry.get() != from => {
debug!(
"conflicting unremap records for `{}`: `{}` and `{}`",
entry.key(),
entry.get().display(),
from.display(),
);
}
Entry::Occupied(_) => {}
};
insert(sysroot_remap(build_runner, unit));
insert(build_dir_remap(build_runner));
let mut seen = HashSet::default();
let mut stack = vec![unit.clone()];
while let Some(unit) = stack.pop() {
if !seen.insert(unit.clone()) {
continue;
}
for dep in build_runner.unit_deps(&unit) {
stack.push(dep.unit.clone());
}
for (from, to) in package_remap(build_runner, &unit) {
if from.is_relative() {
continue;
}
insert((from, to));
}
}
serde_json::to_writer(
&mut out,
&UnremapVersion {
v: CURRENT_UNREMAP_VERSION,
},
)?;
out.write_all(b"\n")?;
let rust_version = build_runner.bcx.rustc().version.to_string();
let metadata = UnremapMetadata {
rust_version: &rust_version,
workspace_root: build_runner.bcx.ws.root(),
};
serde_json::to_writer(&mut out, &metadata)?;
out.write_all(b"\n")?;
for (from, to) in &remaps {
serde_json::to_writer(&mut out, &Remap { from, to })?;
out.write_all(b"\n")?;
}
Ok(())
}
pub(crate) fn append_unremap_suffix(link: &PathBuf) -> PathBuf {
let mut link_buf = link.clone().into_os_string();
link_buf.push(UNREMAP_SUFFIX);
PathBuf::from(link_buf)
}