Most of the time, Bazel users do not need to know the path to the artifacts created for any given target. A notable exception is for users of packaging rules. You typically create an RPM or Debian packaged file for the explicit purpose of taking it from your machine and giving it to someone else.
Users often create scripts to push bazel build
outputs to other places and need to know the path to those outputs. This can be a challenge for rules which may create the file name by combining a base part with a version number, and maybe a CPU architecture. We don't do find them with shell wildcards like bazel-bin/my-pkg/pkg-*.deb
. That is brittle. Fortunately, Bazel provide all the tools we need to get the precise path of an output.
We can use Bazel's cquery command to find information about a target.
In Bazel 5.3.0 or later, there is a --output=files
flag that provides this info directly. (PR)
Older versions of Bazel require a small Starlark program to be supplied. Specifically we use cquery's Starlark output to inspect a target and print exactly what we need. Let's try it:
bazel build :deb bazel cquery :deb --output=starlark --starlark:file=show_all_outputs.bzl 2>/dev/null
That should produce something like
deb: bazel-out/k8-fastbuild/bin/mwp_3.14_all.deb changes: bazel-out/k8-fastbuild/bin/mwp_3.changes
show_all_outputs.bzl is a Starlark script that must contain a function with the name format
, that takes a single argument. The argument is typically named target, and is a configured Bazel target, as you might have access to while writing a custom rule. We can inspect its providers and print them in a useful way.
For pkg_deb, there are two files, the .deb file and the .changes, and both are passed along in the rule's OutputGroupInfo provider. This snippet below (from show_all_outputs.bzl) prints them.
def format(target): provider_map = providers(target) output_group_info = provider_map["OutputGroupInfo"] # Look at the attributes of the provider. Visit the depsets. ret = [] for attr in dir(output_group_info): if attr.startswith("_"): continue attr_value = getattr(output_group_info, attr) if type(attr_value) == "depset": for file in attr_value.to_list(): ret.append("%s: %s" % (attr, file.path)) return "\n".join(ret)
A full explanation of why this works is beyond the scope of this example. It requires some knowledge of how to write custom Bazel rules. See the Bazel documentation for more information.
Sometimes a rule will create an implicit output that the user does not explicitly specify as an attribute of the target. The .changes file from pkg_deb is an example. If we want another rule to depend on an implicitly created file, we can do that with a filegroup that specifies the specific output group containing that file.
In the example below, :deb
is a rule producing an explicit .deb output and an implicit .changes output. We refer to the .changes file using the filegroup
and specifying the desired output group name. Then, any rule can use this filegroup
as an input.
pkg_deb(name = "deb", ...) filegroup( name = "the_changes_file", srcs = [":deb"], output_group = "changes", ) genrule( name = "use_changes_file", srcs = [":the_changes_file"], outs = ["copy_of_changes.txt"], cmd = "cp $(location :the_changes_file) $@", )