0.1.3 Package
Inside the repository root marked by MODULE.bazel (0.1.2 Repository Root), Bazel organizes source code into packages. A package is a directory that contains a BUILD file. That file turns the directory into a unit of code that Bazel knows how to build1.
The BUILD File
The BUILD file is a declarative file written in Starlark that defines targets, the things Bazel can build, test, or run2. A typical BUILD file lists rules like java_library, cc_binary, or sh_test, each describing a set of source files and their dependencies. The syntax and structure of BUILD files are covered in 0.3 Starlark Syntax Basics. The file's presence creates the package.
Two filenames are valid3:
| File | Notes |
|---|---|
BUILD.bazel | Preferred. Explicit and unambiguous. |
BUILD | Also valid. If both exist, BUILD.bazel takes precedence. |
BUILD.bazel is the recommended choice because a directory named build/ on a case-insensitive filesystem (macOS, Windows) would collide with a file named BUILD4. Most newer projects use BUILD.bazel exclusively.
What Belongs to a Package
A package includes all files in its directory and all subdirectories beneath it unless a subdirectory has its own BUILD file, which makes it a separate package1. No file can belong to two packages at once. Every file belongs to the package whose BUILD file is in the nearest ancestor directory.
Consider this directory tree:
myproject/
├── BUILD.bazel
├── lib.cc
├── util/
│ ├── helper.cc
│ └── helper.h
└── tests/
├── BUILD.bazel
├── test.cc
└── data/
└── fixture.json
Two packages exist here:
| Package | Files it owns |
|---|---|
//myproject | lib.cc, util/helper.cc, util/helper.h |
//myproject/tests | test.cc, data/fixture.json |
util/ has no BUILD file, so its contents belong to //myproject. tests/data/ has no BUILD file either, but since tests/ does, fixture.json belongs to //myproject/tests, not to the root package1.
glob() patterns in a BUILD file also respect these boundaries. They never descend into subdirectories that have their own BUILD file4.
Adding a BUILD File Splits a Package
Package boundaries are entirely determined by the presence of BUILD files. Adding util/BUILD.bazel to the tree above has three immediate effects:
-
File ownership shifts.
helper.ccandhelper.hmove from//myprojectto//myproject/util. Labels like//myproject:util/helper.ccbecome invalid and must be replaced with//myproject/util:helper.ccor a named target in//myproject/util. -
Parent globs retract. A
glob(["**/*.cc"])in the rootBUILD.bazelpreviously matchedutil/helper.cc. After the split, it no longer does becauseglob()never crosses into subpackages4. The root package'ssrcslists silently shrink. -
Cross-package references require explicit dependencies. The root package can no longer treat
util/helper.ccas a local file. It must declare a dependency on a target in//myproject/util, subject to visibility rules (0.2.4 Visibility).
Conversely, removing a BUILD file merges a package back into its parent. The files rejoin the parent's scope, and its globs pick them up again.
Package Names
A package's name is the path from the repository root to the directory containing the BUILD file5. In the example above, the two package names are myproject and myproject/tests. These paths become part of the label that identifies every target, covered in 0.2 Language of Labels.
The Root Package
When a BUILD file sits at the repository root itself — next to MODULE.bazel (0.1.2 Repository Root) — the package name is the empty string. This package is best kept free of source code, so that all meaningful packages have descriptive names6.
In practice, the root package often contains project-wide configuration or
tooling rather than application source. Targets and files there use labels such
as //:target because the package path before the colon is empty.
H.1.1 Repository Models discusses
how to choose a repository-wide layout.
Package Encapsulation
Source files are owned by their package4. Crossing that boundary requires an explicit target relationship. 0.2.4 Visibility explains which targets may depend on each other, while 0.2.7 exports_files() covers exposing a raw source file to another package.
How Many Packages?
Many repositories put BUILD files near buildable source7, but there is no
rule that every directory must become a package. Choosing package granularity is
a repository-design decision covered in H.1.1 Repository Models. Here, learn
to derive the boundary from the BUILD files that actually exist.
Three small failure examples make the package boundary concrete: a
malformed BUILD file,
a glob() that stops matching files when a subpackage appears,
and labels that name missing packages or repositories.
The presence of a BUILD file is the single mechanism that creates a package boundary. Use BUILD.bazel as the filename to avoid case-sensitivity issues. Every file belongs to exactly one package, and package boundaries control file ownership, visibility, and glob scope across the entire repository.
Check your understanding · 3 questions
1.Which BUILD file name is preferred, and why?
Select one answer
2.True or false: how package boundaries and file ownership work.
Choose True or False for each sentence
3.What happens when you add util/BUILD.bazel to a project where //myproject previously owned util/?
Select all that apply
Footnotes
-
Repositories, workspaces, packages, and targets — Package definition, file ownership, and the directory tree example ↩1 ↩2 ↩3
-
BUILD files — BUILD file as the program that defines a package ↩
-
BUILD files — BUILD vs BUILD.bazel naming and precedence ↩
-
Bazel Training 101 (Part 9): Packages, Rules, Targets, and Labels — BUILD.bazel preference, package encapsulation, glob behavior ↩1 ↩2 ↩3 ↩4
-
Bazel Glossary — Package name as BUILD file path relative to repo root ↩
-
Labels — Root package label syntax and recommendation to keep it free of source code ↩
-
Best Practices — Every directory with buildable files should be a package ↩