fix(cargo_build_script): expand ${pwd} for execpaths and locations in build_script_env (#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}/`.diff --git a/rust/private/utils.bzl b/rust/private/utils.bzl
index eac3948..e54e513 100644
--- a/rust/private/utils.bzl
+++ b/rust/private/utils.bzl
@@ -286,6 +286,26 @@
if directive in v:
# build script runner will expand pwd to execroot for us
v = v.replace(directive, "$${pwd}/" + directive)
+
+ for directive in ("$(execpaths ", "$(locations "):
+ if directive in v:
+ # Plural forms expand to multiple space-separated paths, so we must
+ # expand each macro individually and prefix every resulting path.
+ # Split on the opening directive; each subsequent part begins with
+ # "label)rest", letting us reconstruct and expand one macro at a time.
+ parts = v.split(directive)
+ result = parts[0]
+ for part in parts[1:]:
+ end = part.find(")")
+ if end == -1:
+ result += directive + part
+ continue
+ macro = directive + part[:end] + ")"
+ expanded = ctx.expand_location(macro, data)
+ prefixed = " ".join(["$${pwd}/" + p for p in expanded.split(" ")])
+ result += prefixed + part[end + 1:]
+ v = result
+
return ctx.expand_make_variables(
v,
ctx.expand_location(v, data),
@@ -295,11 +315,14 @@
def expand_dict_value_locations(ctx, env, data, known_variables):
"""Performs location-macro expansion on string values.
- $(execroot ...) and $(location ...) are prefixed with ${pwd},
- which process_wrapper and build_script_runner will expand at run time
- to the absolute path. This is necessary because include_str!() is relative
- to the currently compiled file, and build scripts run relative to the
- manifest dir, so we can not use execroot-relative paths.
+ $(execpath ...), $(execpaths ...), $(location ...) and $(locations ...) are
+ prefixed with ${pwd}, which process_wrapper and build_script_runner will
+ expand at run time to the absolute path.
+ This is necessary because include_str!() is relative to the currently
+ compiled file, and build scripts run relative to the manifest dir, so we
+ can not use execroot-relative paths.
+ Plural forms (execpaths/locations) expand to multiple space-separated paths;
+ each path receives its own ${pwd}/ prefix.
$(rootpath ...) is unmodified, and is useful for passing in paths via
rustc_env that are encoded in the binary with env!(), but utilized at
diff --git a/test/unit/location_expansion/build_script.rs b/test/unit/location_expansion/build_script.rs
new file mode 100644
index 0000000..67475ee
--- /dev/null
+++ b/test/unit/location_expansion/build_script.rs
@@ -0,0 +1,8 @@
+fn main() {
+ let path = std::env::var("MY_DATA").expect("MY_DATA env var must be set");
+ assert!(
+ std::path::Path::new(&path).exists(),
+ "MY_DATA path does not exist: {}",
+ path
+ );
+}
diff --git a/test/unit/location_expansion/location_expansion_test.bzl b/test/unit/location_expansion/location_expansion_test.bzl
index 354735e..3c63355 100644
--- a/test/unit/location_expansion/location_expansion_test.bzl
+++ b/test/unit/location_expansion/location_expansion_test.bzl
@@ -2,8 +2,15 @@
load("@bazel_skylib//lib:unittest.bzl", "analysistest")
load("@bazel_skylib//rules:write_file.bzl", "write_file")
+load("//cargo:defs.bzl", "cargo_build_script")
load("//rust:defs.bzl", "rust_library")
-load("//test/unit:common.bzl", "assert_action_mnemonic", "assert_argv_contains")
+load("//test/unit:common.bzl", "assert_action_mnemonic", "assert_argv_contains", "assert_env_value")
+
+def _find_action(tut, mnemonic):
+ for action in tut.actions:
+ if action.mnemonic == mnemonic:
+ return action
+ return None
def _location_expansion_rustc_flags_test(ctx):
env = analysistest.begin(ctx)
@@ -11,43 +18,71 @@
action = tut.actions[1]
assert_action_mnemonic(env, action, "Rustc")
- # Because target `rustc_flags` use `$(execpath ...)`, the action does
+ # Because target `rustc_flags` use location macros, the action does
# not advertise `supports-path-mapping`, so file paths remain at their
# configuration-specific `ctx.bin_dir` locations.
assert_argv_contains(env, action, ctx.bin_dir.path + "/test/unit/location_expansion/mylibrary.rs")
- # `$(location ...)` is expanded at analysis time into a literal
- # configuration-dependent string (`bazel-out/<config>/bin/...`).
- # Bazel does not rewrite raw argv strings under path mapping, so this
- # arg keeps the un-mapped configuration prefix even when the rest of
- # the Rustc command uses `bazel-out/cfg/bin/...`. The action will
- # fail at execution time under path mapping because the file is
- # materialized at the mapped path; we accept that as documented in
- # the Rust action implementation.
- assert_argv_contains(env, action, "@${pwd}/" + ctx.bin_dir.path + "/test/unit/location_expansion/generated_flag.data")
+ # All four forms must be prefixed with @${pwd}/ so that process_wrapper
+ # can resolve them to absolute paths at run time. Each form references a
+ # distinct generated file so every assertion targets a unique path.
+ base = "@${pwd}/" + ctx.bin_dir.path + "/test/unit/location_expansion/"
+ assert_argv_contains(env, action, base + "flag_execpath.data")
+ assert_argv_contains(env, action, base + "flag_execpaths.data")
+ assert_argv_contains(env, action, base + "flag_location.data")
+ assert_argv_contains(env, action, base + "flag_locations.data")
return analysistest.end(env)
location_expansion_rustc_flags_test = analysistest.make(_location_expansion_rustc_flags_test)
+def _location_expansion_build_script_env_test(ctx):
+ env = analysistest.begin(ctx)
+ tut = analysistest.target_under_test(env)
+ action = _find_action(tut, "CargoBuildScriptRun")
+ expected = "${pwd}/" + ctx.bin_dir.path + "/test/unit/location_expansion/flag_execpaths.data"
+ assert_env_value(env, action, "MY_DATA", expected)
+ return analysistest.end(env)
+
+location_expansion_build_script_env_test = analysistest.make(_location_expansion_build_script_env_test)
+
def _location_expansion_test():
- write_file(
- name = "flag_generator",
- out = "generated_flag.data",
- content = [
- "--cfg=test_flag",
- "",
- ],
- newline = "unix",
- )
+ for suffix in ("execpath", "execpaths", "location", "locations"):
+ write_file(
+ name = "flag_generator_" + suffix,
+ out = "flag_" + suffix + ".data",
+ content = [
+ "--cfg=test_flag",
+ "",
+ ],
+ newline = "unix",
+ )
rust_library(
name = "mylibrary",
srcs = ["mylibrary.rs"],
edition = "2018",
rustc_flags = [
- "@$(execpath :flag_generator)",
+ "@$(execpath :flag_generator_execpath)",
+ "@$(execpaths :flag_generator_execpaths)",
+ "@$(location :flag_generator_location)",
+ "@$(locations :flag_generator_locations)",
],
- compile_data = [":flag_generator"],
+ compile_data = [
+ ":flag_generator_execpath",
+ ":flag_generator_execpaths",
+ ":flag_generator_location",
+ ":flag_generator_locations",
+ ],
+ )
+
+ cargo_build_script(
+ name = "mybuildscript",
+ srcs = ["build_script.rs"],
+ edition = "2018",
+ data = [":flag_generator_execpaths"],
+ build_script_env = {
+ "MY_DATA": "$(execpaths :flag_generator_execpaths)",
+ },
)
location_expansion_rustc_flags_test(
@@ -55,6 +90,11 @@
target_under_test = ":mylibrary",
)
+ location_expansion_build_script_env_test(
+ name = "location_expansion_build_script_env_test",
+ target_under_test = ":mybuildscript",
+ )
+
def location_expansion_test_suite(name):
"""Entry-point macro called from the BUILD file.
@@ -67,5 +107,6 @@
name = name,
tests = [
":location_expansion_rustc_flags_test",
+ ":location_expansion_build_script_env_test",
],
)