Our previous examples lack a fundamental attribute of a build solution — they did not produce any artifacts. We need to understand how to create a file in Bazel to correct that.
How to Create a File #
The process typically involves four steps:
- Declare a file with
declare_file
function. - Execute a build action,
most frequently,
run
orrun_shell
, to produce the file. - Include the file into a depset.
- Return a provider referring to the depset.
A full explanation of these steps involves a deeper dive into Bazel’s more advanced concepts, so instead, I’ll try to keep it close to the code.
Example #
Let’s copy and modify our hello
example to create a hello.txt
text file. Apart from a couple of names, the only place
we need to change is the implementation function:
def _demo_rule_impl(ctx):
out = ctx.actions.declare_file("hello.txt")
ctx.actions.write(
output = out,
content = "Hello, World!\n",
)
return [DefaultInfo(files = depset([out]))]
In this example,
declare_file
action creates a file object that we can pass to other actions. We generate the file with the
write
action.
Then we create an instance of a depset,
a data structure that can hold and efficiently merge large sets of direct and transitive dependencies.
Finally, the function returns a list of providers. A provider is yet another specialized data structure that passes information
between rules. We use the DefaultInfo
provider here as it is sufficient for our purposes.
Our build is now more meaningful:
$ bazel build //file
INFO: Analyzed target //file:file (4 packages loaded, 7 targets configured).
INFO: Found 1 target...
Target //file:file up-to-date:
bazel-bin/file/hello.txt
...
And we can check that it did produce the file:
$ cat bazel-bin/file/hello.txt
Hello, World!
The complete example is here.