Search code examples
bazel

Can I query for a set of tags?


I want to run a set of Bazel targets. My BUILD file look like this

load("@rules_java//java:defs.bzl", "java_binary")

java_binary(
    name = "run_me",
    srcs = glob(["src1/main/java/com/example/*.java"]),
    tags = ["java", "good"],
)

java_binary(
    name = "me_also",
    srcs = glob(["src2/main/java/com/example/*.java"]),
    tags = ["java", "good"],
)

java_binary(
    name = "do_not_run_me",
    srcs = glob(["src3/main/java/com/example/*.java"]),
    tags = ["java", "bad"],
)

I want to run all the targets tagged as "good". I'm hoping to be able to do something like

bazel query 'tags(["good"], //...)'

I don't see the ability to query for tags in the documentation. I do see attr, so I tried

bazel query 'attr(tags, "\[good\]", //...)'

But that doesn't work. I assume because

List-type attributes (such as srcs, data, etc) are converted to strings of the form [value1, ..., valuen], starting with a [ bracket, ending with a ] bracket and using ", " (comma, space) to delimit multiple values

I am able to make it work with an exact match,

bazel query 'attr(tags, "\[good, java\]", //...)'

However, notice that

  1. The ordering of the list swapped ("\[java, good\]") doesn't get any results). I'm not sure if it's alphabetizing or if it's putting them in a hash-set. But it means the ordering isn't reliable.
  2. I don't want to have to list all the tags. I want to be able to run all the "good" targets even if some are also tagged "slow" or "local".

Can I use tags for this task? Are tags really just intended for tests? (The test_suite is the reason I thought to use tags in the first place.)


Solution

  • If by "run a set of Bazel targets" you mean bazel run, you can use --build_tag_filters for this:

    https://docs.bazel.build/versions/3.2.0/command-line-reference.html#flag--build_tag_filters

    Note however that bazel run supports running only 1 executable target at a time.

    If you want to run many binaries in a single call, you'll need to continue to pursue bazel query.

    The attr() filter accepts a regular expression, so to get around the problem of tags being in arbitrary order, you can do something like:

    bazel query "attr(tags, '\\bgood\\b', //...)"

    (where \b is the word boundary matcher)

    If you have multiple tags, you can use intersect:

    bazel query "attr(tags, '\\bgood\\b', //...) intersect attr(tags, '\\balso-good\\b', //...)"

    That will give you the list of targets to run, then you can do something like:

    targets=$(bazel query "attr(tags, '\\bgood\\b', //...)")
    
    bazel build ${targets[@]}
    
    for target in ${targets[@]}; do
      bazel run $target &
    done
    

    bazel run will build the targets before running them, and bazel will wait on previous invocations of itself in the same workspace[1]. So to get the binaries to run in parallel (if that's something you want), the example builds all the targets before running them. (bazel won't block subsequent invocations of itself once the binary is running)

    There's also another idea that seems nicer, which is to have an sh_binary which depends on all the binaries you want to run, and the sh_binary script simply runs them. You would then be able to do bazel run on that single sh_binary. The problem with that is you'd have to list each binary you want to run in the data attribute of the sh_binary, because it's not possible to create dependencies in a BUILD file using a query (despite there being a genquery rule -- that just outputs the results of the query to a file).

    [1] unless you have separate output bases using --output_base for the different invocations