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], + ), + }, +)