Suppose I am having the following sh_binary
:
sh_binary(
name = "tool_wrapper",
srcs = ["tool_wrapper.sh"],
data = ["@external//tools:binaries"],
args = ["$(locations @external//tools:binaries)"],
)
Now I want to use this tool in a custom rule for generating sources. Is there a way to completely encapsulate this detail such that dependent rules don't have to know this information? I don't want to depend on hard coded paths in the wrapper script because the used binaries, or more specifically the target label, might change. This is the reason why I am using the $(locations ...)
substitution and let bazel figuring out the paths.
The arguments in args
are used only when the target is used with the bazel run
and bazel test
commands: https://bazel.build/reference/be/common-definitions#binary.args, so to accomplish this you'll need a Starlark rule that does this for you:
WORKSPACE
:
workspace(name = "my_workspace")
local_repository(
name = "other-repo",
path = "other-repo",
)
pkg/BUILD
:
load(":tool_path_wrapper.bzl", "tool_path_wrapper")
load(":my_rule.bzl", "my_rule")
my_rule(
name = "foo",
src = "foo.txt",
)
tool_path_wrapper(
name = "tool_wrapper",
binary = ":tool",
tools = ["subtool1", "subtool2", "@other-repo//:tool-in-other-repo"],
)
sh_binary(
name = "tool",
srcs = [":tool.sh"],
data = ["subtool1", "subtool2", "@other-repo//:tool-in-other-repo"],
)
sh_binary(
name = "subtool1",
srcs = ["subtool1.sh"],
)
sh_binary(
name = "subtool2",
srcs = ["subtool2.sh"],
)
pkg/foo.txt
:
foo
pkg/my_rule.bzl
:
def _my_rule_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name + "_output")
ctx.actions.run(
inputs = [ctx.file.src],
executable = ctx.attr._tool.files_to_run,
outputs = [out],
arguments = [ctx.file.src.path, out.path],
)
return DefaultInfo(files = depset([out]))
my_rule = rule(
implementation = _my_rule_impl,
attrs = {
"src": attr.label(mandatory = True, allow_single_file = True),
"_tool": attr.label(default = "//pkg:tool_wrapper", executable = True, cfg = "exec"),
},
)
pkg/tool_path_wrapper.bzl
:
def _get_executable_runfile_path(ctx, target):
return "$0.runfiles/%s/%s" % (ctx.workspace_name, target.files_to_run.executable.short_path)
def _tool_path_wrapper_impl(ctx):
wrapper = ctx.actions.declare_file(ctx.label.name + ".sh")
executables = [ctx.attr.binary] + ctx.attr.tools
runfiles_paths = [_get_executable_runfile_path(ctx, e) for e in executables]
# write a script that passes the paths of the subtools to the main tool, then the rest of the args
ctx.actions.write(
output = wrapper,
content = " ".join(runfiles_paths) + " $@\n",
is_executable = True,
)
return DefaultInfo(
executable = wrapper,
runfiles = ctx.runfiles().merge_all(
[ctx.attr.binary.default_runfiles] +
[t.default_runfiles for t in ctx.attr.tools])
)
tool_path_wrapper = rule(
implementation = _tool_path_wrapper_impl,
attrs = {
"binary": attr.label(mandatory = True),
"tools": attr.label_list(),
},
executable = True,
)
pkg/tool.sh
:
set -e
subtool1="$1"
subtool2="$2"
subtool3="$3"
in_file="$4"
out_file="$5"
"$subtool1" "$in_file" > "$out_file"
"$subtool2" "$in_file" >> "$out_file"
"$subtool3" "$in_file" >> "$out_file"
pkg/subtool1.sh
:
wc -c $1
pkg/subtool2.sh
:
rev $1
other-repo/WORKSPACE
:
workspace(name = "tool_workspace")
other-repo/BUILD
:
sh_binary(
name = "tool-in-other-repo",
srcs = ["tool-in-other-repo.sh"],
visibility = ["//visibility:public"],
)
other-repo/tool-in-other-repo.sh
:
md5sum $1