anchor_cli/debugger/rustc_wrapper.rs
1//! RUSTC_WRAPPER shim that restores absolute paths in SBF DWARF output.
2//!
3//! ## Problem
4//!
5//! The Solana toolchain's cargo passes `-Zremap-cwd-prefix=` (empty
6//! replacement) to rustc for every SBF crate. This strips `DW_AT_comp_dir`
7//! from the DWARF, making all source paths relative. When multiple crates
8//! share filenames like `src/lib.rs`, the debugger can't tell them apart
9//! and may show source from the wrong crate.
10//!
11//! ## Solution
12//!
13//! `anchor debugger` sets `RUSTC_WRAPPER` to the `anchor` binary itself.
14//! Cargo then invokes `anchor <real-rustc> <args...>` for every rustc
15//! call. This module detects that invocation pattern (the env var
16//! `__ANCHOR_RUSTC_WRAPPER=1` disambiguates from normal CLI usage) and
17//! replaces `-Zremap-cwd-prefix=` with `-Zremap-cwd-prefix=$CWD`,
18//! preserving absolute paths in the debug info.
19//!
20//! The sentinel env var is necessary because `RUSTC_WRAPPER` mode passes
21//! a path as argv[1] (the real rustc binary), which clap would reject as
22//! an unknown subcommand. The check in `main.rs` runs before clap
23//! parsing so the process never hits the normal CLI dispatch.
24//!
25//! ## Performance
26//!
27//! The wrapper adds ~1ms of fork+exec overhead per rustc invocation.
28//! This is negligible compared to actual compilation time.
29
30use std::process;
31
32/// Env var set by `anchor debugger` before calling `cargo build-sbf`.
33/// When present, the process knows it was invoked as a RUSTC_WRAPPER
34/// and should rewrite args instead of running the normal CLI.
35pub const WRAPPER_SENTINEL: &str = "__ANCHOR_RUSTC_WRAPPER";
36
37/// If we're running as a RUSTC_WRAPPER (sentinel env var is set),
38/// rewrite the rustc args and exec the real compiler. Never returns.
39///
40/// If we're NOT in wrapper mode, returns `false` so the caller can
41/// proceed with normal CLI parsing.
42pub fn maybe_exec_as_wrapper() -> bool {
43 if std::env::var_os(WRAPPER_SENTINEL).is_none() {
44 return false;
45 }
46
47 let args: Vec<String> = std::env::args().collect();
48 // RUSTC_WRAPPER invocation: argv[0]=anchor, argv[1]=rustc, argv[2..]=args
49 if args.len() < 2 {
50 eprintln!("anchor rustc-wrapper: expected <rustc> <args...>");
51 process::exit(1);
52 }
53
54 let rustc = &args[1];
55 let cwd = std::env::current_dir()
56 .map(|p| p.display().to_string())
57 .unwrap_or_default();
58
59 let rewritten: Vec<String> = args[2..]
60 .iter()
61 .map(|arg| {
62 if arg == "-Zremap-cwd-prefix=" {
63 format!("-Zremap-cwd-prefix={cwd}")
64 } else {
65 arg.clone()
66 }
67 })
68 .collect();
69
70 let status = process::Command::new(rustc)
71 .args(&rewritten)
72 .status()
73 .unwrap_or_else(|e| {
74 eprintln!("anchor rustc-wrapper: failed to exec {rustc}: {e}");
75 process::exit(1);
76 });
77
78 process::exit(status.code().unwrap_or(1));
79}