BUILD files
A large, well-organized codebase divides into many small components. These components, and the code dependencies between them, form a directed graph.
In Pants parlance, these components are called targets. The information about your
targets and their dependencies lives in files named BUILD
, scattered throughout your
source tree. A BUILD
file in a given directory describes targets that own the
source files in or under that directory.
- See tutorial for an easy introduction to
BUILD
files. - See the BUILD Dictionary for an exhaustive list of all
BUILD
file syntax.
Target Granularity
A target can encapsulate any amount of code, from a single source file to (as a silly hypothetical) the entire codebase. In practice, you'll want to pick a granularity that reflects a "sensible" level at which to express dependencies: Too coarse, and you lose the benefits of fine-grained invalidation. Too fine, and you drown in BUILD boilerplate.
Many programming languages (E.g., Java, Python, Go) have a concept of a package, usually corresponding to a single filesystem directory. It turns out that this is often the appropriate level of granularity for targets. The idiom of having one target per directory, representing a single package, is sometimes referred to as the 1:1:1 rule. It's by no means required, but has proven in practice to be a good rule of thumb. And of course there are always exceptions.
Note that Pants forbids circular dependencies between targets. That is, the dependency graph must be a DAG. In fact, this is good codebase hygiene in general. So if you have any tightly-bound cross dependencies between certain packages, they will all have to be part of a single target until you untangle the dependency "hairball".
Target Definitions
A target definition in a BUILD
file looks something like
java_library( name='util', dependencies = [ '3rdparty:commons-math', '3rdparty:thrift', 'src/java/org/pantsbuild/auth', ':base' ], sources=globs('*.java', exclude=[['Base.java']]), )
type
Each target will have a different target type, which is java_library
in this case.
This tells Pants tasks what can be done with the target.
name
The target's name, along with the path to its BUILD
file, forms its address.
The address has two important roles:
- It's used on the command line to specify which targets to operate on.
- It's used in other
BUILD
files to reference the target as a dependency.
If a target does not explicitly pass a name, it will be assigned the name of the current directory (of course, at most one target may do this per directory).
dependencies
List of targets that this target depends upon. If this target's code imports or otherwise depends on code in other targets, list those targets here.
- To reference a target
target
defined inpath/to/BUILD
usepath/to:target
. - If the target has the same name as the BUILD file's directory, you can omit the repetition:
path/to/target
instead ofpath/to/target:target
. - If the target is defined in the same BUILD file, you can omit the path:
:target
instead ofpath/to:target
. - More details on how to address targets in a list of dependencies.
sources
The source files in this target. These are usually defined in one of three ways:
- If the
sources
argument is not passed (and the--target-arguments-implicit-sources
option is set: enabled by default in1.4.0.dev2
), many target types define a default ("implicit") source glob to collect all relevant files in the current directory. When possible, this style is recommended because it encourages 1:1:1 (see the
Target Granularity section for more information).- As an example:
java_library
defaults to collectingglobs('*.java', exclude=[globs('*Test.java')])
.
- As an example:
- Explicitly globbing over the files in the BUILD file's directory:
sources=globs('*.java')
. - Enumerating the files:
sources=['FileUtil.java', 'NetUtil.java']
.
You can exclude files from the results of a glob. For example, to glob over unit tests
but not integration tests you could use something like this:
sources=globs('*.py', exclude=[globs('*_integration.py')])
.
The value of exclude=
is a list of things that evaluate to lists of source files,
i.e., globs or literal lists. This is why there are double-brackets around Base.java
in
the example target above.
You can also recursively glob over files in all subdirectories of the BUILD file's directory: sources=rglobs('*.java')
.
However this is discouraged as it tends to lead to coarse-grained dependencies, and Pants's
advantages come into play when you have many fine-grained dependencies.
BUILD.*
files
BUILD files are usually just named BUILD
, but they can also be named BUILD.ext
, with any
extension. Pants considers all files matching BUILD(.*)
in a single directory to be a single
logical BUILD file. In particular, they share a single namespace, so target names must be
distinct across all such files.
This has various uses, such as the ability to separate internal-only BUILD definitions from those
that should be pushed to an open-source mirror of an internal repo: You can put the former
in BUILD.internal
files and the latter in BUILD.oss
files.
Debugging BUILD Files
If you're curious to know how Pants interprets your BUILD
files, these
techniques can be especially helpful:
What targets does a BUILD file define? Use the list
goal:
$ ./pants list examples/src/java/org/pantsbuild/example/hello/greet
examples/src/java/org/pantsbuild/example/hello/greet:greet
Are any BUILD files broken?
List every target to see if there are any errors:
Use the recursive wildcard ::
with the list goal:
$ ./pants list :: ...lots of output... File "pants/commands/command.py", line 79, in __init__ File "pants/commands/goal_runner.py", line 144, in setup_parser File "pants/base/build_graph.py", line 351, in inject_address_closure TransitiveLookupError: great was not found in BUILD file examples/src/java/org/pantsbuild/example/h ello/greet/BUILD. Perhaps you meant: :greet referenced from examples/src/scala/org/pantsbuild/example/hello/welcome:welcome
Do I pull in the transitive dependencies I expect? Use depmap
:
$ ./pants depmap examples/tests/java/org/pantsbuild/example/hello/greet
internal-examples.tests.java.org.pantsbuild.example.hello.greet.greet
internal-3rdparty.junit
internal-3rdparty.hamcrest-core
org.hamcrest-hamcrest-core-1.3
junit-junit-dep-4.11
internal-examples.src.java.org.pantsbuild.example.hello.greet.greet
internal-examples.src.resources.org.pantsbuild.example.hello.hello
junit-junit-dep-4.11
org.hamcrest-hamcrest-core-1.3
What source files do I depend on? Use filedeps
:
$ ./pants filedeps examples/src/java/org/pantsbuild/example/hello/main
~archie/workspace/pants/examples/src/java/org/pantsbuild/example/hello/greet/BUILD
~archie/workspace/pants/examples/src/java/org/pantsbuild/example/hello/main/config/greetee.txt
~archie/workspace/pants/examples/src/resources/org/pantsbuild/example/hello/BUILD
~archie/workspace/pants/examples/src/java/org/pantsbuild/example/hello/main/HelloMain.java
~archie/workspace/pants/examples/src/resources/org/pantsbuild/example/hello/world.txt
~archie/workspace/pants/examples/src/java/org/pantsbuild/example/hello/main/BUILD
~archie/workspace/pants/examples/src/java/org/pantsbuild/example/hello/greet/Greeting.java
Use the -h
flag to get help on these commands and their various options.