From cd13823111ff0f61fa34b6de65144dd823acfd00 Mon Sep 17 00:00:00 2001 From: Walter Gray Date: Mon, 20 Apr 2026 11:20:39 -0700 Subject: [PATCH] Fix nondeterministic SVH from sandbox-local OUT_DIR/CARGO_MANIFEST_DIR Under sandboxing, ${pwd} resolves to a per-pid sandbox path that differs across actions. rustc's env!() bakes the raw string into the crate SVH and --remap-path-prefix does not normalize env!() values, so crates like pyo3-build-config that do PathBuf::from(env!("OUT_DIR")) produce divergent SVHs between hollow-metadata and full rlib actions, breaking pipelined compilation and poisoning the cache. Route CARGO_MANIFEST_DIR and OUT_DIR through ${exec_root}, and resolve ${exec_root} in the process wrapper by walking up from cwd to the first ancestor containing Bazel's DO_NOT_BUILD_HERE sentinel, then appending execroot/. Falls back to the prior cwd-derived value if the sentinel is not found. ${output_base} is left untouched so that --remap-path-prefix=\${output_base}=. keeps stripping sandbox-local prefixes from paths embedded in rmeta/debuginfo. --- rust/private/rustc.bzl | 14 ++++++++++++-- util/process_wrapper/options.rs | 21 +++++++++++++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl index d12900cb28..1ae27da1f9 100644 --- a/rust/private/rustc.bzl +++ b/rust/private/rustc.bzl @@ -1022,11 +1022,21 @@ def construct_arguments( # Both ctx.label.workspace_root and ctx.label.package are relative paths # and either can be empty strings. Avoid trailing/double slashes in the path. - components = "${{pwd}}/{}/{}".format(ctx.label.workspace_root, ctx.label.package).split("/") + # + # Use ${exec_root} (sandbox-invariant absolute path to Bazel's real execroot) + # instead of ${pwd} here. Under sandboxed execution every rustc action runs + # in its own per-pid sandbox cwd, so ${pwd} resolves to a different absolute + # string for each action. rustc's env!() macro bakes the raw string into the + # compiled crate and hashes it into the SVH; --remap-path-prefix does not + # normalize env!() values. Result: nondeterministic SVHs for crates that do + # `PathBuf::from(env!("OUT_DIR"))` (e.g. pyo3-build-config), breaking + # pipelined compilation (full/hollow rlib SVHs diverge) and poisoning the + # remote/disk cache. + components = "${{exec_root}}/{}/{}".format(ctx.label.workspace_root, ctx.label.package).split("/") env["CARGO_MANIFEST_DIR"] = "/".join([c for c in components if c]) if out_dir != None: - env["OUT_DIR"] = "${pwd}/" + out_dir + env["OUT_DIR"] = "${exec_root}/" + out_dir # Arguments for launching rustc from the process wrapper rustc_path = ctx.actions.args() diff --git a/util/process_wrapper/options.rs b/util/process_wrapper/options.rs index 2e139f7ccc..9c496d996c 100644 --- a/util/process_wrapper/options.rs +++ b/util/process_wrapper/options.rs @@ -137,6 +137,12 @@ pub(crate) fn options() -> Result { .to_str() .ok_or_else(|| OptionError::Generic("current directory not utf-8".to_owned()))? .to_owned(); + // ${output_base} has historically resolved to the sandbox's local cwd + // (parent of canonicalized `cwd/external`) rather than Bazel's real output_base. + // Downstream consumers — in particular the `--remap-path-prefix=${output_base}=.` + // flag emitted by rules_rust — rely on that sandbox-local value to fully + // strip the sandbox cwd prefix from paths embedded in rmeta/debuginfo. + // Leaving ${output_base} untouched preserves that behavior. let output_base = { let external = std::path::Path::new(¤t_dir).join("external"); match std::fs::canonicalize(external) { @@ -157,12 +163,23 @@ pub(crate) fn options() -> Result { } }; + // ${exec_root} resolves to Bazel's real execroot, sandbox-invariant across + // all strategies. Derived by walking up `cwd` to the first ancestor that + // contains Bazel's `DO_NOT_BUILD_HERE` sentinel file (the real output_base) + // and appending `execroot/`. Used by rules_rust to set + // `CARGO_MANIFEST_DIR` / `OUT_DIR` to a sandbox-invariant absolute path so + // that `env!()` values baked into SVH are identical across pipelined + // Rustc / RustcMetadata actions and across independent invocations. let exec_root = { - let workspace_name = std::path::Path::new(¤t_dir) + let cwd_path = std::path::Path::new(¤t_dir); + let workspace_name = cwd_path .file_name() .and_then(|n| n.to_str()) .unwrap_or("_main"); - format!("{}/execroot/{}", output_base, workspace_name) + match incremental_cache::find_output_base(cwd_path) { + Some(real_ob) => format!("{}/execroot/{}", real_ob.to_string_lossy(), workspace_name), + None => format!("{}/execroot/{}", output_base, workspace_name), + } }; let subst_mappings = subst_mapping_raw