How do I make a bazel `sh_binary` target depend on other binary targets?

An easier approach for me is to add the cc_binary as a dependency in the data section. In prefix/BUILD

cc_binary(name = "foo", ...)
sh_test(name = "foo_test", srcs = ["foo_test.sh"], data = [":foo"])

Inside foo_test.sh, the working directory is different, so you need to find the right prefix for the binary

#! /usr/bin/env bash

executable=prefix/foo

$executable ...

You can either create a genrule to run these tools as part of the build, or create a sh_binary that depends on the tools via the data attribute and runs them them.

The genrule approach

This is the easier way and lets you run the tools as part of the build.

genrule(
    name = "foo",
    tools = [
        "//tool_a:py",
        "//tool_b:cc",
    ],
    srcs = [
        "//source:file1",
        ":file2",
    ],
    outs = [
        "output_file1",
        "output_file2",
    ],
    cmd = "$(location //tool_a:py) --input=$(location //source:file1) --output=$(location output_file1) && $(location //tool_b:cc) < $(location :file2) > $(location output_file2)",
)

The sh_binary approach

This is more complicated, but lets you run the sh_binary either as part of the build (if it is in a genrule.tools, similar to the previous approach) or after the build (from under bazel-bin).

In the sh_binary you have to data-depend on the tools:

sh_binary(
    name = "foo",
    srcs = ["my_shbin.sh"],
    data = [
        "//tool_a:py",
        "//tool_b:cc",
    ],
)

Then, in the sh_binary you have to use the so-called "Bash runfiles library" built into Bazel to look up the runtime-path of the binaries. This library's documentation is in its source file.

The idea is:

  1. the sh_binary has to depend on a specific target
  2. you have to copy-paste some boilerplate code to the top of the sh_binary (reason is described here)
  3. then you can use the rlocation function to look up the runtime-path of the binaries

For example your my_shbin.sh may look like this:

#!/bin/bash
# --- begin runfiles.bash initialization ---
...
# --- end runfiles.bash initialization ---

path=$(rlocation "__main__/tool_a/py")
if [[ ! -f "${path:-}" ]]; then
  echo >&2 "ERROR: could not look up the Python tool path"
  exit 1
fi
$path --input=$1 --output=$2

The __main__ in the rlocation path argument is the name of the workspace. Since your WORKSPACE file does not have a "workspace" rule in, which would define the workspace's name, Bazel will use the default workspace name, which is __main__.

Tags:

Bazel