豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
Skip to content

Commit 7605373

Browse files
authored
feat(bazel): allow integration commands to resolve executables through expansion (#285)
* feat(bazel): allow integration commands to resolve executables through expansion Gives a tool built using `nodejs_binary` or `sh_binary`, the tool cannot be used as binary in an integration test command currently because: * Bazel location expansion does not respect the executable "FilesToRun" information of the binaries, and rather sees _all_ outputs, erroring that `$(locations X)` need to be used instead. See e.g. bazelbuild/bazel#11820) * The command is currently split by space and this decouples the Make funtion expression incorrectly into a broken expansion. e.g. `$(rootpath X)` will become `["$(rootpath", "X"]`). This commit fixes both things by relying on the Bazel `CommandHelper.java` class which handles the expansion of commands as expected, with similar semantics of a `genrule` as per `GenRuleBase.java`. This allows test authors to conveniently use a `nodejs_binary` or `sh_binary` as command binary because the `CommandHelper` knows exactly which targets provide executables or not. There is no good API for the plain Make expansion of a command itself, so we use a little trick (which is documented sufficiently in the code). * fixup! feat(bazel): allow integration commands to resolve executables through expansion Address feedback
1 parent c1dbbba commit 7605373

File tree

5 files changed

+83
-15
lines changed

5 files changed

+83
-15
lines changed

bazel/integration/index.bzl

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ def _serialize_file(file):
66

77
return struct(path = file.path, shortPath = file.short_path)
88

9+
def _create_expanded_value(value, isExpanded):
10+
"""Creates a JSON serializable dictionary matching the `BazelExpandedValue` type in
11+
the test runner."""
12+
return {
13+
"value": value,
14+
"containsExpansion": isExpanded,
15+
}
16+
917
def _serialize_and_expand_value(ctx, value, description):
1018
"""Expands Bazel make variable and location expressions for the given value. Returns a JSON
1119
serializable dictionary matching the `BazelExpandedValue` type in the test runner."""
@@ -17,14 +25,51 @@ def _serialize_and_expand_value(ctx, value, description):
1725
# directly use `ctx.var` but would have switch users from e.g. `$(VAR)` to `{VAR}`.
1826
expanded_make_value = ctx.expand_make_variables(description, expanded_location_value, {})
1927

20-
return {
21-
"value": expanded_make_value,
22-
"containsExpansion": expanded_make_value != value,
23-
}
28+
return _create_expanded_value(expanded_make_value, expanded_make_value != value)
29+
30+
def _expand_and_split_command(ctx, command):
31+
"""Expands a command using the Bazel command helper. The command is then split into the
32+
binary and its arguments, matching the runner `[BazelExpandedValue, ...string[]]` type."""
33+
34+
# Instead of manually resolving the command using e.g. `ctx.expand_location`, we use
35+
# the Bazel `resolve_command` helper which internally follows the semantics of a `genrule`,
36+
# allowing for better expansion/resolution of tools provided in the `data` attribute.
37+
# This is necessary so that e.g. executables from a `sh_binary` can be conveniently
38+
# expanded through `$(rootpath <label>`). Note that the same would not work with
39+
# `ctx.expand_location` as a `sh_binary` exposes multiple files causing an error like:
40+
# --> `expression expands to more than one file, please use $(locations ...)`.
41+
# The Bazel command helper utility instead (which is also used by the genrule internally),
42+
# will be able to determine the executable (based on the current exec platform) and expand it.
43+
# https://cs.opensource.google/bazel/bazel/+/master:src/main/java/com/google/devtools/build/lib/analysis/CommandHelper.java;l=175-199;drc=2255ce4165f936f695111020fa664b259a875c4a.
44+
inputs, resolved_bash_command, manifests = ctx.resolve_command(
45+
command = command,
46+
attribute = "command (%s)" % command,
47+
expand_locations = True,
48+
make_variables = ctx.var,
49+
tools = ctx.attr.data,
50+
)
51+
52+
# If the resolved command does not have three arguments, then there were too many arguments
53+
# and Bazel extracted the command into a dedicated Bash script that we cannot read here.
54+
# https://cs.opensource.google/bazel/bazel/+/master:src/main/java/com/google/devtools/build/lib/analysis/BashCommandConstructor.java;l=40;drc=2255ce4165f936f695111020fa664b259a875c4a.
55+
# https://cs.opensource.google/bazel/bazel/+/master:src/main/java/com/google/devtools/build/lib/analysis/CommandHelper.java;l=275-282;drc=2255ce4165f936f695111020fa664b259a875c4a.
56+
if len(resolved_bash_command) != 3:
57+
fail("Test command too long. Please use a shorter one, or extract this " +
58+
"into a dedicated script: %s" % command)
59+
60+
# The third argument of the resolved `bash -c` command will hold the actually expanded command.
61+
# We extract the command since we were only interested in the proper expansion of tools.
62+
# https://cs.opensource.google/bazel/bazel/+/master:src/main/java/com/google/devtools/build/lib/analysis/BashCommandConstructor.java;l=40;drc=2255ce4165f936f695111020fa664b259a875c4a.
63+
resolved_command = resolved_bash_command[2].split(" ")
64+
resolved_binary = resolved_command[0]
65+
original_binary = command.split(" ", 1)[0]
66+
67+
# If the resolved command binary does not match the binary from the original command, then
68+
# we know a Make expression has been expanded and we capture that in a `BazelExpandedValue`.
69+
if resolved_binary != original_binary:
70+
return [_create_expanded_value(resolved_binary, True)] + resolved_command[1:]
2471

25-
def _split_and_expand_command(ctx, command):
26-
"""Splits a command into the binary and its arguments. Bazel make expression are expanded."""
27-
return [_serialize_and_expand_value(ctx, v, "command") for v in command.split(" ", 1)]
72+
return [_create_expanded_value(resolved_binary, False)] + resolved_command[1:]
2873

2974
def _serialize_and_expand_environment(ctx, environment_dict):
3075
"""Converts the given environment dictionary into a JSON-serializable dictionary
@@ -75,7 +120,7 @@ def _integration_test_config_impl(ctx):
75120
config = struct(
76121
testPackage = ctx.label.package,
77122
testFiles = [_serialize_file(f) for f in ctx.files.srcs],
78-
commands = [_split_and_expand_command(ctx, c) for c in ctx.attr.commands],
123+
commands = [_expand_and_split_command(ctx, c) for c in ctx.attr.commands],
79124
environment = _serialize_and_expand_environment(ctx, ctx.attr.environment),
80125
npmPackageMappings = npmPackageMappings,
81126
toolMappings = toolMappings,

bazel/integration/test_runner/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ interface TestConfig {
2020
testFiles: BazelFileInfo[];
2121
npmPackageMappings: Record<string, BazelFileInfo>;
2222
toolMappings: Record<string, BazelFileInfo>;
23-
commands: [[binary: BazelExpandedValue, ...args: BazelExpandedValue[]]];
23+
commands: [[binary: BazelExpandedValue, ...args: string[]]];
2424
environment: Record<string, BazelExpandedValue>;
2525
}
2626

bazel/integration/test_runner/runner.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export class TestRunner {
4444
private readonly testPackage: string,
4545
private readonly toolMappings: Record<string, BazelFileInfo>,
4646
private readonly npmPackageMappings: Record<string, BazelFileInfo>,
47-
private readonly commands: [[binary: BazelExpandedValue, ...args: BazelExpandedValue[]]],
47+
private readonly commands: [[binary: BazelExpandedValue, ...args: string[]]],
4848
private readonly environment: Record<string, BazelExpandedValue>,
4949
) {}
5050

@@ -218,10 +218,7 @@ export class TestRunner {
218218
const resolvedBinary = binary.containsExpansion
219219
? await resolveBinaryWithRunfiles(binary.value)
220220
: binary.value;
221-
const evaluatedArgs = expandEnvironmentVariableSubstitutions(
222-
args.map((v) => v.value),
223-
commandEnv,
224-
);
221+
const evaluatedArgs = expandEnvironmentVariableSubstitutions(args, commandEnv);
225222
const success = await runCommandInChildProcess(
226223
resolvedBinary,
227224
evaluatedArgs,
@@ -231,7 +228,7 @@ export class TestRunner {
231228

232229
if (!success) {
233230
throw Error(
234-
`Integration test command: \`${binary.value} ${evaluatedArgs.join(' ')}\` failed. ` +
231+
`Integration test command: \`${resolvedBinary} ${evaluatedArgs.join(' ')}\` failed. ` +
235232
`See error output above for details.`,
236233
);
237234
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
load("//bazel/integration:index.bzl", "integration_test")
2+
3+
sh_binary(
4+
name = "external_script",
5+
srcs = ["external_script.sh"],
6+
)
7+
8+
integration_test(
9+
name = "test",
10+
srcs = [],
11+
commands = [
12+
"$(rootpath :external_script) First Second",
13+
],
14+
data = [":external_script"],
15+
)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/usr/bin/env bash
2+
3+
if [[ "$1" != "First" ]]; then
4+
echo "First argument not matching: $1"
5+
exit 1
6+
fi
7+
8+
if [[ "$2" != "Second" ]]; then
9+
echo "Second argument not matching: $2"
10+
exit 1
11+
fi

0 commit comments

Comments
 (0)