How to Copy Bazel Build Outputs to Your Source Directory
NOTE: This article was updated on 2022/01/31 to point to the new repository home for updatesrc
.
Have you ever wanted to copy the output of a Bazel build step to your source directory (e.g., generated documentation, formatted source files)? Read on to learn how the updatesrc_update rule and the updatesrc_update_all macro in the cgrindel/bazel-starlib repository can make this super simple.
Table of Contents
Add updatesrc
to Your Workspace.
The updatesrc
rules live in cgrindel/bazel-starlib. Let’s add cgrindel/bazel-starlib to your WORKSPACE file.
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "cgrindel_bazel_starlib",
sha256 = "fc2ee0fce914e3aee1a6af460d4ba1eed9d82e8125294d14e7d3f236d4a10a5d",
strip_prefix = "bazel-starlib-0.3.2",
urls = [
"http://github.com/cgrindel/bazel-starlib/archive/v0.3.2.tar.gz",
],
)
load("@cgrindel_bazel_starlib//:deps.bzl", "bazel_starlib_dependencies")
bazel_starlib_dependencies()
load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
bazel_skylib_workspace()
The above code will download the repository and its dependencies.
Specify the Files to Update
The updatesrc_update rule defines an executable Bazel target that copies specified files from the Bazel output to the workspace/source directory. There are two ways to select files to copy.
Option #1: Specify the Files Using the srcs
and outs
Attributes
This option is helpful if you are generating files with a genrule or something similar. In the same Bazel package as the genrule
, you define an updatesrc_update target specifying the source files using the srcs
attribute and the output files using the outs
attribute. The source list and output list must result in file lists that have the same length, as the contents of the n-th index of the output list replace the contents of the n-th index of the source list.
In the following example, the genrule
targets accept a source file (e.g., c.txt
) and create a modified version (e.g., c.txt_modified
). The updatesrc_update target defines the mapping between the output files and the source files.
load(
"@cgrindel_bazel_starlib//updatesrc:defs.bzl",
"updatesrc_update",
)
genrule(
name = "c_modified",
srcs = ["c.txt"],
outs = ["c.txt_modified"],
cmd = """\
echo "# Howdy" > $@
cat $(location c.txt) >> $@
""",
)
genrule(
name = "d_modified",
srcs = ["d.txt"],
outs = ["d.txt_modified"],
cmd = """\
echo "# Howdy" > $@
cat $(location d.txt) >> $@
""",
)
updatesrc_update(
name = "update",
srcs = ["c.txt", "d.txt"],
outs = [":c_modified", ":d_modified"],
)
To copy the output files to the source directory, you execute the updatesrc_update target.
$ bazel run //path/to/pkg:update
A working example is in the examples directory of the repository.
Option #2: Custom Rule Provides UpdateSrcsInfo
The second option is for Bazel rule authors. This repository defines a provider called UpdateSrcsInfo. If a rule generates an output that can be copied to the source directory, the rule can return an instance of this provider. The UpdateSrcsInfo provider maps source files and with their corresponding output files.
In the following example, we define a rule called header
. It adds header text to the top of a source file. In addition to returning a DefaultInfo
provider, it returns an instance of an UpdateSrcsInfo
provider with the output-source mapping.
# File: header/header.bzl
load(
"@cgrindel_bazel_starlib//updatesrc:defs.bzl",
"UpdateSrcsInfo",
"update_srcs",
)
def _header_impl(ctx):
outs = []
updsrcs = []
for src in ctx.files.srcs:
out = ctx.actions.declare_file(src.basename + "_with_header")
outs.append(out)
updsrcs.append(update_srcs.create(src = src, out = out))
ctx.actions.run(
outputs = [out],
inputs = [src],
executable = ctx.executable._header_tool,
arguments = [src.path, out.path, ctx.attr.header],
)
return [
DefaultInfo(files = depset(outs)),
UpdateSrcsInfo(update_srcs = depset(updsrcs)),
]
header = rule(
implementation = _header_impl,
attrs = {
"srcs": attr.label_list(
allow_files = True,
mandatory = True,
),
"header": attr.string(
mandatory = True,
),
"_header_tool": attr.label(
default = "@simple_example//header:header.sh",
executable = True,
cfg = "host",
allow_files = True,
),
},
doc = "Copies the output files to the workspace directory.",
)
A Bazel package that uses the rule looks like the following:
# File: path/to/pkg/BUILD.bazel
load("//header:header.bzl", "header")
load("@cgrindel_bazel_starlib//updatesrc:defs.bzl", "updatesrc_update")
header(
name = "add_headers",
srcs = glob(["*.txt"]),
header = "# Super cool header",
)
updatesrc_update(
name = "update",
deps = [":add_headers"],
)
To copy the output files to the source directory, you execute the updatesrc_update target.
$ bazel run //path/to/pkg:update
A working example is in the examples directory of the repository.
Update All of the Files
As we mentioned above, each updatesrc_update rule is executable and running it will copy the files for that particular target. However, if you want to update all of the files in your repository, you will define an updatesrc_update_all target. The updatesrc_update_all macro defines an executable target that queries your repository for every updatesrc_update target and executes them one at a time.
At the root of your workspace, create a BUILD.bazel
file if you don’t have one. Add the following:
load(
"@cgrindel_bazel_starlib//updatesrc:defs.bzl",
"updatesrc_update_all",
)
# Define a runnable target to execute all of the updatesrc_update targets
# that are defined in your workspace.
updatesrc_update_all(
name = "update_all",
)
Then, update all of your mapped source files by executing the update_all
target.
$ bazel run //:update_all
In addition to automatically executing all of the updatesrc_update targets, the updatesrc_update_all macro can be configured to run other executable targets. It is handy if you write a custom file-copy script and have everything updated with a simple command.
In the following example, we define an update_all
target that executes all updatesrc_update targets and executes a custom target called //doc:update
.
load(
"@cgrindel_bazel_starlib//updatesrc:defs.bzl",
"updatesrc_update_all",
)
updatesrc_update_all(
name = "update_all",
targets_to_run = [
"//doc:update",
],
)
Then, as before, you run the update_all
target.
$ bazel run //:update_all
Conclusion
This article demonstrated using updatesrc to copy files from Bazel output directories to your workspace/source directory.