Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions rust/private/utils.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,26 @@ def _expand_location_for_build_script_runner(ctx, v, data, known_variables):
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),
Expand All @@ -295,11 +315,14 @@ def _expand_location_for_build_script_runner(ctx, v, data, known_variables):
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
Expand Down
8 changes: 8 additions & 0 deletions test/unit/location_expansion/build_script.rs
Original file line number Diff line number Diff line change
@@ -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
);
}
85 changes: 63 additions & 22 deletions test/unit/location_expansion/location_expansion_test.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -2,59 +2,99 @@

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)
tut = analysistest.target_under_test(env)
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_execpath",
":flag_generator_execpaths",
":flag_generator_location",
":flag_generator_locations",
],
compile_data = [":flag_generator"],
)

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(
name = "location_expansion_rustc_flags_test",
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.

Expand All @@ -67,5 +107,6 @@ def location_expansion_test_suite(name):
name = name,
tests = [
":location_expansion_rustc_flags_test",
":location_expansion_build_script_env_test",
],
)