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=['*.java', '!Base.java'],
  tags={'common'},
)

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 in path/to/BUILD use path/to:target.
  • If the target has the same name as the BUILD file's directory, you can omit the repetition:
    path/to/target instead of path/to/target:target.
  • If the target is defined in the same BUILD file, you can omit the path:
    :target instead of path/to:target.
  • More details on how to address targets in a list of dependencies.

sources

The source files in this target. These are defined in one of two ways:

  • If the sources argument is not passed, many target types define a sensible default sources value 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).
    • For example, java_library defaults to collecting ['*.java', '!*Test.java'].
  • Explicitly providing file names and globs: sources=['App.java', '*Util.java'].

You can exclude files by prefixing the file name or glob with !. For example, to collect unit tests but not integration tests you could use something like this:
sources=['*_test.py', '!*_integration_test.py'].

You can also recursively glob over files in all subdirectories of the BUILD file's directory: sources=['**/*.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.

tags

Tags are a set of strings used to describe or categorize targets. They can be inspected during a build to allow for features such as filtering task targets (ex. skip linting targets with a particular tag) or focused testing (ex. running only unit tests by excluding targets with a integration tag).

Tags can be configured for targets in three ways: - Directly in the BUILD file (tags={'common'} in the example above) - In pants.toml:

[target-tag-assignments]
tag_targets_mappings = """
{
  'tag1': ['path/to/target:', 'path/to/another/target:bar'],
  'tag2': ['path/to/another/target:bar']
}
"""
./pants list src/python/pants/base:exceptions --target-tag-assignments-tag-targets-mappings=@/path/to/target_tag_definitions.json

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 src/python/myproject/example
src/python/myproject/example:example

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.

Generated by publish_docs from dist/markdown/html/src/docs/build_files.html 2022-12-03T01:08:59.403617