run_binary: runs an executable as an action (#153)
This rule is an alternative for genrule(): it can run a binary with the desired arguments, environment, inputs, and outputs, as a single build action, without shelling out to Bash. Fixes https://github.com/bazelbuild/bazel-skylib/issues/149
This commit is contained in:
parent
c585222071
commit
bf8a55b668
|
@ -121,3 +121,10 @@ stardoc(
|
||||||
input = "//rules:native_binary.bzl",
|
input = "//rules:native_binary.bzl",
|
||||||
deps = ["//rules:native_binary"],
|
deps = ["//rules:native_binary"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
stardoc(
|
||||||
|
name = "run_binary_docs",
|
||||||
|
out = "run_binary_doc_gen.md",
|
||||||
|
input = "//rules:run_binary.bzl",
|
||||||
|
deps = ["//rules:run_binary"],
|
||||||
|
)
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
<a name="#run_binary"></a>
|
||||||
|
## run_binary
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
run_binary(<a href="#run_binary-name">name</a>, <a href="#run_binary-args">args</a>, <a href="#run_binary-env">env</a>, <a href="#run_binary-outs">outs</a>, <a href="#run_binary-srcs">srcs</a>, <a href="#run_binary-tool">tool</a>)
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
Runs a binary as a build action.<br/><br/>This rule does not require Bash (unlike <code>native.genrule</code>).
|
||||||
|
|
||||||
|
### Attributes
|
||||||
|
|
||||||
|
<table class="params-table">
|
||||||
|
<colgroup>
|
||||||
|
<col class="col-param" />
|
||||||
|
<col class="col-description" />
|
||||||
|
</colgroup>
|
||||||
|
<tbody>
|
||||||
|
<tr id="run_binary-name">
|
||||||
|
<td><code>name</code></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://bazel.build/docs/build-ref.html#name">Name</a>; required
|
||||||
|
<p>
|
||||||
|
A unique name for this target.
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr id="run_binary-args">
|
||||||
|
<td><code>args</code></td>
|
||||||
|
<td>
|
||||||
|
List of strings; optional
|
||||||
|
<p>
|
||||||
|
Command line arguments of the binary.<br/><br/>Subject to<code><a href="https://docs.bazel.build/versions/master/be/make-variables.html#location">$(location)</a></code> expansion.
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr id="run_binary-env">
|
||||||
|
<td><code>env</code></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://bazel.build/docs/skylark/lib/dict.html">Dictionary: String -> String</a>; optional
|
||||||
|
<p>
|
||||||
|
Environment variables of the action.<br/><br/>Subject to <code><a href="https://docs.bazel.build/versions/master/be/make-variables.html#location">$(location)</a></code> expansion.
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr id="run_binary-outs">
|
||||||
|
<td><code>outs</code></td>
|
||||||
|
<td>
|
||||||
|
List of labels; required
|
||||||
|
<p>
|
||||||
|
Output files generated by the action.<br/><br/>These labels are available for <code>$(location)</code> expansion in <code>args</code> and <code>env</code>.
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr id="run_binary-srcs">
|
||||||
|
<td><code>srcs</code></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://bazel.build/docs/build-ref.html#labels">List of labels</a>; optional
|
||||||
|
<p>
|
||||||
|
Additional inputs of the action.<br/><br/>These labels are available for <code>$(location)</code> expansion in <code>args</code> and <code>env</code>.
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr id="run_binary-tool">
|
||||||
|
<td><code>tool</code></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://bazel.build/docs/build-ref.html#labels">Label</a>; required
|
||||||
|
<p>
|
||||||
|
The tool to run in the action.<br/><br/>Must be the label of a *_binary rule, of a rule that generates an executable file, or of a file that can be executed as a subprocess (e.g. an .exe or .bat file on Windows or a binary with executable permission on Linux). This label is available for <code>$(location)</code> expansion in <code>args</code> and <code>env</code>.
|
||||||
|
</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,12 @@ bzl_library(
|
||||||
deps = ["//rules/private:copy_file_private"],
|
deps = ["//rules/private:copy_file_private"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
bzl_library(
|
||||||
|
name = "run_binary",
|
||||||
|
srcs = ["run_binary.bzl"],
|
||||||
|
deps = ["//lib:dicts"],
|
||||||
|
)
|
||||||
|
|
||||||
# Exported for build_test.bzl to make sure of, it is an implementation detail
|
# Exported for build_test.bzl to make sure of, it is an implementation detail
|
||||||
# of the rule and should not be directly used by anything else.
|
# of the rule and should not be directly used by anything else.
|
||||||
exports_files(["empty_test.sh"])
|
exports_files(["empty_test.sh"])
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
# Copyright 2019 The Bazel Authors. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
run_binary() build rule implementation.
|
||||||
|
|
||||||
|
Runs a binary as a build action. This rule does not require Bash (unlike native.genrule()).
|
||||||
|
"""
|
||||||
|
|
||||||
|
load("//lib:dicts.bzl", "dicts")
|
||||||
|
|
||||||
|
def _impl(ctx):
|
||||||
|
tool_as_list = [ctx.attr.tool]
|
||||||
|
tool_inputs, tool_input_mfs = ctx.resolve_tools(tools = tool_as_list)
|
||||||
|
args = [
|
||||||
|
# Expand $(location) / $(locations) in args.
|
||||||
|
#
|
||||||
|
# To keep the rule simple, do not expand Make Variables (like *_binary.args usually would).
|
||||||
|
# (We can add this feature later if users ask for it.)
|
||||||
|
#
|
||||||
|
# Also for simple implementation and usage, do not Bash-tokenize the arguments. Without
|
||||||
|
# tokenization the user can write args=["a b"] to pass (a b) as one argument, but with
|
||||||
|
# tokenization they would have to write args=["'a b'"] or args=["a\\ b"]. There's no
|
||||||
|
# documented tokenization function anyway (as of 2019-05-21 ctx.tokenize exists but is
|
||||||
|
# undocumented, see https://github.com/bazelbuild/bazel/issues/8389).
|
||||||
|
ctx.expand_location(a, tool_as_list) if "$(location" in a else a
|
||||||
|
for a in ctx.attr.args
|
||||||
|
]
|
||||||
|
envs = {
|
||||||
|
# Expand $(location) / $(locations) in the values.
|
||||||
|
k: ctx.expand_location(v, tool_as_list) if "$(location" in v else v
|
||||||
|
for k, v in ctx.attr.env.items()
|
||||||
|
}
|
||||||
|
ctx.actions.run(
|
||||||
|
outputs = ctx.outputs.outs,
|
||||||
|
inputs = depset(direct = ctx.files.srcs, transitive = [tool_inputs]),
|
||||||
|
executable = ctx.executable.tool,
|
||||||
|
arguments = args,
|
||||||
|
mnemonic = "RunBinary",
|
||||||
|
use_default_shell_env = False,
|
||||||
|
env = dicts.add(ctx.configuration.default_shell_env, envs),
|
||||||
|
input_manifests = tool_input_mfs,
|
||||||
|
)
|
||||||
|
return DefaultInfo(
|
||||||
|
files = depset(items = ctx.outputs.outs),
|
||||||
|
runfiles = ctx.runfiles(files = ctx.outputs.outs),
|
||||||
|
)
|
||||||
|
|
||||||
|
run_binary = rule(
|
||||||
|
implementation = _impl,
|
||||||
|
doc = "Runs a binary as a build action.<br/><br/>This rule does not require Bash (unlike" +
|
||||||
|
" <code>native.genrule</code>).",
|
||||||
|
attrs = {
|
||||||
|
"tool": attr.label(
|
||||||
|
doc = "The tool to run in the action.<br/><br/>Must be the label of a *_binary rule," +
|
||||||
|
" of a rule that generates an executable file, or of a file that can be" +
|
||||||
|
" executed as a subprocess (e.g. an .exe or .bat file on Windows or a binary" +
|
||||||
|
" with executable permission on Linux). This label is available for" +
|
||||||
|
" <code>$(location)</code> expansion in <code>args</code> and <code>env</code>.",
|
||||||
|
executable = True,
|
||||||
|
allow_files = True,
|
||||||
|
mandatory = True,
|
||||||
|
cfg = "host",
|
||||||
|
),
|
||||||
|
"env": attr.string_dict(
|
||||||
|
doc = "Environment variables of the action.<br/><br/>Subject to " +
|
||||||
|
" <code><a href=\"https://docs.bazel.build/versions/master/be/make-variables.html#location\">$(location)</a></code>" +
|
||||||
|
" expansion.",
|
||||||
|
),
|
||||||
|
"srcs": attr.label_list(
|
||||||
|
allow_files = True,
|
||||||
|
doc = "Additional inputs of the action.<br/><br/>These labels are available for" +
|
||||||
|
" <code>$(location)</code> expansion in <code>args</code> and <code>env</code>.",
|
||||||
|
),
|
||||||
|
"outs": attr.output_list(
|
||||||
|
mandatory = True,
|
||||||
|
doc = "Output files generated by the action.<br/><br/>These labels are available for" +
|
||||||
|
" <code>$(location)</code> expansion in <code>args</code> and <code>env</code>.",
|
||||||
|
),
|
||||||
|
"args": attr.string_list(
|
||||||
|
doc = "Command line arguments of the binary.<br/><br/>Subject to" +
|
||||||
|
"<code><a href=\"https://docs.bazel.build/versions/master/be/make-variables.html#location\">$(location)</a></code>" +
|
||||||
|
" expansion.",
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
|
@ -0,0 +1,166 @@
|
||||||
|
load("//rules:diff_test.bzl", "diff_test")
|
||||||
|
load("//rules:run_binary.bzl", "run_binary")
|
||||||
|
load("//rules:write_file.bzl", "write_file")
|
||||||
|
|
||||||
|
package(
|
||||||
|
default_testonly = 1,
|
||||||
|
default_visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
diff_test(
|
||||||
|
name = "run_script_test",
|
||||||
|
file1 = ":run_script.out",
|
||||||
|
file2 = ":run_script_expected",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generate this file with write_file instead of checking it in to the source
|
||||||
|
# tree. This ensures line endings are consistent across "run_script.expected"
|
||||||
|
# and "run_script.out".
|
||||||
|
write_file(
|
||||||
|
name = "run_script_expected",
|
||||||
|
out = "run_script.expected",
|
||||||
|
content = [
|
||||||
|
"arg1=(foo)",
|
||||||
|
"arg2=(bar)",
|
||||||
|
"ENV_LOCATION=(a tests/run_binary/BUILD)",
|
||||||
|
"ENV_LOCATIONS=(b\\ tests/run_binary/BUILD tests/run_binary/printargs.cc)",
|
||||||
|
"ENV_COMPLEX=(xx/yy \\\"zz)",
|
||||||
|
"ENV_PATH_BASH=($PATH)",
|
||||||
|
"ENV_PATH_CMD=(%PATH%)",
|
||||||
|
# Can't prevent "echo" from adding a newline on Windows, so let's add
|
||||||
|
# one to the expected output too.
|
||||||
|
"",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
run_binary(
|
||||||
|
name = "run_script",
|
||||||
|
srcs = [
|
||||||
|
"BUILD",
|
||||||
|
":dummy_srcs",
|
||||||
|
],
|
||||||
|
outs = ["run_script.out"],
|
||||||
|
# Not testing any complex arguments here, because Windows .bat file argument
|
||||||
|
# escaping is different from most MSVC-built Windows binaries. We test
|
||||||
|
# argument escaping in "run_bin".
|
||||||
|
args = [
|
||||||
|
"foo",
|
||||||
|
"bar",
|
||||||
|
],
|
||||||
|
# Test complex environment variables. They are location-expanded but not
|
||||||
|
# Bash-tokenized, and should appear the same for Windows .bat files and Bash
|
||||||
|
# .sh scripts.
|
||||||
|
env = {
|
||||||
|
# Testing $(location) expansion only on source files so the result is
|
||||||
|
# predictable. The path of generated files depends on the target
|
||||||
|
# platform.
|
||||||
|
"ENV_LOCATION": "a $(location BUILD)",
|
||||||
|
"ENV_LOCATIONS": "b\\ $(locations :dummy_srcs)",
|
||||||
|
"ENV_COMPLEX": "xx/yy \\\"zz",
|
||||||
|
"ENV_PATH_BASH": "$PATH",
|
||||||
|
"ENV_PATH_CMD": "%PATH%",
|
||||||
|
"OUT": "$(location run_script.out)",
|
||||||
|
},
|
||||||
|
tool = ":script",
|
||||||
|
)
|
||||||
|
|
||||||
|
write_file(
|
||||||
|
name = "script",
|
||||||
|
# On Windows we need the ".bat" extension.
|
||||||
|
# On other platforms the extension doesn't matter.
|
||||||
|
# Therefore we can use ".bat" on every platform.
|
||||||
|
out = "script.bat",
|
||||||
|
content = select({
|
||||||
|
"@bazel_tools//src/conditions:host_windows": [
|
||||||
|
"@echo>%OUT% arg1=(%1)",
|
||||||
|
"@echo>>%OUT% arg2=(%2)",
|
||||||
|
"@echo>>%OUT% ENV_LOCATION=(%ENV_LOCATION%)",
|
||||||
|
"@echo>>%OUT% ENV_LOCATIONS=(%ENV_LOCATIONS%)",
|
||||||
|
"@echo>>%OUT% ENV_COMPLEX=(%ENV_COMPLEX%)",
|
||||||
|
"@echo>>%OUT% ENV_PATH_BASH=(%ENV_PATH_BASH%)",
|
||||||
|
"@echo>>%OUT% ENV_PATH_CMD=(%ENV_PATH_CMD%)",
|
||||||
|
],
|
||||||
|
"//conditions:default": [
|
||||||
|
"#!/bin/bash",
|
||||||
|
"echo > \"$OUT\" \"arg1=($1)\"",
|
||||||
|
"echo >> \"$OUT\" \"arg2=($2)\"",
|
||||||
|
"echo >> \"$OUT\" \"ENV_LOCATION=($ENV_LOCATION)\"",
|
||||||
|
"echo >> \"$OUT\" \"ENV_LOCATIONS=($ENV_LOCATIONS)\"",
|
||||||
|
"echo >> \"$OUT\" \"ENV_COMPLEX=($ENV_COMPLEX)\"",
|
||||||
|
"echo >> \"$OUT\" \"ENV_PATH_BASH=($ENV_PATH_BASH)\"",
|
||||||
|
"echo >> \"$OUT\" \"ENV_PATH_CMD=($ENV_PATH_CMD)\"",
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
is_executable = True,
|
||||||
|
)
|
||||||
|
|
||||||
|
diff_test(
|
||||||
|
name = "run_bin_test",
|
||||||
|
file1 = ":run_bin.out",
|
||||||
|
file2 = ":run_bin_expected",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generate this file with write_file instead of checking it in to the source
|
||||||
|
# tree. This ensures line endings are consistent across "run_bin.expected"
|
||||||
|
# and "run_bin.out".
|
||||||
|
write_file(
|
||||||
|
name = "run_bin_expected",
|
||||||
|
out = "run_bin.expected",
|
||||||
|
content = [
|
||||||
|
"arg1=(a b)",
|
||||||
|
"arg2=(\"c d\")",
|
||||||
|
"arg3=(e\\ f)",
|
||||||
|
"arg4=(xx/yy\\ \\\"zz)",
|
||||||
|
"arg5=(tests/run_binary/BUILD)",
|
||||||
|
"arg6=(tests/run_binary/BUILD tests/run_binary/printargs.cc)",
|
||||||
|
"arg7=('tests/run_binary/BUILD $tests/run_binary/BUILD')",
|
||||||
|
"arg8=($PATH)",
|
||||||
|
"arg9=($$PATH)",
|
||||||
|
"arg10=(${PATH})",
|
||||||
|
# Add trailing newline, as printed by printargs.
|
||||||
|
"",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
run_binary(
|
||||||
|
name = "run_bin",
|
||||||
|
srcs = [
|
||||||
|
"BUILD",
|
||||||
|
":dummy_srcs",
|
||||||
|
],
|
||||||
|
outs = ["run_bin.out"],
|
||||||
|
# Test complex arguments here. They are location-expanded but not
|
||||||
|
# Bash-tokenized, and should appear the same on every platform.
|
||||||
|
args = [
|
||||||
|
"a b",
|
||||||
|
"\"c d\"",
|
||||||
|
"e\\ f",
|
||||||
|
"xx/yy\\ \\\"zz",
|
||||||
|
# Testing $(location) expansion only on source files so the result is
|
||||||
|
# predictable. The path of generated files depends on the target
|
||||||
|
# platform.
|
||||||
|
"$(location BUILD)",
|
||||||
|
"$(locations :dummy_srcs)",
|
||||||
|
"'$(location BUILD) $$(location BUILD)'",
|
||||||
|
"$PATH",
|
||||||
|
"$$PATH",
|
||||||
|
"${PATH}",
|
||||||
|
],
|
||||||
|
# Not testing any complex envvars here, because we already did in
|
||||||
|
# "run_script".
|
||||||
|
env = {"OUT": "$(location run_bin.out)"},
|
||||||
|
tool = ":printargs",
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "dummy_srcs",
|
||||||
|
srcs = [
|
||||||
|
"BUILD",
|
||||||
|
"printargs.cc",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_binary(
|
||||||
|
name = "printargs",
|
||||||
|
srcs = ["printargs.cc"],
|
||||||
|
)
|
|
@ -0,0 +1,36 @@
|
||||||
|
// Copyright 2019 The Bazel Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
char* out_path = getenv("OUT");
|
||||||
|
if (!out_path || !*out_path) {
|
||||||
|
fprintf(stderr, "ERROR(" __FILE__ ":%d): envvar OUT is undefined\n",
|
||||||
|
__LINE__);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
FILE* f = fopen(out_path, "wt");
|
||||||
|
if (!f) {
|
||||||
|
fprintf(stderr, "ERROR(" __FILE__ ":%d): could not open output file '%s'\n",
|
||||||
|
__LINE__, out_path);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
for (int i = 1; i < argc; ++i) {
|
||||||
|
fprintf(f, "arg%d=(%s)\n", i, argv[i]);
|
||||||
|
}
|
||||||
|
fclose(f);
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
Reference in New Issue