Okay, you've read the basics, so what's next?
We didn't talk much about the actual format of BUILD files last time, but an astute observer may have noticed that the rule invocations look rather like Python. This is not coincidence; BUILD files are interpreted as a (slightly) restricted subset of Python.
So what can you do with them? Well, let's say you want to translate your documentation to a bunch of languages:
Obviously this would be a bit repetitive if you redefined the rules separately for each language. Instead, we can take advantage of the build language to genericise them across languages; for each language we translate it and then format it as HTML.
for language in ['en_NZ', 'en_GB', 'it_IT', 'es_MX', 'el_GR']: genrule( name = 'txt_documentation_' + language, srcs = ['docs.txt'], outs = ['docs_%s.txt' % language], cmd = 'cat $SRC | $(exe //tools:translate_tool) --language %s > $OUT' % language, ) genrule( name = 'documentation_' + language, srcs = [':txt_documentation_' + language], outs = ['docs_%s.html' % language], cmd = '$(exe //tools:html_doc_tool) --src $SRC --out $OUT', visibility = ['PUBLIC'], )
While the above is perfectly readable, having to define two rules each time is a bit tedious;
maybe we'd rather define one rule to perform the translation & HTML conversion in one pass.
def create_html_docs(name, language, srcs, visibility=None): """Converts documentation to the given language and then to HTML.""" genrule( name = '_%s_%s#translate' % (name, language), srcs=srcs, outs = ['_%s_%s_translated.txt' % (name, language)], cmd = 'cat $SRC | $(exe //tools:translate_tool) --language %s > $OUT' % language, ) genrule( name = '%s_%s' % (name, language), srcs = [':_%s_%s#translate' % (name, language)], outs = ['%s_%s.html' % (name, language)], cmd = '$(exe //tools:html_doc_tool) --src $SRC --out $OUT', visibility = ['PUBLIC'], )
include_defs('//docs/documentation_rules.build_defs') for language in ['en_NZ', 'en_GB', 'it_IT', 'es_MX', 'el_GR']: create_html_docs( name = 'docs', language = language, srcs = ['docs.txt'], visibility = ['PUBLIC'], )
This example is getting a little strained, but serves to illustrate how you can create something that acts like a single build rule, but internally creates multiple steps to produce the final result. Since the BUILD files are essentially Python it's as simple as defining a function.
Note the microformat of defining 'internal' private rules with a leading underscore and trailing hashtag; in the interactive build mode those will be omitted to make it appear to users as though they're building the rule they defined by name, even though internally it's split into multiple rules.
Writing some rules requires that the transitive set of dependencies are available when they're built.
For example, consider
cc_binary; all your
cc_library rules have created a bunch of .o files,
but each of those only has its own set of symbols. The final binary rule must link all of them together to produce
a working executable.
Fortunately, this is pretty easy in Please. You can apply
needs_transitive_deps = True to a
genrule to make them all available at build time. This is easy to do but of course shouldn't be taken
lightly since it will slow the rule down (needs more time to prepare sources) and may harm incrementality.
The typical pattern is that
_library rules don't need this but
rules do, although this is somewhat dependent on the language.
gentest is similar to
genrule but defines a target that runs an arbitrary test. The
test_cmd attribute defines the command to run; this is often simply
to run its own output.
The protocol for tests to follow is pretty simple; the test command should return zero on success or nonzero
for failure (Unix FTW). The test should also write either a file called
test.results or multiple files
into a directory named the same; these are parsed as one of the formats Please understands (currently either
xUnit XML or Go's test output format). Optionally a test can be marked with
no_test_output = True
to indicate that it writes no files, in which case its return value is the only indicator of success.
Rules, particularly the various
_test rules can have an optional set of labels associated with
them. These are essentially just a set of tags that can be used to filter them when run via
plz test ... --include py will run all your tests labelled with 'py',
plz test ... --exclude slow will run all your tests except those labelled with 'slow'.
The syntax looks like:
go_test( name = 'my_test', srcs = ['my_test.go'], labels = ['slow', 'integration'], )
There is one special label,
manual which always excludes tests from autodetection when being run with
plz test //mypackage:all or
plz test //mypackage/.... This is often useful to disable tests
from general usage if needed (for example, if a test starts failing, you can disable it while investigating).
Tests can be marked as flaky which causes them to be automatically re-run several times. They are considered to pass if any one run passes.
The syntax looks like:
By default they are run three times, you can alter this on a per-test basis by passing an integer instead of a boolean.
go_test( name = 'my_test', srcs = ['my_test.go'], flaky = True, )
--max_flakes flag can be used to cap the number of re-runs allowed on a single invocation.
Tests can also be marked as containerised so they are isolated within a container for the duration of their run. One main advantage of this is for integration tests that need to open ports and start servers, which are then insulated from port clashes against one another.
Within the container the test will have access only to the data it's declared it needs at runtime, so you will have to be correct about declaring all such dependencies (but of course one would do that anyway...).
The syntax looks like:
go_test( name = 'my_test', srcs = ['my_test.go'], container = True, )
Currently we support Docker as a container format, we plan to support rkt containers in a future release.
build_rule is the fundamental unit all other build rules are built from. We discussed
just now which is very similar;
build_rule allows control over more esoteric features of the system.
The difference between
build_rule from a user's perspective is pretty arbitrary;
conventionally we use
genrule in build files for defining arbitrary build transformations and
build_rule for defining other rules, but there's no particularly principled reason for this.
If you're still curious about what more can be done, try the advanced stuff.