From 241e184630f3b38f7ba36311a0edbba892a35366 Mon Sep 17 00:00:00 2001 From: David Zbarsky Date: Sat, 5 Sep 2026 14:39:43 -0400 Subject: [PATCH] fix: remap bootstrap source paths in rustc Bootstrap actions pass rustc flags through @params, but the C++ bootstrap process wrapper substitutes only direct arguments. The literal remap placeholders leave execution directories in tinyjson crate metadata. Use -Zremap-cwd-prefix for bootstrap compilation so rustc resolves its own working directory. Default RUSTC_BOOTSTRAP to the bootstrap crate's name, preserving explicit environment settings. Keep the existing response files and SDKROOT expansion without changing the C++ wrapper. Add a regression test that reads the actual bootstrap tinyjson archive. The test fails before the fix. Sandboxed and local builds in different output bases now produce identical archives without execution directories. Validation: Bazel 9.1.0, Rust 1.98.0, macOS arm64; bazel test //... passed 621 tests and skipped 37 platform-incompatible tests. Refs hermeticbuild/rules_rs#233 and hermeticbuild/rules_rust#46. Co-authored-by: Codex --- rust/private/rustc.bzl | 26 ++++++++++++++----- test/process_wrapper_bootstrap/BUILD.bazel | 16 ++++++++++++ .../bootstrap_reproducibility_test.rs | 19 ++++++++++++++ .../bootstrap_tinyjson.bzl | 21 +++++++++++++++ 4 files changed, 75 insertions(+), 7 deletions(-) create mode 100644 test/process_wrapper_bootstrap/bootstrap_reproducibility_test.rs create mode 100644 test/process_wrapper_bootstrap/bootstrap_tinyjson.bzl diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl index b74b046b25..a7006fb429 100644 --- a/rust/private/rustc.bzl +++ b/rust/private/rustc.bzl @@ -1385,13 +1385,18 @@ def construct_arguments( # For determinism to help with build distribution and such if remap_path_prefix != None: - # `--remap-path-prefix` flags are applied in reverse order. We need to - # specify the outermost directory (output_base) first, so that it's - # remapped last. Otherwise we can end up with a partial rewrite where - # "/path/to/output_base/execroot" becomes "./execroot" rather than ".". - rustc_flags.add("--remap-path-prefix=${{output_base}}={}".format(remap_path_prefix)) - rustc_flags.add("--remap-path-prefix=${{pwd}}={}".format(remap_path_prefix)) - rustc_flags.add("--remap-path-prefix=${{exec_root}}={}".format(remap_path_prefix)) + if toolchain._bootstrapping: + # rustc resolves cwd itself, so bootstrap response files need no + # placeholder substitution to remap source paths in crate metadata. + rustc_flags.add(remap_path_prefix, format = "-Zremap-cwd-prefix=%s") + else: + # `--remap-path-prefix` flags are applied in reverse order. We need to + # specify the outermost directory (output_base) first, so that it's + # remapped last. Otherwise we can end up with a partial rewrite where + # "/path/to/output_base/execroot" becomes "./execroot" rather than ".". + rustc_flags.add("--remap-path-prefix=${output_base}=%s" % remap_path_prefix) + rustc_flags.add("--remap-path-prefix=${pwd}=%s" % remap_path_prefix) + rustc_flags.add("--remap-path-prefix=${exec_root}=%s" % remap_path_prefix) emit_without_paths = [] redirect_link_to_metadata = build_metadata and crate_info.metadata != None @@ -1661,6 +1666,11 @@ def construct_arguments( if hasattr(ctx.attr, "_extra_exec_rustc_env") and is_exec_configuration(ctx): env.update(ctx.attr._extra_exec_rustc_env[ExtraExecRustcEnvInfo].extra_exec_rustc_env) + if toolchain._bootstrapping and remap_path_prefix != None: + # -Zremap-cwd-prefix is unstable. Respect explicit RUSTC_BOOTSTRAP values; + # otherwise limit the opt-in to this bootstrap crate. + env.setdefault("RUSTC_BOOTSTRAP", ctx.configuration.default_shell_env.get("RUSTC_BOOTSTRAP", crate_info.name)) + # Strip any `-Zallow-features=` entries out of the toolchain's extra # rustc flags into `all_allowed_unstable_features` and, when # `unstable_rust_features_config` is configured to a concrete list, @@ -2163,6 +2173,8 @@ def rustc_compile( use_json_output = bool(build_metadata) or bool(rustc_output) or bool(rustc_rmeta_output), skip_expanding_rustc_env = skip_expanding_rustc_env, require_explicit_unstable_features = require_explicit_unstable_features, + # _spawnvp in bootstrap_process_wrapper does not preserve embedded + # quotes in direct Windows arguments. rustc reads them from a response file. always_use_param_file = rust_toolchain._bootstrapping, inject_allow_features_guardrail = inject_allow_features_guardrail, allowed_unstable_rust_features = allowed_unstable_rust_features, diff --git a/test/process_wrapper_bootstrap/BUILD.bazel b/test/process_wrapper_bootstrap/BUILD.bazel index 6a0c167adc..0645b3444d 100644 --- a/test/process_wrapper_bootstrap/BUILD.bazel +++ b/test/process_wrapper_bootstrap/BUILD.bazel @@ -1,4 +1,20 @@ load("//rust:defs.bzl", "rust_binary", "rust_test") +load(":bootstrap_tinyjson.bzl", "bootstrap_tinyjson") + +bootstrap_tinyjson( + name = "bootstrap_tinyjson", +) + +rust_test( + name = "bootstrap_reproducibility_test", + srcs = ["bootstrap_reproducibility_test.rs"], + data = [":bootstrap_tinyjson"], + edition = "2021", + env = { + "BOOTSTRAP_TINYJSON_RLOCATIONPATH": "$(rlocationpath :bootstrap_tinyjson)", + }, + deps = ["//rust/runfiles"], +) rust_binary( name = "bootstrap_process_wrapper_probe", diff --git a/test/process_wrapper_bootstrap/bootstrap_reproducibility_test.rs b/test/process_wrapper_bootstrap/bootstrap_reproducibility_test.rs new file mode 100644 index 0000000000..e83cb970be --- /dev/null +++ b/test/process_wrapper_bootstrap/bootstrap_reproducibility_test.rs @@ -0,0 +1,19 @@ +//! Check the metadata of tinyjson compiled with the bootstrap process wrapper. + +#[test] +fn bootstrap_tinyjson_does_not_embed_execution_paths() { + let rfiles = runfiles::Runfiles::create().unwrap(); + let rlocation = std::env::var("BOOTSTRAP_TINYJSON_RLOCATIONPATH").unwrap(); + let artifact = runfiles::rlocation!(rfiles, rlocation.as_str()).unwrap(); + let bytes = std::fs::read(&artifact).unwrap(); + + for prefix in ["/execroot/", "\\execroot\\", "/sandbox/", "\\sandbox\\"] { + assert!( + !bytes + .windows(prefix.len()) + .any(|part| part == prefix.as_bytes()), + "{} contains an unremapped execution path ({prefix})", + artifact.display(), + ); + } +} diff --git a/test/process_wrapper_bootstrap/bootstrap_tinyjson.bzl b/test/process_wrapper_bootstrap/bootstrap_tinyjson.bzl new file mode 100644 index 0000000000..a6f1e82bba --- /dev/null +++ b/test/process_wrapper_bootstrap/bootstrap_tinyjson.bzl @@ -0,0 +1,21 @@ +"""Expose tinyjson from the process wrapper's bootstrap configuration.""" + +load("//rust:defs.bzl", "rust_common") + +def _bootstrap_tinyjson_impl(ctx): + crates = ctx.attr.process_wrapper[rust_common.dep_info].transitive_crates.to_list() + tinyjson = [crate.output for crate in crates if crate.name == "tinyjson"] + if len(tinyjson) != 1: + fail("Expected one bootstrap tinyjson output, got %s" % tinyjson) + return [DefaultInfo(files = depset(tinyjson))] + +bootstrap_tinyjson = rule( + implementation = _bootstrap_tinyjson_impl, + attrs = { + "process_wrapper": attr.label( + cfg = "exec", + default = "//util/process_wrapper:process_wrapper", + providers = [rust_common.dep_info], + ), + }, +)