The BUILD language

Please's BUILD files typically contain a series of build rule declarations. These are invocations of builtins like java_binary which create new BUILD targets.

However, you can do much more with it; the syntax is actually a restricted Python subset so it's possible to script the creation of build targets in elaborate ways.

Syntax

Formally, the grammar for the BUILD files is defined as for Python since we interpret them as such (using PyPy, as it happens). The only exceptions to this are that the import, print and exec keywords are disallowed. This means you can cheerfully have loops, classes, exceptions etc as you want.

The reason these are disallowed is in an effort to keep BUILD file parsing reliable and efficient. Allowing import of arbitrary modules opens the door for all kinds of bad things to be done at parse time (we know, we did this in build systems we've used in the past and it did not have a happy ending). There are other solutions for these anyway (well not exec, but you weren't going to use that anyway, right?); see subinclude and log.

Similarly, a number of the standard builtins are not available; an incomplete list of these includes execfile, eval, file and open.
Again, if BUILD files were able to open and read arbitrary files, it would be possible to do many bad things which would ultimately lead to builds being slow or unreliable.

Note that BUILD files are parsed in-order, so invocations of functions that affect global defaults (notably package) will only affect build rules declared after them. However the rules themselves can be declared in any order, it isn't necessary to declare dependencies before rules that depend on them.

Style

We normally write BUILD files in an idiom which doesn't quite match standard Python styles. The justification is that these are mostly just inherited from working on Blaze, but a brief explanation follows after an example:


      # Taken from //src/core/BUILD in the Please repo
      go_library(
          name = 'core',
          srcs = glob(['*.go'], excludes=['*_test.go', 'version.go']) + [':version'],
          visibility = ['PUBLIC'],
          deps = [
              '//third_party/go:gcfg',
              '//third_party/go:logging',
              '//third_party/go:queue',
          ]
      )
    

All arguments to build rules are passed as keywords. This is pretty important since (1) nobody will be able to read your BUILD file otherwise and (2) while we don't normally change the order of function arguments, we might sometimes insert earlier ones which will break code using positional arguments.

Arguments to functions like glob() and subinclude() are not necessarily passed as keywords.

We put spaces around the = for each argument to the build rule.

Either single or double quotes work, as usual, but don't mix both in one file.

Lists either go all on one line:

['*_test.go', 'version.go']
or are broken across multiple lines like so:
          [
              '//third_party/go:gcfg',
              '//third_party/go:logging',
              '//third_party/go:queue',
          ]

Indentation is normally four spaces.

We generally try to order lists lexicographically but don't always get this 100%.

If you'd like an autoformatter for BUILD files, Google's Buildifier is very good & fast. We use a slightly modified version of it internally & on the Please repo.