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
37type RemapPair = (PathBuf, String);
39
40pub(crate) fn trim_paths_args_rustdoc(
42 cmd: &mut ProcessBuilder,
43 build_runner: &BuildRunner<'_, '_>,
44 unit: &Unit,
45 trim_paths: &TomlTrimPaths,
46) -> CargoResult<()> {
47 match trim_paths {
48 TomlTrimPaths::Values(values) if !values.contains(&TomlTrimPathsValue::Diagnostics) => {
50 return Ok(());
51 }
52 _ => {}
53 }
54
55 for pair in trim_paths_remap(build_runner, unit) {
56 let mut arg = OsString::from("--remap-path-prefix=");
57 arg.push(pair);
58 cmd.arg(arg);
59 }
60
61 Ok(())
62}
63
64pub(crate) fn trim_paths_args(
70 cmd: &mut ProcessBuilder,
71 build_runner: &BuildRunner<'_, '_>,
72 unit: &Unit,
73 trim_paths: &TomlTrimPaths,
74) -> CargoResult<()> {
75 if trim_paths.is_none() {
76 return Ok(());
77 }
78
79 cmd.arg(format!("--remap-path-scope={trim_paths}"));
81
82 for pair in trim_paths_remap(build_runner, unit) {
83 let mut arg = OsString::from("--remap-path-prefix=");
84 arg.push(pair);
85 cmd.arg(arg);
86 }
87
88 Ok(())
89}
90
91pub(crate) fn trim_paths_remap(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> Vec<OsString> {
110 let mut remaps = Vec::with_capacity(4);
111 remaps.extend(
112 package_remap(build_runner, unit)
113 .into_iter()
114 .map(join_remap),
115 );
116 remaps.push(join_remap(build_dir_remap(build_runner)));
117 remaps.push(join_remap(sysroot_remap(build_runner, unit)));
118 remaps
119}
120
121fn join_remap((from, to): RemapPair) -> OsString {
122 let mut remap = OsString::with_capacity(from.as_os_str().len() + 1 + to.len());
123 remap.push(from);
124 remap.push("=");
125 remap.push(to);
126 remap
127}
128
129fn sysroot_remap(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> RemapPair {
134 let mut sysroot = build_runner.bcx.target_data.info(unit.kind).sysroot.clone();
136 sysroot.push("lib");
137 sysroot.push("rustlib");
138 sysroot.push("src");
139 sysroot.push("rust");
140
141 let rustc = build_runner.bcx.rustc();
142 let to = match rustc.commit_hash.as_ref() {
143 Some(commit_hash) => format!("/rustc/{commit_hash}"),
144 None => format!("/rustc/{}", rustc.version),
145 };
146 (sysroot, to)
147}
148
149fn package_remap(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> Vec<RemapPair> {
151 let pkg_root = unit.pkg.root();
152 let ws_root = build_runner.bcx.ws.root();
153 let source_id = unit.pkg.package_id().source_id();
154
155 if source_id.is_git() {
156 if let Some((from, rev)) = git_checkout(build_runner, pkg_root) {
157 const GIT_OID_LEN: usize = 7; let repo = hex::short_hash(source_id.canonical_url());
159 let rev = &rev[..rev.len().min(GIT_OID_LEN)];
160 return vec![(from.to_path_buf(), format!("/cargo/git/{repo}/{rev}"))];
161 }
162 } else if source_id.is_registry() {
163 let registry_src = build_runner.bcx.gctx.registry_source_path();
164 let registry_src = registry_src.as_path_unlocked();
165 let from = pkg_root.parent().unwrap();
166 if from.starts_with(registry_src) {
167 let registry = hex::short_hash(&source_id);
168 return vec![(from.to_path_buf(), format!("/cargo/registry/{registry}"))];
169 }
170 }
171
172 if pkg_root.strip_prefix(ws_root).is_ok() {
174 workspace_remap(build_runner, unit)
175 } else {
176 let from = pkg_root.to_path_buf();
177 let to = format!("/cargo/deps/{}-{}", unit.pkg.name(), unit.pkg.version());
178 vec![(from, to)]
179 }
180}
181
182fn workspace_remap(build_runner: &BuildRunner<'_, '_>, unit: &Unit) -> Vec<RemapPair> {
184 let ws_root = build_runner.bcx.ws.root();
185 let (src, rustc_workdir) = path_args(build_runner.bcx.ws, unit);
191
192 let custom_prefix = build_runner
193 .bcx
194 .gctx
195 .get_env(WS_REMAP_ENV)
196 .ok()
197 .filter(|prefix| !prefix.is_empty());
198
199 let relative_remap = if let Some(prefix) = custom_prefix
205 && src.is_relative()
206 && let Ok(rel) = unit.pkg.root().strip_prefix(&rustc_workdir)
207 && !rel.as_os_str().is_empty()
208 {
209 let mut rel_to = prefix.to_owned();
210 for comp in rel.components() {
211 rel_to.push('/');
213 rel_to.push_str(&comp.as_os_str().to_string_lossy());
214 }
215 Some((rel.to_path_buf(), rel_to))
216 } else {
217 None
218 };
219
220 let from = if ws_root.starts_with(&rustc_workdir) {
221 rustc_workdir
222 } else {
223 ws_root.to_path_buf()
224 };
225
226 let absolute_remap = (from, custom_prefix.unwrap_or(".").to_owned());
227
228 let mut remaps = vec![absolute_remap];
229 remaps.extend(relative_remap);
230 remaps
231}
232
233fn git_checkout<'a>(
240 build_runner: &BuildRunner<'_, '_>,
241 pkg_root: &'a Path,
242) -> Option<(&'a Path, &'a str)> {
243 let checkouts = build_runner.bcx.gctx.git_checkouts_path();
244 let checkouts = checkouts.as_path_unlocked();
245 let rel = pkg_root.strip_prefix(checkouts).ok()?;
246 let mut components = rel.components();
247 let (_repo, rev) = (components.next()?, components.next()?);
248 let rev = rev.as_os_str().to_str()?;
249 let checkout_root = pkg_root.ancestors().nth(components.count())?;
250 Some((checkout_root, rev))
251}
252
253fn build_dir_remap(build_runner: &BuildRunner<'_, '_>) -> RemapPair {
266 let from = build_runner.bcx.ws.build_dir().into_path_unlocked();
267 let to = "/cargo/build-dir".to_owned();
268 (from, to)
269}
270
271#[derive(Serialize)]
272#[serde(rename_all = "snake_case")]
273struct UnremapVersion {
274 v: u8,
275}
276
277#[derive(Serialize)]
278#[serde(rename_all = "snake_case")]
279struct UnremapMetadata<'a> {
280 rust_version: &'a str,
281 workspace_root: &'a Path,
282}
283
284#[derive(Serialize)]
285#[serde(rename_all = "snake_case")]
286struct Remap<'a> {
287 from: &'a str,
288 to: &'a Path,
289}
290
291pub(crate) fn should_emit_unremap_file(unit: &Unit) -> bool {
293 if !unit.profile.debuginfo.is_turned_on() {
296 return false;
297 }
298
299 match unit.profile.trim_paths.as_ref() {
300 None => false,
301 Some(TomlTrimPaths::All) => true,
302 Some(TomlTrimPaths::Values(values)) => values.contains(&TomlTrimPathsValue::Object),
303 }
304}
305
306pub(crate) fn write_unremap_file(
308 mut out: impl Write,
309 build_runner: &BuildRunner<'_, '_>,
310 unit: &Unit,
311) -> CargoResult<()> {
312 let mut remaps = BTreeMap::new();
313
314 let mut insert = |(from, to): RemapPair| match remaps.entry(to) {
315 Entry::Vacant(entry) => {
316 entry.insert(from);
317 }
318 Entry::Occupied(entry) if *entry.get() != from => {
319 debug!(
320 "conflicting unremap records for `{}`: `{}` and `{}`",
321 entry.key(),
322 entry.get().display(),
323 from.display(),
324 );
325 }
326 Entry::Occupied(_) => {}
327 };
328
329 insert(sysroot_remap(build_runner, unit));
330 insert(build_dir_remap(build_runner));
331
332 let mut seen = HashSet::default();
333 let mut stack = vec![unit.clone()];
334 while let Some(unit) = stack.pop() {
335 if !seen.insert(unit.clone()) {
336 continue;
337 }
338 for dep in build_runner.unit_deps(&unit) {
339 stack.push(dep.unit.clone());
340 }
341 for (from, to) in package_remap(build_runner, &unit) {
342 if from.is_relative() {
345 continue;
346 }
347 insert((from, to));
348 }
349 }
350
351 serde_json::to_writer(
352 &mut out,
353 &UnremapVersion {
354 v: CURRENT_UNREMAP_VERSION,
355 },
356 )?;
357 out.write_all(b"\n")?;
358 let rust_version = build_runner.bcx.rustc().version.to_string();
359 let metadata = UnremapMetadata {
360 rust_version: &rust_version,
361 workspace_root: build_runner.bcx.ws.root(),
362 };
363 serde_json::to_writer(&mut out, &metadata)?;
364 out.write_all(b"\n")?;
365 for (from, to) in &remaps {
366 serde_json::to_writer(&mut out, &Remap { from, to })?;
367 out.write_all(b"\n")?;
368 }
369
370 Ok(())
371}
372
373pub(crate) fn append_unremap_suffix(link: &PathBuf) -> PathBuf {
375 let mut link_buf = link.clone().into_os_string();
376 link_buf.push(UNREMAP_SUFFIX);
377 PathBuf::from(link_buf)
378}