Search code examples
bazelbazel-rules

How to properly handle args in sh_binary target?


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.


Solution

  • 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