Skip to main content

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}