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 serde::Serialize;
15use tracing::debug;
16
17use super::BuildRunner;
18use super::Unit;
19use crate::util::data_structures::HashSet;
20use crate::util::errors::CargoResult;
21use crate::util::hex;
22use crate::util::path_args;
23
24const CURRENT_UNREMAP_VERSION: u8 = 1;
26
27pub(crate) const UNREMAP_SUFFIX: &str = ".trim-paths.jsonl";
29
30pub(crate) const WS_REMAP_ENV: &str = "__CARGO_RUSTC_BOOTSTRAP_WS_REMAP";
35
36type RemapPair = (PathBuf, String);
38
39pub(crate) fn trim_paths_args_rustdoc(
41 cmd: &mut ProcessBuilder,
42 build_runner: &BuildRunner<'_, '_>,
43 unit: &Unit,
44 trim_paths: &TomlTrimPaths,
45) -> CargoResult<()> {
46 if !matches!(trim_paths, TomlTrimPaths::All) {
48 return Ok(());
49 }
50
51 for pair in trim_paths_remap(build_runner, unit) {
52 let mut arg = OsString::from("--remap-path-prefix=");
53 arg.push(pair);
54 cmd.arg(arg);
55 }
56
57 Ok(())
58}
59
60pub(crate) fn trim_paths_args(
66 cmd: &mut ProcessBuilder,
67 build_runner: &BuildRunner<'_, '_>,
68 unit: &Unit,
69 trim_paths: &TomlTrimPaths,
70) -> CargoResult<()> {
71 if trim_paths.is_none() {
72 return Ok(());
73 }
74
75 cmd.arg(format!("--remap-path-scope={trim_paths}"));
77
78 for pair in trim_paths_remap(build_runner, unit) {
79 let mut arg = OsString::from("--remap-path-prefix=");
80 arg.push(pair);
81 cmd.arg(arg);
82 }
83
84 Ok(())
85}
86
87pub(crate) fn trim_paths_remap(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> Vec<OsString> {
106 let mut remaps = Vec::with_capacity(4);
107 remaps.extend(
108 package_remap(build_runner, unit)
109 .into_iter()
110 .map(join_remap),
111 );
112 remaps.push(join_remap(build_dir_remap(build_runner)));
113 remaps.push(join_remap(sysroot_remap(build_runner, unit)));
114 remaps
115}
116
117fn join_remap((from, to): RemapPair) -> OsString {
118 let mut remap = OsString::with_capacity(from.as_os_str().len() + 1 + to.len());
119 remap.push(from);
120 remap.push("=");
121 remap.push(to);
122 remap
123}
124
125fn sysroot_remap(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> RemapPair {
130 let mut sysroot = build_runner.bcx.target_data.info(unit.kind).sysroot.clone();
132 sysroot.push("lib");
133 sysroot.push("rustlib");
134 sysroot.push("src");
135 sysroot.push("rust");
136
137 let rustc = build_runner.bcx.rustc();
138 let to = match rustc.commit_hash.as_ref() {
139 Some(commit_hash) => format!("/rustc/{commit_hash}"),
140 None => format!("/rustc/{}", rustc.version),
141 };
142 (sysroot, to)
143}
144
145fn package_remap(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> Vec<RemapPair> {
147 let pkg_root = unit.pkg.root();
148 let ws_root = build_runner.bcx.ws.root();
149 let source_id = unit.pkg.package_id().source_id();
150
151 if source_id.is_git() {
152 if let Some((from, rev)) = git_checkout(build_runner, pkg_root) {
153 const GIT_OID_LEN: usize = 7; let repo = hex::short_hash(source_id.canonical_url());
155 let rev = &rev[..rev.len().min(GIT_OID_LEN)];
156 return vec![(from.to_path_buf(), format!("/cargo/git/{repo}/{rev}"))];
157 }
158 } else if source_id.is_registry() {
159 let registry_src = build_runner.bcx.gctx.registry_source_path();
160 let registry_src = registry_src.as_path_unlocked();
161 let from = pkg_root.parent().unwrap();
162 if from.starts_with(registry_src) {
163 let registry = hex::short_hash(&source_id);
164 return vec![(from.to_path_buf(), format!("/cargo/registry/{registry}"))];
165 }
166 }
167
168 if pkg_root.strip_prefix(ws_root).is_ok() {
170 workspace_remap(build_runner, unit)
171 } else {
172 let from = pkg_root.to_path_buf();
173 let to = format!("/cargo/deps/{}-{}", unit.pkg.name(), unit.pkg.version());
174 vec![(from, to)]
175 }
176}
177
178fn workspace_remap(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> Vec<RemapPair> {
180 let ws_root = build_runner.bcx.ws.root();
181 let (src, rustc_workdir) = path_args(build_runner.bcx.ws, unit);
187
188 let custom_prefix = build_runner
189 .bcx
190 .gctx
191 .get_env(WS_REMAP_ENV)
192 .ok()
193 .filter(|prefix| !prefix.is_empty());
194
195 let relative_remap = if let Some(prefix) = custom_prefix
201 && src.is_relative()
202 && let Ok(rel) = unit.pkg.root().strip_prefix(&rustc_workdir)
203 && !rel.is_empty()
204 {
205 let mut rel_to = prefix.to_owned();
206 for comp in rel.components() {
207 rel_to.push('/');
209 rel_to.push_str(&comp.as_os_str().to_string_lossy());
210 }
211 Some((rel.to_path_buf(), rel_to))
212 } else {
213 None
214 };
215
216 let from = if ws_root.starts_with(&rustc_workdir) {
217 rustc_workdir
218 } else {
219 ws_root.to_path_buf()
220 };
221
222 let absolute_remap = (from, custom_prefix.unwrap_or(".").to_owned());
223
224 let mut remaps = vec![absolute_remap];
225 remaps.extend(relative_remap);
226 remaps
227}
228
229fn git_checkout<'a>(
236 build_runner: &BuildRunner<'_, '_>,
237 pkg_root: &'a Path,
238) -> Option<(&'a Path, &'a str)> {
239 let checkouts = build_runner.bcx.gctx.git_checkouts_path();
240 let checkouts = checkouts.as_path_unlocked();
241 let rel = pkg_root.strip_prefix(checkouts).ok()?;
242 let mut components = rel.components();
243 let (_repo, rev) = (components.next()?, components.next()?);
244 let rev = rev.as_os_str().to_str()?;
245 let checkout_root = pkg_root.ancestors().nth(components.count())?;
246 Some((checkout_root, rev))
247}
248
249fn build_dir_remap(build_runner: &BuildRunner<'_, '_>) -> RemapPair {
262 let from = build_runner.bcx.ws.build_dir().into_path_unlocked();
263 let to = "/cargo/build-dir".to_owned();
264 (from, to)
265}
266
267#[derive(Serialize)]
268#[serde(rename_all = "snake_case")]
269struct UnremapVersion {
270 v: u8,
271}
272
273#[derive(Serialize)]
274#[serde(rename_all = "snake_case")]
275struct UnremapMetadata<'a> {
276 rust_version: &'a str,
277 workspace_root: &'a Path,
278}
279
280#[derive(Serialize)]
281#[serde(rename_all = "snake_case")]
282struct Remap<'a> {
283 from: &'a str,
284 to: &'a Path,
285}
286
287pub(crate) fn should_emit_unremap_file(unit: &Unit) -> bool {
289 if !unit.profile.debuginfo.is_turned_on() {
292 return false;
293 }
294
295 match unit.profile.trim_paths.as_ref() {
296 None => false,
297 Some(TomlTrimPaths::None) => false,
298 Some(TomlTrimPaths::Object | TomlTrimPaths::All) => true,
299 }
300}
301
302pub(crate) fn write_unremap_file(
304 mut out: impl Write,
305 build_runner: &BuildRunner<'_, '_>,
306 unit: &Unit,
307) -> CargoResult<()> {
308 let mut remaps = BTreeMap::new();
309
310 let mut insert = |(from, to): RemapPair| match remaps.entry(to) {
311 Entry::Vacant(entry) => {
312 entry.insert(from);
313 }
314 Entry::Occupied(entry) if *entry.get() != from => {
315 debug!(
316 "conflicting unremap records for `{}`: `{}` and `{}`",
317 entry.key(),
318 entry.get().display(),
319 from.display(),
320 );
321 }
322 Entry::Occupied(_) => {}
323 };
324
325 insert(sysroot_remap(build_runner, unit));
326 insert(build_dir_remap(build_runner));
327
328 let mut seen = HashSet::default();
329 let mut stack = vec![unit.clone()];
330 while let Some(unit) = stack.pop() {
331 if !seen.insert(unit.clone()) {
332 continue;
333 }
334 for dep in build_runner.unit_deps(&unit) {
335 stack.push(dep.unit.clone());
336 }
337 for (from, to) in package_remap(build_runner, &unit) {
338 if from.is_relative() {
341 continue;
342 }
343 insert((from, to));
344 }
345 }
346
347 serde_json::to_writer(
348 &mut out,
349 &UnremapVersion {
350 v: CURRENT_UNREMAP_VERSION,
351 },
352 )?;
353 out.write_all(b"\n")?;
354 let rust_version = build_runner.bcx.rustc().version.to_string();
355 let metadata = UnremapMetadata {
356 rust_version: &rust_version,
357 workspace_root: build_runner.bcx.ws.root(),
358 };
359 serde_json::to_writer(&mut out, &metadata)?;
360 out.write_all(b"\n")?;
361 for (from, to) in &remaps {
362 serde_json::to_writer(&mut out, &Remap { from, to })?;
363 out.write_all(b"\n")?;
364 }
365
366 Ok(())
367}
368
369pub(crate) fn append_unremap_suffix(link: &PathBuf) -> PathBuf {
371 let mut link_buf = link.clone().into_os_string();
372 link_buf.push(UNREMAP_SUFFIX);
373 PathBuf::from(link_buf)
374}