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. See below for a formal description of the grammar.

You can do most things that one might consider "simple" Python; for and if statements, define functions, create lists and dicts, etc. Conventionally we keep complex logic in build_defs files but at present there is no difference in accepted syntax between the two.

Since it is not possible to import things, one obviously requires a different method of sharing code; in Please that is subinclude. This function takes the output of a build rule elsewhere in the repo and makes it available in the context of the currently executing file - for example, if it has defined a function, that function is now available in your BUILD file at the top level.

See here for a full description of available builtin rules.

Types

The set of builtin types are again a subset of Python's:

  • Integers (all integers are 64-bit signed integers)
  • Strings
  • Lists
  • Dictionaries
  • Functions
  • True, False and None are singletons which are instantiated by name.

Specifically there are no classes, sets, floats or bytes. Tuples don't exist as a standalone type, in various cases lists are generated as a replacement. In some cases the runtime may prohibit modification of a list or dict when they can be shared between multiple BUILD files in order to prevent one from seeing another's modifications.

Dictionaries are somewhat restricted in function; they may only be keyed by strings and cannot be iterated directly - i.e. one must use keys(), values() or items(). The results of all these functions are always consistently ordered.

Functions

The following functions are available as builtins; unless otherwise noted they mimic Python's builtins in most cases (although this is not guaranteed to every possible nuance).

  • len
  • enumerate
  • zip
  • isinstance
  • range
  • any
  • all
  • sorted

Several additional builtin functions are also available:

  • package_name - returns the package being currently parsed.
  • join_path - like os.path.join
  • split_path - like os.path.split
  • splitext - like os.path.splitext
  • basename - like os.path.basename
  • dirname - like os.path.dirname

The following are available as member functions of strings:

  • join
  • split
  • replace
  • partition
  • rpartition
  • startswith
  • endswith
  • format - although this lacks some functionality, % is generally preferred
  • lstrip
  • rstrip
  • strip
  • find
  • rfind
  • count
  • upper
  • lower

The following are available as member functions of dictionaries:

  • keys
  • values
  • items
  • copy

Finally, messages can be logged to Please's usual logging mechanism. These may or may not be displayed depending on the -v flag; by default only warning and above are visible.

  • log.debug
  • log.info
  • log.notice
  • log.warning
  • log.error
  • log.fatal - this will cause the process to exit immediately and unsuccessfully.

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'], exclude=['*_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.

Grammar

The grammar is defined as (more or less) the following, where Ident, String, Int and EOL are token types emitted by the lexer.

# Start symbol for the grammar, representing the top-level structure of a file.
file_input = { statement };

# Any single statement. Must occur on its own line.
statement = ( "pass" | "continue" | func_def | for | if | return |
          raise | assert | ident_statement | literal ) EOL;
return = "return" [ expression { "," expression } ];
raise = "raise" expression;
assert = "assert" expression [ "," String ];
for = "for" Ident { "," Ident } "in" expression ":" EOL { statement };
if = "if" expression ":" EOL { statement }
     [ "elif" expression ":" EOL { statement } ]
     [ "else" ":" EOL { statement } ];
func_def = "def" Ident "(" [ argument { "," argument } ] ")" ":" EOL
           [ String EOL ]
           { statement };
argument = Ident [ ":" String { "|" String } ] { "&" Ident } [ "=" expression ];
ident_statement = Ident
                  ( { "," Ident } "=" expression
                  | ( "[" expression "]" ( "=" | "+=" ) expression)
                  | ( "." ident | call | "=" expression | "+=" expression ) );

# Any generalised expression, with all the trimmings.
expression = [ "-" | "not" ] value [ operator expression ]
             [ "if" expression "else" expression ];
value = ( String | Int | "True" | "False" | "None" | list | dict | tuple | lambda | ident )
        [ slice ] [ ( "." ident | call ) ];
ident = Ident { "." ident | call };
call = "(" [ arg { "," arg } ] ")";
arg = expression | ident "=" expression;
list = "[" expression { "," expression } comprehension "]";
tuple = "(" expression { "," expression } ")";
dict = "[" expression ":" expression { "," expression ":" expression } comprehension "]";
comprehension = "for" Ident { "," Ident } "in" expression
                [ "for" Ident { "," Ident } "in" expression ]
                [ "if" expression ];
slice = "[" [ expression ] [ ":" expression ] "]";
lambda = "lambda" [ lambda_arg { "," lambda_arg } ] ":" expression;
lambda_arg = Ident [ "=" expression ];
operator = ("+" | "-" | "%" | "<" | ">" | "and" | "or" | "is" |
            "in" | "not" "in" | "==" | "!=" | ">=" | "<=" );

As mentioned above, this is similar to Python but lacks the import, try, except, finally, class, global, nonlocal, while and async keywords. The implementation may happen to permit using these as identifiers although it's discouraged (some tools might attempt to operate on the file using Python's ast module for convenience, which would not be possible if those keywords are used).
As a result, while raise and assert are supported, it's not possible to catch and handle the resulting exceptions. These hence function only to signal an error condition which results in immediate termination.

A more limited set of operators than in Python are available. The provided set are considered sufficient for use in BUILD files.

Function annotations similar to PEP-3107 / PEP-484 are available, although they have first-class meaning as type hints. The arguments are annotated with the expected type or types (separated by |) and when called the type of the argument will be verified to match. This makes it easier to give useful feedback to users if they make mistakes in their BUILD files (e.g. passing a string where a list is required).

Varargs and kwargs functions are not supported.