Skylark cookbook
================
[TOC]
## Empty
Minimalist example of a rule that does nothing. If you build it, the target will
succeed (with no generated file).
`empty.bzl`:
```python
def impl(ctx):
# You may use print for debugging.
print("This rule does nothing")
empty = rule(impl)
```
`BUILD`:
```build
load("/pkg/empty", "empty")
empty(name = "nothing")
```
## Macro creating a native rule
An example of a macro creating a native rule. Native rules are accessed using
the `native` module.
`extension.bzl`:
```python
def macro(name, visibility=None):
# Creating a native genrule.
native.genrule(
name = name,
outs = [name + '.txt'],
cmd = 'echo hello > $@',
visibility = visibility,
)
```
`BUILD`:
```build
load("/pkg/extension", "macro")
macro(name = "myrule")
```
## Macro creating a Skylark rule
An example of a macro creating a Skylark rule.
`empty.bzl`:
```python
def impl(ctx):
print("This rule does nothing")
empty = rule(impl)
```
`extension.bzl`:
```python
# Loading the Skylark rule. The rule doesn't have to be in a separate file.
load("/pkg/empty", "empty")
def macro(name, visibility=None):
# Creating the Skylark rule.
empty(name = name, visibility=visibility)
```
`BUILD`:
```build
load("/pkg/extension", "macro")
macro(name = "myrule")
```
## Simple shell command
Example of a rule that runs a shell command on an input file specified by
the user. The output has the same name as the input, with a `.txt` suffix.
`size.bzl`:
```python
def impl(ctx):
output = ctx.outputs.out
input = ctx.file.file
ctx.action(
inputs=[input],
outputs=[output],
command="stat -L -c%%s %s > %s" % (input.path, output.path))
size = rule(
implementation=impl,
attrs={"file": attr.label(mandatory=True, allow_files=True, single_file=True)},
outputs={"out": "%{name}.size"},
)
```
`foo.txt`:
```
Hello
```
`BUILD`:
```build
load("/pkg/size", "size")
size(
name = "foo_size",
file = "foo.txt",
)
```
## Write string to a file
Example of a rule that writes a string to a file.
`file.bzl`:
```python
def impl(ctx):
output = ctx.outputs.out
ctx.file_action(output=output, content=ctx.attr.content)
file = rule(
implementation=impl,
attrs={"content": attr.string()},
outputs={"out": "%{name}.txt"},
)
```
`BUILD`:
```build
load("/pkg/file", "file")
file(
name = "hello",
content = "Hello world",
)
```
## Execute an input binary
This rule has a mandatory `binary` attribute. It is a label that can refer
only to executable rules or files.
`execute.bzl`:
```python
def impl(ctx):
# ctx.new_file is used for temporary files.
# If it should be visible for user, declare it in rule.outputs instead.
f = ctx.new_file(ctx.configuration.bin_dir, "hello")
# As with outputs, each time you declare a file,
# you need an action to generate it.
ctx.file_action(output=f, content=ctx.attr.input_content)
ctx.action(
inputs=[f],
outputs=[ctx.outputs.out],
executable=ctx.executable.binary,
arguments=[
f.path,
ctx.outputs.out.path, # Access the output file using
# ctx.outputs.
]
)
execute = rule(
implementation=impl,
attrs={
"binary": attr.label(cfg=HOST_CFG, mandatory=True, allow_files=True,
executable=True),
"input_content": attr.string(),
"out": attr.output(mandatory=True),
},
)
```
`a.sh`:
```shell
#! /bin/bash
tr 'a-z' 'A-Z' < $1 > $2
```
`BUILD`:
```build
load("/pkg/execute", "execute")
execute(
name = "e",
input_content = "some text",
binary = "a.sh",
out = "foo",
)
```
## Define simple runfiles
`execute.bzl`:
```python
def impl(ctx):
executable = ctx.outputs.executable
# Create the output executable file with command as its content.
ctx.file_action(
output=executable,
content=ctx.attr.command,
executable=True)
return struct(
# Create runfiles from the files specified in the data attribute.
# The shell executable - the output of this rule - can use them at runtime.
# It is also possible to define data_runfiles and default_runfiles.
# However if runfiles is specified it's not possible to define the above
# ones since runfiles sets them both.
# Remember, that the struct returned by the implementation function needs
# to have a field named "runfiles" in order to create the actual runfiles
# symlink tree.
runfiles=ctx.runfiles(files=ctx.files.data)
)
execute = rule(
implementation=impl,
executable=True,
attrs={
"command": attr.string(),
"data": attr.label_list(cfg=DATA_CFG, allow_files=True),
},
)
```
`data.txt`:
```
Hello World!
```
`BUILD`:
```build
load("/pkg/execute", "execute")
execute(
name = "e",
# The path to data.txt has to include the package directories as well. I.e.
# if the BUILD file is under foo/BUILD and the data file is foo/data.txt
# then it needs to be referred as foo/data.txt in the command.
command = "cat data.txt",
data = [':data.txt']
)
```
## Mandatory providers
In this example, rules have a `number` attribute. Each rule adds its
number with the numbers of its transitive dependencies, and write the
result in a file. This shows how to transfer information from a dependency
to its dependents.
`sum.bzl`:
```python
def impl(ctx):
result = ctx.attr.number
for i in ctx.targets.deps:
result += i.number
ctx.file_action(output=ctx.outputs.out, content=str(result))
# Fields in the struct will be visible by other rules.
return struct(number=result)
sum = rule(
implementation=impl,
attrs={
"number": attr.int(default=1),
# All deps must provide all listed providers.
"deps": attr.label_list(providers=["number"]),
},
outputs = {"out": "%{name}.sum"}
)
```
`BUILD`:
```build
load("/pkg/sum", "sum")
sum(
name = "n",
deps = ["n2", "n5"],
)
sum(
name = "n2",
number = 2,
)
sum(
name = "n5",
number = 5,
)
```
## Optional providers
This is a similar example, but dependencies may not provide a number.
`sum.bzl`:
```python
def impl(ctx):
result = ctx.attr.number
for i in ctx.targets.deps:
if hasattr(i, "number"):
result += i.number
ctx.file_action(output=ctx.outputs.out, content=str(result))
# Fields in the struct will be visible by other rules.
return struct(number=result)
sum = rule(
implementation=impl,
attrs={
"number": attr.int(default=1),
"deps": attr.label_list(),
},
outputs = {"out": "%{name}.sum"}
)
```
`BUILD`:
```build
load("/pkg/sum", "sum")
sum(
name = "n",
deps = ["n2", "n5"],
)
sum(
name = "n2",
number = 2,
)
sum(
name = "n5",
number = 5,
)
```
## Default executable output
This example shows how to create a default executable output.
`extension.bzl`:
```python
def impl(ctx):
ctx.file_action(
# Access the executable output file using ctx.outputs.executable.
output = ctx.outputs.executable,
content = "#!/bin/bash\necho Hello!",
executable = True
)
# The executable output is added automatically to this target.
executable_rule = rule(
implementation = impl,
executable = True
)
```
`BUILD`:
```build
load("/pkg/extension", "executable_rule")
executable_rule(name = "my_rule")
```
## Default outputs
This example shows how to create default outputs for a rule.
`extension.bzl`:
```python
def impl(ctx):
ctx.file_action(
# Access the default outputs using ctx.outputs.