Intermediate Please

Okay, you've read the basics, so what's next?

BUILD file format

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:


	  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'],
          )
      
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.

Custom build rules

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. docs/documentation_rules.build_defs:


	  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'],
              )
      
docs/BUILD:

	  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.

Transitive dependencies

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 _binary and _test rules do, although this is somewhat dependent on the language.

gentest

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 $(exe :rule_name) 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.

Labels

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, for example plz test ... --include py will run all your tests labelled with 'py', and 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).

Flaky tests

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:


        go_test(
            name = 'my_test',
            srcs = ['my_test.go'],
            flaky = True,
        )
      
By default they are run three times, you can alter this on a per-test basis by passing an integer instead of a boolean.

The --max_flakes flag can be used to cap the number of re-runs allowed on a single invocation.

Containerised tests

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

build_rule is the fundamental unit all other build rules are built from. We discussed genrule just now which is very similar; build_rule allows control over more esoteric features of the system.

The difference between genrule and 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.

What else?

If you're still curious about what more can be done, try the advanced stuff.