diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl index 776dce73b4..de145e8004 100644 --- a/rust/private/rustc.bzl +++ b/rust/private/rustc.bzl @@ -882,6 +882,33 @@ def _will_emit_object_file(emit): def _remove_codegen_units(flag): return None if flag.startswith("-Ccodegen-units") else flag +def _should_add_oso_prefix(feature_configuration, ld_is_direct_driver, toolchain): + """Whether to add -oso_prefix to strip absolute paths from N_OSO entries. + + On macOS, ld64 embeds absolute paths in N_OSO stab entries which breaks + build reproducibility. The -oso_prefix flag strips a prefix from these + entries. This function gates the flag on linker support. + + Args: + feature_configuration (FeatureConfiguration): Feature configuration to be queried. + ld_is_direct_driver (bool): Whether the linker is invoked directly (e.g. rust-lld). + toolchain (rust_toolchain): The current Rust toolchain. + + Returns: + bool: True if -oso_prefix should be added. + """ + if not toolchain.target_os.startswith(("mac", "darwin", "ios")): + return False + + if not ld_is_direct_driver: + return feature_configuration and cc_common.is_enabled( + feature_configuration = feature_configuration, + feature_name = "oso_prefix_is_pwd", + ) + + # lld-macho has supported -oso_prefix since LLVM 14 (2021). + return True + def construct_arguments( *, ctx, @@ -938,7 +965,9 @@ def construct_arguments( include_link_flags (bool, optional): Whether to include flags like `-l` that instruct the linker to search for a library. stamp (bool, optional): Whether or not workspace status stamping is enabled. For more details see https://docs.bazel.build/versions/main/user-manual.html#flag--stamp - remap_path_prefix (str, optional): A value used to remap `${pwd}` to. If set to None, no prefix will be set. + remap_path_prefix (str, optional): A value used to remap `${pwd}`, `${exec_root}`, and `${output_base}` to. + If set to None, no remapping will be applied. On macOS, also adds `-oso_prefix` to strip absolute paths + from N_OSO linker entries. use_json_output (bool): Have rustc emit json and process_wrapper parse json messages to output rendered output. build_metadata (bool): Generate CLI arguments for building *only* .rmeta files. This requires use_json_output. force_depend_on_objects (bool): Force using `.rlib` object files instead of metadata (`.rmeta`) files even if they are available. @@ -988,6 +1017,8 @@ def construct_arguments( # Since we cannot get the `exec_root` from starlark, we cheat a little and # use `${pwd}` which resolves the `exec_root` at action execution time. process_wrapper_flags.add("--subst", "pwd=${pwd}") + process_wrapper_flags.add("--subst", "exec_root=${exec_root}") + process_wrapper_flags.add("--subst", "output_base=${output_base}") # If stamping is enabled, enable the functionality in the process wrapper if stamp: @@ -1075,6 +1106,8 @@ def construct_arguments( # For determinism to help with build distribution and such if remap_path_prefix != None: rustc_flags.add("--remap-path-prefix=${{pwd}}={}".format(remap_path_prefix)) + rustc_flags.add("--remap-path-prefix=${{exec_root}}={}".format(remap_path_prefix)) + rustc_flags.add("--remap-path-prefix=${{output_base}}={}".format(remap_path_prefix)) emit_without_paths = [] for kind in emit: @@ -1139,6 +1172,17 @@ def construct_arguments( # Additional context: https://github.com/rust-lang/rust/pull/36574 rustc_flags.add_all(link_args, format_each = "--codegen=link-arg=%s") + if remap_path_prefix != None and _should_add_oso_prefix( + feature_configuration, + ld_is_direct_driver, + toolchain, + ): + if ld_is_direct_driver: + rustc_flags.add("--codegen=link-arg=-oso_prefix") + rustc_flags.add("${output_base}/", format = "--codegen=link-arg=%s") + else: + rustc_flags.add("--codegen=link-arg=-Wl,-oso_prefix,${output_base}/") + _add_native_link_flags( rustc_flags, dep_info, diff --git a/test/unit/remap_path_prefix/BUILD.bazel b/test/unit/remap_path_prefix/BUILD.bazel index 03739da675..fa77ac8256 100644 --- a/test/unit/remap_path_prefix/BUILD.bazel +++ b/test/unit/remap_path_prefix/BUILD.bazel @@ -1,5 +1,6 @@ load("//rust:defs.bzl", "rust_binary", "rust_library", "rust_test") load(":debug_transition.bzl", "dbg_rust_binary") +load(":remap_path_prefix_test.bzl", "remap_path_prefix_test_suite") rust_library( name = "dep", @@ -35,3 +36,7 @@ rust_test( }, deps = ["//rust/runfiles"], ) + +remap_path_prefix_test_suite( + name = "remap_path_prefix_test_suite", +) diff --git a/test/unit/remap_path_prefix/remap_path_prefix_test.bzl b/test/unit/remap_path_prefix/remap_path_prefix_test.bzl new file mode 100644 index 0000000000..431852cf51 --- /dev/null +++ b/test/unit/remap_path_prefix/remap_path_prefix_test.bzl @@ -0,0 +1,110 @@ +"""Analysis tests verifying remap_path_prefix flags.""" + +load("@bazel_skylib//lib:unittest.bzl", "analysistest") +load("@bazel_skylib//rules:write_file.bzl", "write_file") +load("//rust:defs.bzl", "rust_binary", "rust_library") +load( + "//test/unit:common.bzl", + "assert_action_mnemonic", + "assert_argv_contains", + "assert_list_contains_adjacent_elements", +) + +def _remap_path_prefix_test_impl(ctx): + env = analysistest.begin(ctx) + target = analysistest.target_under_test(env) + + action = target.actions[0] + assert_action_mnemonic(env, action, "Rustc") + + assert_argv_contains(env, action, "--remap-path-prefix=${pwd}=.") + assert_argv_contains(env, action, "--remap-path-prefix=${exec_root}=.") + assert_argv_contains(env, action, "--remap-path-prefix=${output_base}=.") + + return analysistest.end(env) + +_remap_path_prefix_test = analysistest.make(_remap_path_prefix_test_impl) + +def _subst_flags_test_impl(ctx): + """Verify that process wrapper --subst flags are present.""" + env = analysistest.begin(ctx) + target = analysistest.target_under_test(env) + + action = target.actions[0] + assert_action_mnemonic(env, action, "Rustc") + + assert_list_contains_adjacent_elements(env, action.argv, ["--subst", "pwd=${pwd}"]) + assert_list_contains_adjacent_elements(env, action.argv, ["--subst", "exec_root=${exec_root}"]) + assert_list_contains_adjacent_elements(env, action.argv, ["--subst", "output_base=${output_base}"]) + + return analysistest.end(env) + +_subst_flags_test = analysistest.make(_subst_flags_test_impl) + +def remap_path_prefix_test_suite(name): + """Entry-point macro called from the BUILD file. + + Args: + name (str): The name of the test suite. + """ + write_file( + name = "remap_lib_src", + out = "remap_lib.rs", + content = [ + "pub fn hello() {}", + "", + ], + ) + + rust_library( + name = "remap_lib", + srcs = [":remap_lib.rs"], + edition = "2021", + ) + + write_file( + name = "remap_bin_src", + out = "remap_bin.rs", + content = [ + "fn main() {}", + "", + ], + ) + + rust_binary( + name = "remap_bin", + srcs = [":remap_bin.rs"], + edition = "2021", + ) + + _remap_path_prefix_test( + name = "remap_path_prefix_lib_test", + target_under_test = ":remap_lib", + ) + + _remap_path_prefix_test( + name = "remap_path_prefix_bin_test", + target_under_test = ":remap_bin", + ) + + _subst_flags_test( + name = "subst_flags_lib_test", + target_under_test = ":remap_lib", + ) + + _subst_flags_test( + name = "subst_flags_bin_test", + target_under_test = ":remap_bin", + ) + + tests = [ + ":remap_path_prefix_lib_test", + ":remap_path_prefix_bin_test", + ":subst_flags_lib_test", + ":subst_flags_bin_test", + ] + + native.test_suite( + name = name, + tests = tests, + ) diff --git a/util/process_wrapper/BUILD.bazel b/util/process_wrapper/BUILD.bazel index af56264e4c..83e2555e6c 100644 --- a/util/process_wrapper/BUILD.bazel +++ b/util/process_wrapper/BUILD.bazel @@ -40,7 +40,7 @@ rust_binary_without_process_wrapper( # paths embedded in this section, is stripped out. rustc_flags = select({ ":opt_linux": ["-Cstrip=debuginfo"], - ":opt_macos": ["-Cstrip=debuginfo"], + ":opt_macos": ["-Cstrip=symbols"], "//conditions:default": [], }), visibility = ["//visibility:public"], diff --git a/util/process_wrapper/options.rs b/util/process_wrapper/options.rs index 2f252cadc7..1bbd6cdfdd 100644 --- a/util/process_wrapper/options.rs +++ b/util/process_wrapper/options.rs @@ -137,6 +137,34 @@ pub(crate) fn options() -> Result { .to_str() .ok_or_else(|| OptionError::Generic("current directory not utf-8".to_owned()))? .to_owned(); + let output_base = { + let external = std::path::Path::new(¤t_dir).join("external"); + match std::fs::read_link(&external) { + Ok(target) => target + .parent() + .unwrap_or(&target) + .to_str() + .unwrap_or(¤t_dir) + .to_owned(), + Err(_) => { + let cwd = std::path::Path::new(¤t_dir); + cwd.parent() + .and_then(|p| p.parent()) + .and_then(|p| p.to_str()) + .unwrap_or(¤t_dir) + .to_owned() + } + } + }; + + let exec_root = { + let workspace_name = std::path::Path::new(¤t_dir) + .file_name() + .and_then(|n| n.to_str()) + .unwrap_or("_main"); + format!("{}/execroot/{}", output_base, workspace_name) + }; + let subst_mappings = subst_mapping_raw .unwrap_or_default() .into_iter() @@ -146,6 +174,10 @@ pub(crate) fn options() -> Result { })?; let v = if val == "${pwd}" { current_dir.as_str() + } else if val == "${output_base}" { + output_base.as_str() + } else if val == "${exec_root}" { + exec_root.as_str() } else { val }