Targets and BUILD files
Attaching metadata to your code.
Some build work, such as counting lines of code, needs only information found in source files, and doesn't require any other data.
Other work may require more information about the source files. For example, to run a test you need to know about all the transitive dependencies of that test.
This extra information lives in entities called targets
. A target is a collection of source files, with some extra metadata attached.
BUILD files
Target information lives in files with the name BUILD
. For example:
python_library(
name = 'greet',
sources = ['*.py', '!*_test.py'],
dependencies = [
"//:translate",
":greetings",
"helloworld/util"
],
compatibility = ">=3.6",
)
Let's break this down:
python_library
: The target's type. In this case, a library containing Python code.name = 'greet'
: The name of the target, which defaults to the name of the directory but may be explicitly specified. Target names must be unique within a directory.sources = ['*.py', '!*_test.py']
: The source files belonging to the target.dependencies = [...]
: The other targets on which this target directly depends.
Some target types are built in, but most are provided by backends. For example, the python_library
target type is provided by the pants.backend.python
backend.
All target types have a name
field, and almost all have sources
and dependencies
fields. Specific target types will have extra fields, as needed. For example, the python_binary
target has an entry_point
field that points to the main module for the binary.
Target addresses
A target is identified by its address
. A target address has the form path/to/directory:name
. For example, the target in the example above has the address helloworld/greet:greet
.
A target's dependencies
field references the targets it depends on using their addresses. Addresses are also sometimes used as command-line arguments.
Addresses are globally unique, which is guaranteed by the fact that target names must be unique within a BUILD file.
Address Abbreviation
If the target name is the same as the name of the directory containing it, then you can omit the target name. For example,
helloworld/greet
is an abbreviated form ofhelloworld/greet:greet
.
Sources
Almost all targets have a sources
field, which determines which source files belong to the target.
The value of the sources
field is a list of names and/or glob patterns, relative to the BUILD file's directory. Sources must be in or below this directory, i.e., patterns containing ../
are not allowed.
A !
prefix excludes matching files that would otherwise be included. For example, ['*.py', '!exclude_*.py']
will include foo.py
but exclude exclude_bar.py
.
While it is not recommended for a target to own source files in subdirectories (with the 1:1:1 method preferred), it is possible with the following syntax: sources=['**/*.py']
, where **
is a recursive glob that matches zero or more directories. This replaces the pre-v1.25 syntax which used rglobs
to indicate that a glob was recursive.
Dependencies
Almost all targets have a dependencies
field, which determines the direct dependencies of the target.
The value of the dependencies
field is a list of addresses of other targets. If code in target A
directly depends on code in target B
(e.g., by importing a symbol from B
) then A
should declare a dependency on B
. There is no need to declare indirect dependencies (that is, transitive dependencies of your direct dependencies).
Dependency Shorthand
If you depend on another target in the same BUILD file, you can omit the address and just use the name. For example, for dependencies listed in
helloworld/greet
, the string:greetings
is shorthand helloworld/greet:greetings`.
Default field values
To cut down on boilerplate:
- A target's
name
defaults to the name of the directory it's in. - Some target types will have a sensible default for the
sources
field.
For example, the default value for thesources
field of apython_library
target is['*.py', '!*_test.py', '!test_*.py', '!conftest.py']
. I.e., "all Python source files in this target's directory, that aren't test-related".
So, for example, the target above might be rewritten more succinctly as:
python_library(
dependencies = [
"//:translate",
":greetings",
"helloworld/util"
],
compatibility = ">=3.6",
)
The build graph
The set of targets in a repo form the build graph. The vertices in this graph are the targets, and the (directed) edges are the dependencies. The build graph must not contain directed cycles, i.e., it must form a DAG (a Directed Acyclic Graph).
Target granularity
A target's sources can be as fine-grained as a single file, or as course-grained as an entire tree of files. Target granularity has implications for invalidation and caching, and therefore for performance.
Different granularities work best for different scenarios. But we've found that having one library and/or one test target per directory often tends to work well in practice. The default sources
values for various target types reflect this.
Upcoming feature: dependency inference
We are working on a feature where Pants understands your Python import statements and will automatically add them to the
dependencies
field for your targets. This means that you will be able to leave offdependencies
in most of your BUILD files.
Updated over 3 years ago