Skip to content

Commit fb01b69

Browse files
fix(cargo_build_script): expand ${pwd} for execpaths and locations in build_script_env (bazelbuild#4088)
Singular `$(execpath ...)` and `$(location ...)` were already prefixed with `${pwd}/` so build_script_runner could resolve them to absolute paths at run time. The plural forms `$(execpaths ...)` and `$(locations ...)` were silently omitted from this handling, leaving their paths as execroot-relative. For plural forms each macro is expanded individually (via ctx.expand_location) and every resulting space-separated path is prefixed with `${pwd}/`.
1 parent a2113e9 commit fb01b69

3 files changed

Lines changed: 99 additions & 27 deletions

File tree

rust/private/utils.bzl

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,26 @@ def _expand_location_for_build_script_runner(ctx, v, data, known_variables):
286286
if directive in v:
287287
# build script runner will expand pwd to execroot for us
288288
v = v.replace(directive, "$${pwd}/" + directive)
289+
290+
for directive in ("$(execpaths ", "$(locations "):
291+
if directive in v:
292+
# Plural forms expand to multiple space-separated paths, so we must
293+
# expand each macro individually and prefix every resulting path.
294+
# Split on the opening directive; each subsequent part begins with
295+
# "label)rest", letting us reconstruct and expand one macro at a time.
296+
parts = v.split(directive)
297+
result = parts[0]
298+
for part in parts[1:]:
299+
end = part.find(")")
300+
if end == -1:
301+
result += directive + part
302+
continue
303+
macro = directive + part[:end] + ")"
304+
expanded = ctx.expand_location(macro, data)
305+
prefixed = " ".join(["$${pwd}/" + p for p in expanded.split(" ")])
306+
result += prefixed + part[end + 1:]
307+
v = result
308+
289309
return ctx.expand_make_variables(
290310
v,
291311
ctx.expand_location(v, data),
@@ -295,11 +315,14 @@ def _expand_location_for_build_script_runner(ctx, v, data, known_variables):
295315
def expand_dict_value_locations(ctx, env, data, known_variables):
296316
"""Performs location-macro expansion on string values.
297317
298-
$(execroot ...) and $(location ...) are prefixed with ${pwd},
299-
which process_wrapper and build_script_runner will expand at run time
300-
to the absolute path. This is necessary because include_str!() is relative
301-
to the currently compiled file, and build scripts run relative to the
302-
manifest dir, so we can not use execroot-relative paths.
318+
$(execpath ...), $(execpaths ...), $(location ...) and $(locations ...) are
319+
prefixed with ${pwd}, which process_wrapper and build_script_runner will
320+
expand at run time to the absolute path.
321+
This is necessary because include_str!() is relative to the currently
322+
compiled file, and build scripts run relative to the manifest dir, so we
323+
can not use execroot-relative paths.
324+
Plural forms (execpaths/locations) expand to multiple space-separated paths;
325+
each path receives its own ${pwd}/ prefix.
303326
304327
$(rootpath ...) is unmodified, and is useful for passing in paths via
305328
rustc_env that are encoded in the binary with env!(), but utilized at
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
fn main() {
2+
let path = std::env::var("MY_DATA").expect("MY_DATA env var must be set");
3+
assert!(
4+
std::path::Path::new(&path).exists(),
5+
"MY_DATA path does not exist: {}",
6+
path
7+
);
8+
}

test/unit/location_expansion/location_expansion_test.bzl

Lines changed: 63 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,59 +2,99 @@
22

33
load("@bazel_skylib//lib:unittest.bzl", "analysistest")
44
load("@bazel_skylib//rules:write_file.bzl", "write_file")
5+
load("//cargo:defs.bzl", "cargo_build_script")
56
load("//rust:defs.bzl", "rust_library")
6-
load("//test/unit:common.bzl", "assert_action_mnemonic", "assert_argv_contains")
7+
load("//test/unit:common.bzl", "assert_action_mnemonic", "assert_argv_contains", "assert_env_value")
8+
9+
def _find_action(tut, mnemonic):
10+
for action in tut.actions:
11+
if action.mnemonic == mnemonic:
12+
return action
13+
return None
714

815
def _location_expansion_rustc_flags_test(ctx):
916
env = analysistest.begin(ctx)
1017
tut = analysistest.target_under_test(env)
1118
action = tut.actions[1]
1219
assert_action_mnemonic(env, action, "Rustc")
1320

14-
# Because target `rustc_flags` use `$(execpath ...)`, the action does
21+
# Because target `rustc_flags` use location macros, the action does
1522
# not advertise `supports-path-mapping`, so file paths remain at their
1623
# configuration-specific `ctx.bin_dir` locations.
1724
assert_argv_contains(env, action, ctx.bin_dir.path + "/test/unit/location_expansion/mylibrary.rs")
1825

19-
# `$(location ...)` is expanded at analysis time into a literal
20-
# configuration-dependent string (`bazel-out/<config>/bin/...`).
21-
# Bazel does not rewrite raw argv strings under path mapping, so this
22-
# arg keeps the un-mapped configuration prefix even when the rest of
23-
# the Rustc command uses `bazel-out/cfg/bin/...`. The action will
24-
# fail at execution time under path mapping because the file is
25-
# materialized at the mapped path; we accept that as documented in
26-
# the Rust action implementation.
27-
assert_argv_contains(env, action, "@${pwd}/" + ctx.bin_dir.path + "/test/unit/location_expansion/generated_flag.data")
26+
# All four forms must be prefixed with @${pwd}/ so that process_wrapper
27+
# can resolve them to absolute paths at run time. Each form references a
28+
# distinct generated file so every assertion targets a unique path.
29+
base = "@${pwd}/" + ctx.bin_dir.path + "/test/unit/location_expansion/"
30+
assert_argv_contains(env, action, base + "flag_execpath.data")
31+
assert_argv_contains(env, action, base + "flag_execpaths.data")
32+
assert_argv_contains(env, action, base + "flag_location.data")
33+
assert_argv_contains(env, action, base + "flag_locations.data")
2834
return analysistest.end(env)
2935

3036
location_expansion_rustc_flags_test = analysistest.make(_location_expansion_rustc_flags_test)
3137

38+
def _location_expansion_build_script_env_test(ctx):
39+
env = analysistest.begin(ctx)
40+
tut = analysistest.target_under_test(env)
41+
action = _find_action(tut, "CargoBuildScriptRun")
42+
expected = "${pwd}/" + ctx.bin_dir.path + "/test/unit/location_expansion/flag_execpaths.data"
43+
assert_env_value(env, action, "MY_DATA", expected)
44+
return analysistest.end(env)
45+
46+
location_expansion_build_script_env_test = analysistest.make(_location_expansion_build_script_env_test)
47+
3248
def _location_expansion_test():
33-
write_file(
34-
name = "flag_generator",
35-
out = "generated_flag.data",
36-
content = [
37-
"--cfg=test_flag",
38-
"",
39-
],
40-
newline = "unix",
41-
)
49+
for suffix in ("execpath", "execpaths", "location", "locations"):
50+
write_file(
51+
name = "flag_generator_" + suffix,
52+
out = "flag_" + suffix + ".data",
53+
content = [
54+
"--cfg=test_flag",
55+
"",
56+
],
57+
newline = "unix",
58+
)
4259

4360
rust_library(
4461
name = "mylibrary",
4562
srcs = ["mylibrary.rs"],
4663
edition = "2018",
4764
rustc_flags = [
48-
"@$(execpath :flag_generator)",
65+
"@$(execpath :flag_generator_execpath)",
66+
"@$(execpaths :flag_generator_execpaths)",
67+
"@$(location :flag_generator_location)",
68+
"@$(locations :flag_generator_locations)",
69+
],
70+
compile_data = [
71+
":flag_generator_execpath",
72+
":flag_generator_execpaths",
73+
":flag_generator_location",
74+
":flag_generator_locations",
4975
],
50-
compile_data = [":flag_generator"],
76+
)
77+
78+
cargo_build_script(
79+
name = "mybuildscript",
80+
srcs = ["build_script.rs"],
81+
edition = "2018",
82+
data = [":flag_generator_execpaths"],
83+
build_script_env = {
84+
"MY_DATA": "$(execpaths :flag_generator_execpaths)",
85+
},
5186
)
5287

5388
location_expansion_rustc_flags_test(
5489
name = "location_expansion_rustc_flags_test",
5590
target_under_test = ":mylibrary",
5691
)
5792

93+
location_expansion_build_script_env_test(
94+
name = "location_expansion_build_script_env_test",
95+
target_under_test = ":mybuildscript",
96+
)
97+
5898
def location_expansion_test_suite(name):
5999
"""Entry-point macro called from the BUILD file.
60100
@@ -67,5 +107,6 @@ def location_expansion_test_suite(name):
67107
name = name,
68108
tests = [
69109
":location_expansion_rustc_flags_test",
110+
":location_expansion_build_script_env_test",
70111
],
71112
)

0 commit comments

Comments
 (0)