cargo/compiler/
trim_paths.rs1use std::collections::BTreeMap;
6use std::collections::btree_map::Entry;
7use std::ffi::OsString;
8use std::io::Write;
9use std::path::Path;
10use std::path::PathBuf;
11
12use cargo_util::ProcessBuilder;
13use cargo_util_schemas::manifest::TomlTrimPaths;
14use cargo_util_schemas::manifest::TomlTrimPathsValue;
15use serde::Serialize;
16use tracing::debug;
17
18use super::BuildRunner;
19use super::Unit;
20use crate::util::data_structures::HashSet;
21use crate::util::errors::CargoResult;
22use crate::util::hex;
23use crate::util::path_args;
24
25const CURRENT_UNREMAP_VERSION: u8 = 1;
27
28pub(crate) const UNREMAP_SUFFIX: &str = ".trim-paths.jsonl";
30
31pub(crate) const WS_REMAP_ENV: &str = "__CARGO_RUSTC_BOOTSTRAP_WS_REMAP";
36
37pub(crate) fn trim_paths_args_rustdoc(
39 cmd: &mut ProcessBuilder,
40 build_runner: &BuildRunner<'_, '_>,
41 unit: &Unit,
42 trim_paths: &TomlTrimPaths,
43) -> CargoResult<()> {
44 match trim_paths {
45 TomlTrimPaths::Values(values) if !values.contains(&TomlTrimPathsValue::Diagnostics) => {
47 return Ok(());
48 }
49 _ => {}
50 }
51
52 for pair in trim_paths_remap(build_runner, unit) {
53 let mut arg = OsString::from("--remap-path-prefix=");
54 arg.push(pair);
55 cmd.arg(arg);
56 }
57
58 Ok(())
59}
60
61pub(crate) fn trim_paths_args(
67 cmd: &mut ProcessBuilder,
68 build_runner: &BuildRunner<'_, '_>,
69 unit: &Unit,
70 trim_paths: &TomlTrimPaths,
71) -> CargoResult<()> {
72 if trim_paths.is_none() {
73 return Ok(());
74 }
75
76 cmd.arg(format!("--remap-path-scope={trim_paths}"));
78
79 for pair in trim_paths_remap(build_runner, unit) {
80 let mut arg = OsString::from("--remap-path-prefix=");
81 arg.push(pair);
82 cmd.arg(arg);
83 }
84
85 Ok(())
86}
87
88pub(crate) fn trim_paths_remap(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> [OsString; 3] {
107 [
108 join_remap(package_remap(build_runner, unit)),
109 join_remap(build_dir_remap(build_runner)),
110 join_remap(sysroot_remap(build_runner)),
111 ]
112}
113
114fn join_remap((from, to): (PathBuf, String)) -> OsString {
115 let mut remap = OsString::with_capacity(from.as_os_str().len() + 1 + to.len());
116 remap.push(from);
117 remap.push("=");
118 remap.push(to);
119 remap
120}
121
122fn sysroot_remap(build_runner: &BuildRunner<'_, '_>) -> (PathBuf, String) {
127 let sysroot = build_runner
129 .bcx
130 .get_sysroot()
131 .join("lib")
132 .join("rustlib")
133 .join("src")
134 .join("rust");
135
136 let rustc = build_runner.bcx.rustc();
137 let to = match rustc.commit_hash.as_ref() {
138 Some(commit_hash) => format!("/rustc/{commit_hash}"),
139 None => format!("/rustc/{}", rustc.version),
140 };
141 (sysroot, to)
142}
143
144fn package_remap(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> (PathBuf, String) {
146 let pkg_root = unit.pkg.root();
147 let ws_root = build_runner.bcx.ws.root();
148 let source_id = unit.pkg.package_id().source_id();
149
150 if source_id.is_git() {
151 if let Some((from, rev)) = git_checkout(build_runner, pkg_root) {
152 const GIT_OID_LEN: usize = 7; let repo = hex::short_hash(source_id.canonical_url());
154 let rev = &rev[..rev.len().min(GIT_OID_LEN)];
155 return (from.to_path_buf(), format!("/cargo/git/{repo}/{rev}"));
156 }
157 } else if source_id.is_registry() {
158 let registry_src = build_runner.bcx.gctx.registry_source_path();
159 let registry_src = registry_src.as_path_unlocked();
160 let from = pkg_root.parent().unwrap();
161 if from.starts_with(registry_src) {
162 let registry = hex::short_hash(&source_id);
163 return (from.to_path_buf(), format!("/cargo/registry/{registry}"));
164 }
165 }
166
167 if pkg_root.strip_prefix(ws_root).is_ok() {
169 workspace_remap(build_runner, unit)
170 } else {
171 let from = pkg_root.to_path_buf();
172 let to = format!("/cargo/deps/{}-{}", unit.pkg.name(), unit.pkg.version());
173 (from, to)
174 }
175}
176
177fn workspace_remap(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> (PathBuf, String) {
179 let ws_root = build_runner.bcx.ws.root();
180 let (_, rustc_workdir) = path_args(build_runner.bcx.ws, unit);
186 let from = if ws_root.starts_with(&rustc_workdir) {
187 rustc_workdir
188 } else {
189 ws_root.to_path_buf()
190 };
191 let to = build_runner
192 .bcx
193 .gctx
194 .get_env(WS_REMAP_ENV)
195 .ok()
196 .filter(|prefix| !prefix.is_empty())
197 .unwrap_or(".")
198 .to_owned();
199 (from, to)
200}
201
202fn git_checkout<'a>(
209 build_runner: &BuildRunner<'_, '_>,
210 pkg_root: &'a Path,
211) -> Option<(&'a Path, &'a str)> {
212 let checkouts = build_runner.bcx.gctx.git_checkouts_path();
213 let checkouts = checkouts.as_path_unlocked();
214 let rel = pkg_root.strip_prefix(checkouts).ok()?;
215 let mut components = rel.components();
216 let (_repo, rev) = (components.next()?, components.next()?);
217 let rev = rev.as_os_str().to_str()?;
218 let checkout_root = pkg_root.ancestors().nth(components.count())?;
219 Some((checkout_root, rev))
220}
221
222fn build_dir_remap(build_runner: &BuildRunner<'_, '_>) -> (PathBuf, String) {
235 let from = build_runner.bcx.ws.build_dir().into_path_unlocked();
236 let to = "/cargo/build-dir".to_owned();
237 (from, to)
238}
239
240#[derive(Serialize)]
241#[serde(rename_all = "snake_case")]
242struct UnremapVersion {
243 v: u8,
244}
245
246#[derive(Serialize)]
247#[serde(rename_all = "snake_case")]
248struct UnremapMetadata<'a> {
249 rust_version: &'a str,
250 workspace_root: &'a Path,
251}
252
253#[derive(Serialize)]
254#[serde(rename_all = "snake_case")]
255struct Remap<'a> {
256 from: &'a str,
257 to: &'a Path,
258}
259
260pub(crate) fn should_emit_unremap_file(unit: &Unit) -> bool {
262 if !unit.profile.debuginfo.is_turned_on() {
265 return false;
266 }
267
268 match unit.profile.trim_paths.as_ref() {
269 None => false,
270 Some(TomlTrimPaths::All) => true,
271 Some(TomlTrimPaths::Values(values)) => values.contains(&TomlTrimPathsValue::Object),
272 }
273}
274
275pub(crate) fn write_unremap_file(
277 mut out: impl Write,
278 build_runner: &BuildRunner<'_, '_>,
279 unit: &Unit,
280) -> CargoResult<()> {
281 let mut remaps = BTreeMap::new();
282
283 let mut insert = |(from, to): (PathBuf, String)| match remaps.entry(to) {
284 Entry::Vacant(entry) => {
285 entry.insert(from);
286 }
287 Entry::Occupied(entry) if *entry.get() != from => {
288 debug!(
289 "conflicting unremap records for `{}`: `{}` and `{}`",
290 entry.key(),
291 entry.get().display(),
292 from.display(),
293 );
294 }
295 Entry::Occupied(_) => {}
296 };
297
298 insert(sysroot_remap(build_runner));
299 insert(build_dir_remap(build_runner));
300
301 let mut seen = HashSet::default();
302 let mut stack = vec![unit.clone()];
303 while let Some(unit) = stack.pop() {
304 if !seen.insert(unit.clone()) {
305 continue;
306 }
307 for dep in build_runner.unit_deps(&unit) {
308 stack.push(dep.unit.clone());
309 }
310 insert(package_remap(build_runner, &unit));
311 }
312
313 serde_json::to_writer(
314 &mut out,
315 &UnremapVersion {
316 v: CURRENT_UNREMAP_VERSION,
317 },
318 )?;
319 out.write_all(b"\n")?;
320 let rust_version = build_runner.bcx.rustc().version.to_string();
321 let metadata = UnremapMetadata {
322 rust_version: &rust_version,
323 workspace_root: build_runner.bcx.ws.root(),
324 };
325 serde_json::to_writer(&mut out, &metadata)?;
326 out.write_all(b"\n")?;
327 for (from, to) in &remaps {
328 serde_json::to_writer(&mut out, &Remap { from, to })?;
329 out.write_all(b"\n")?;
330 }
331
332 Ok(())
333}
334
335pub(crate) fn append_unremap_suffix(link: &PathBuf) -> PathBuf {
337 let mut link_buf = link.clone().into_os_string();
338 link_buf.push(UNREMAP_SUFFIX);
339 PathBuf::from(link_buf)
340}