Skip to content

Commit 9d7f463

Browse files
committed
Remap path prefixes for canonicalized paths
1 parent 0a59de5 commit 9d7f463

9 files changed

Lines changed: 230 additions & 35 deletions

File tree

rust/private/rustc.bzl

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -883,6 +883,27 @@ def _will_emit_object_file(emit):
883883
def _remove_codegen_units(flag):
884884
return None if flag.startswith("-Ccodegen-units") else flag
885885

886+
def _should_add_oso_prefix(toolchain):
887+
"""Whether to add -oso_prefix to strip absolute paths from N_OSO entries.
888+
889+
On macOS, ld64 embeds absolute paths in N_OSO stab entries which breaks
890+
build reproducibility. The -oso_prefix flag strips a prefix from these
891+
entries.
892+
893+
Both Apple's ld64 and lld-macho support -oso_prefix. For indirect linker
894+
drivers (cc/clang), the flag is passed as -Wl,-oso_prefix,<prefix>.
895+
896+
Args:
897+
toolchain (rust_toolchain): The current Rust toolchain.
898+
899+
Returns:
900+
bool: True if -oso_prefix should be added.
901+
"""
902+
if not toolchain.target_os.startswith(("mac", "darwin", "ios")):
903+
return False
904+
905+
return True
906+
886907
def construct_arguments(
887908
*,
888909
ctx,
@@ -939,7 +960,9 @@ def construct_arguments(
939960
include_link_flags (bool, optional): Whether to include flags like `-l` that instruct the linker to search for a library.
940961
stamp (bool, optional): Whether or not workspace status stamping is enabled. For more details see
941962
https://docs.bazel.build/versions/main/user-manual.html#flag--stamp
942-
remap_path_prefix (str, optional): A value used to remap `${pwd}` to. If set to None, no prefix will be set.
963+
remap_path_prefix (str, optional): A value used to remap `${pwd}`, `${exec_root}`, and `${output_base}` to.
964+
If set to None, no remapping will be applied. On macOS, also adds `-oso_prefix` to strip absolute paths
965+
from N_OSO linker entries.
943966
use_json_output (bool): Have rustc emit json and process_wrapper parse json messages to output rendered output.
944967
build_metadata (bool): Generate CLI arguments for building *only* .rmeta files. This requires use_json_output.
945968
force_depend_on_objects (bool): Force using `.rlib` object files instead of metadata (`.rmeta`) files even if they are available.
@@ -989,6 +1012,8 @@ def construct_arguments(
9891012
# Since we cannot get the `exec_root` from starlark, we cheat a little and
9901013
# use `${pwd}` which resolves the `exec_root` at action execution time.
9911014
process_wrapper_flags.add("--subst", "pwd=${pwd}")
1015+
process_wrapper_flags.add("--subst", "exec_root=${exec_root}")
1016+
process_wrapper_flags.add("--subst", "output_base=${output_base}")
9921017

9931018
# If stamping is enabled, enable the functionality in the process wrapper
9941019
if stamp:
@@ -1076,6 +1101,8 @@ def construct_arguments(
10761101
# For determinism to help with build distribution and such
10771102
if remap_path_prefix != None:
10781103
rustc_flags.add("--remap-path-prefix=${{pwd}}={}".format(remap_path_prefix))
1104+
rustc_flags.add("--remap-path-prefix=${{exec_root}}={}".format(remap_path_prefix))
1105+
rustc_flags.add("--remap-path-prefix=${{output_base}}={}".format(remap_path_prefix))
10791106

10801107
emit_without_paths = []
10811108
for kind in emit:
@@ -1140,6 +1167,15 @@ def construct_arguments(
11401167
# Additional context: https://github.com/rust-lang/rust/pull/36574
11411168
rustc_flags.add_all(link_args, format_each = "--codegen=link-arg=%s")
11421169

1170+
if remap_path_prefix != None and _should_add_oso_prefix(
1171+
toolchain,
1172+
):
1173+
if ld_is_direct_driver:
1174+
rustc_flags.add("--codegen=link-arg=-oso_prefix")
1175+
rustc_flags.add("${pwd}/", format = "--codegen=link-arg=%s")
1176+
else:
1177+
rustc_flags.add("--codegen=link-arg=-Wl,-oso_prefix,${pwd}/")
1178+
11431179
_add_native_link_flags(
11441180
rustc_flags,
11451181
dep_info,

test/unit/remap_path_prefix/BUILD.bazel

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
load("//rust:defs.bzl", "rust_binary", "rust_library", "rust_test")
22
load(":debug_transition.bzl", "dbg_rust_binary")
3+
load(":remap_path_prefix_test.bzl", "remap_path_prefix_test_suite")
34

45
rust_library(
56
name = "dep",
@@ -35,3 +36,7 @@ rust_test(
3536
},
3637
deps = ["//rust/runfiles"],
3738
)
39+
40+
remap_path_prefix_test_suite(
41+
name = "remap_path_prefix_test_suite",
42+
)

test/unit/remap_path_prefix/integration_test.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ fn test_backtrace() {
1919
let mut check_next = false;
2020
for line in stderr.split('\n') {
2121
if check_next {
22-
if !line.contains("./test/unit/remap_path_prefix/panic_bin.rs:6:5") {
23-
panic!("Expected line to contain ./test/unit/remap_path_prefix/panic_bin.rs:6:5 but was {}", line);
22+
if !line.contains("test/unit/remap_path_prefix/panic_bin.rs:6:5") {
23+
panic!("Expected line to contain test/unit/remap_path_prefix/panic_bin.rs:6:5 but was {}", line);
2424
}
2525
return;
2626
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
"""Analysis tests verifying remap_path_prefix flags."""
2+
3+
load("@bazel_skylib//lib:unittest.bzl", "analysistest")
4+
load("@bazel_skylib//rules:write_file.bzl", "write_file")
5+
load("//rust:defs.bzl", "rust_binary", "rust_library")
6+
load(
7+
"//test/unit:common.bzl",
8+
"assert_action_mnemonic",
9+
"assert_argv_contains",
10+
"assert_list_contains_adjacent_elements",
11+
)
12+
13+
def _remap_path_prefix_test_impl(ctx):
14+
env = analysistest.begin(ctx)
15+
target = analysistest.target_under_test(env)
16+
17+
action = target.actions[0]
18+
assert_action_mnemonic(env, action, "Rustc")
19+
20+
assert_argv_contains(env, action, "--remap-path-prefix=${pwd}=.")
21+
assert_argv_contains(env, action, "--remap-path-prefix=${exec_root}=.")
22+
assert_argv_contains(env, action, "--remap-path-prefix=${output_base}=.")
23+
24+
return analysistest.end(env)
25+
26+
_remap_path_prefix_test = analysistest.make(_remap_path_prefix_test_impl)
27+
28+
def _subst_flags_test_impl(ctx):
29+
"""Verify that process wrapper --subst flags are present."""
30+
env = analysistest.begin(ctx)
31+
target = analysistest.target_under_test(env)
32+
33+
action = target.actions[0]
34+
assert_action_mnemonic(env, action, "Rustc")
35+
36+
assert_list_contains_adjacent_elements(env, action.argv, ["--subst", "pwd=${pwd}"])
37+
assert_list_contains_adjacent_elements(env, action.argv, ["--subst", "exec_root=${exec_root}"])
38+
assert_list_contains_adjacent_elements(env, action.argv, ["--subst", "output_base=${output_base}"])
39+
40+
return analysistest.end(env)
41+
42+
_subst_flags_test = analysistest.make(_subst_flags_test_impl)
43+
44+
def remap_path_prefix_test_suite(name):
45+
"""Entry-point macro called from the BUILD file.
46+
47+
Args:
48+
name (str): The name of the test suite.
49+
"""
50+
write_file(
51+
name = "remap_lib_src",
52+
out = "remap_lib.rs",
53+
content = [
54+
"pub fn hello() {}",
55+
"",
56+
],
57+
)
58+
59+
rust_library(
60+
name = "remap_lib",
61+
srcs = [":remap_lib.rs"],
62+
edition = "2021",
63+
)
64+
65+
write_file(
66+
name = "remap_bin_src",
67+
out = "remap_bin.rs",
68+
content = [
69+
"fn main() {}",
70+
"",
71+
],
72+
)
73+
74+
rust_binary(
75+
name = "remap_bin",
76+
srcs = [":remap_bin.rs"],
77+
edition = "2021",
78+
)
79+
80+
_remap_path_prefix_test(
81+
name = "remap_path_prefix_lib_test",
82+
target_under_test = ":remap_lib",
83+
)
84+
85+
_remap_path_prefix_test(
86+
name = "remap_path_prefix_bin_test",
87+
target_under_test = ":remap_bin",
88+
)
89+
90+
_subst_flags_test(
91+
name = "subst_flags_lib_test",
92+
target_under_test = ":remap_lib",
93+
)
94+
95+
_subst_flags_test(
96+
name = "subst_flags_bin_test",
97+
target_under_test = ":remap_bin",
98+
)
99+
100+
tests = [
101+
":remap_path_prefix_lib_test",
102+
":remap_path_prefix_bin_test",
103+
":subst_flags_lib_test",
104+
":subst_flags_bin_test",
105+
]
106+
107+
native.test_suite(
108+
name = name,
109+
tests = tests,
110+
)

util/process_wrapper/BUILD.bazel

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,7 @@
1-
load("@bazel_skylib//lib:selects.bzl", "selects")
2-
31
# buildifier: disable=bzl-visibility
42
load("//rust/private:rust.bzl", "rust_binary_without_process_wrapper", "rust_test_without_process_wrapper_test")
53
load("//util/process_wrapper/private:bootstrap_process_wrapper.bzl", "bootstrap_process_wrapper")
64

7-
config_setting(
8-
name = "compilation_mode_opt",
9-
values = {"compilation_mode": "opt"},
10-
)
11-
12-
selects.config_setting_group(
13-
name = "opt_linux",
14-
match_all = [
15-
":compilation_mode_opt",
16-
"@platforms//os:linux",
17-
],
18-
visibility = ["@rules_rust_tinyjson//:__pkg__"],
19-
)
20-
21-
selects.config_setting_group(
22-
name = "opt_macos",
23-
match_all = [
24-
":compilation_mode_opt",
25-
"@platforms//os:macos",
26-
],
27-
visibility = ["@rules_rust_tinyjson//:__pkg__"],
28-
)
29-
305
rust_binary_without_process_wrapper(
316
name = "process_wrapper",
327
srcs = glob(["*.rs"]),
@@ -37,10 +12,12 @@ rust_binary_without_process_wrapper(
3712
edition = "2018",
3813
# To ensure the process wrapper is produced deterministically
3914
# debug info, which is known to sometimes have host specific
40-
# paths embedded in this section, is stripped out.
15+
# paths embedded in this section, is stripped out. On macOS,
16+
# full symbol stripping is needed to also remove N_OSO stab
17+
# entries whose timestamps vary between builds.
4118
rustc_flags = select({
42-
":opt_linux": ["-Cstrip=debuginfo"],
43-
":opt_macos": ["-Cstrip=debuginfo"],
19+
"@platforms//os:linux": ["-Cstrip=debuginfo"],
20+
"@platforms//os:macos": ["-Cstrip=symbols"],
4421
"//conditions:default": [],
4522
}),
4623
visibility = ["//visibility:public"],

util/process_wrapper/BUILD.tinyjson.bazel

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ rust_library_without_process_wrapper(
1010
# debug info, which is known to sometimes have host specific
1111
# paths embedded in this section, is stripped out.
1212
rustc_flags = select({
13-
"@rules_rust//util/process_wrapper:opt_linux": ["-Cstrip=debuginfo"],
14-
"@rules_rust//util/process_wrapper:opt_macos": ["-Cstrip=debuginfo"],
13+
"@platforms//os:linux": ["-Cstrip=debuginfo"],
14+
"@platforms//os:macos": ["-Cstrip=debuginfo"],
1515
"//conditions:default": [],
1616
}),
1717
visibility = ["@rules_rust//util/process_wrapper:__pkg__"],

util/process_wrapper/options.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,34 @@ pub(crate) fn options() -> Result<Options, OptionError> {
137137
.to_str()
138138
.ok_or_else(|| OptionError::Generic("current directory not utf-8".to_owned()))?
139139
.to_owned();
140+
let output_base = {
141+
let external = std::path::Path::new(&current_dir).join("external");
142+
match std::fs::canonicalize(external) {
143+
Ok(canonical) => canonical
144+
.parent()
145+
.and_then(|p| p.to_str())
146+
.unwrap_or(&current_dir)
147+
.to_owned(),
148+
Err(_) => match std::fs::canonicalize(&current_dir) {
149+
Ok(canonical) => canonical
150+
.parent()
151+
.and_then(|p| p.parent())
152+
.and_then(|p| p.to_str())
153+
.unwrap_or(&current_dir)
154+
.to_owned(),
155+
Err(_) => current_dir.clone(),
156+
},
157+
}
158+
};
159+
160+
let exec_root = {
161+
let workspace_name = std::path::Path::new(&current_dir)
162+
.file_name()
163+
.and_then(|n| n.to_str())
164+
.unwrap_or("_main");
165+
format!("{}/execroot/{}", output_base, workspace_name)
166+
};
167+
140168
let subst_mappings = subst_mapping_raw
141169
.unwrap_or_default()
142170
.into_iter()
@@ -146,6 +174,10 @@ pub(crate) fn options() -> Result<Options, OptionError> {
146174
})?;
147175
let v = if val == "${pwd}" {
148176
current_dir.as_str()
177+
} else if val == "${output_base}" {
178+
output_base.as_str()
179+
} else if val == "${exec_root}" {
180+
exec_root.as_str()
149181
} else {
150182
val
151183
}

util/process_wrapper/private/process_wrapper.bat

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ SET command=%*
66
:: Resolve the `${pwd}` placeholders
77
SET command=!command:${pwd}=%CD%!
88

9+
:: Resolve the `${output_base}` and `${exec_root}` placeholders.
10+
:: The external directory is a junction/symlink to output_base\external.
11+
:: This mirrors the logic in options.rs used by the real process wrapper.
12+
FOR /F "delims=" %%i IN ('cd external\.. ^& cd') DO SET output_base=%%i
13+
FOR %%i IN ("%CD%") DO SET workspace_name=%%~nxi
14+
SET exec_root=!output_base!\execroot\!workspace_name!
15+
SET command=!command:${output_base}=%output_base%!
16+
SET command=!command:${exec_root}=%exec_root%!
17+
918
:: Strip out the leading `--` argument.
1019
SET command=!command:~3!
1120

util/process_wrapper/private/process_wrapper.sh

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,39 @@ set -eu
55
# Skip the first argument which is expected to be `--`
66
shift
77

8+
# Derive output_base and exec_root so we can expand their placeholders
9+
# in rustc flags (e.g. --remap-path-prefix, -oso_prefix). This mirrors
10+
# the logic in options.rs used by the real process wrapper.
11+
phys_pwd=$(cd -P . && pwd)
12+
if [ -d "external" ]; then
13+
output_base=$(cd -P external/.. && pwd)
14+
else
15+
output_base="${phys_pwd%/*}"
16+
output_base="${output_base%/*}"
17+
fi
18+
workspace_name="${phys_pwd##*/}"
19+
exec_root="${output_base}/execroot/${workspace_name}"
20+
821
for arg in "$@"; do
922
case "$arg" in
1023
*'${pwd}'*)
11-
# Split on '${pwd}' and rejoin with the actual PWD value
1224
prefix="${arg%%\$\{pwd\}*}"
1325
suffix="${arg#*\$\{pwd\}}"
14-
arg="${prefix}${PWD}${suffix}"
26+
arg="${prefix}${phys_pwd}${suffix}"
27+
;;
28+
esac
29+
case "$arg" in
30+
*'${output_base}'*)
31+
prefix="${arg%%\$\{output_base\}*}"
32+
suffix="${arg#*\$\{output_base\}}"
33+
arg="${prefix}${output_base}${suffix}"
34+
;;
35+
esac
36+
case "$arg" in
37+
*'${exec_root}'*)
38+
prefix="${arg%%\$\{exec_root\}*}"
39+
suffix="${arg#*\$\{exec_root\}}"
40+
arg="${prefix}${exec_root}${suffix}"
1541
;;
1642
esac
1743
set -- "$@" "$arg"

0 commit comments

Comments
 (0)