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; for now, the key point is that the file's presence is what 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 — they must be replaced with//myproject/util:helper.ccor a named target in//myproject/util. -
Globs retract. A
glob(["**/*.cc"])in the rootBUILD.bazelpreviously matchedutil/helper.cc. After the split, it no longer does —glob()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. Bazel's style guidance suggests keeping this package free of source code, so that all meaningful packages have descriptive names6.
In practice, the root BUILD file takes on a different role: rather than compilable code, it hosts project-wide tooling and configuration. Opening the root BUILD file in a real project, you'll typically find:
- Build file generators — targets that scan the entire repo and create or update
BUILDfiles in subdirectories (e.g., Gazelle). - Formatters and linters — targets for formatting BUILD files (Buildifier) and source code across all languages.
- Dependency management — targets that produce or consume lock files for Python (
requirements_lock.txt), JavaScript (package-lock.json), or other ecosystems. - Exported config files — root-level configs like
.ruff.toml,pyproject.toml,.clang-tidy, orbuf.yamlmade available to subpackages that reference them in their build rules. - Convenience aliases — short names that redirect to targets deeper in the directory tree.
Many of these files live at the root because non-Bazel tools expect them there. A requirements.txt at the root works with pip install -r requirements.txt; a pyproject.toml at the root is found by IDEs and linters automatically. Moving them into a subdirectory would break compatibility with the broader tooling ecosystem. The root BUILD file bridges the gap between Bazel's package model and the conventions that other tools rely on.
The syntax behind all of this — how targets, rules, and label references work — is covered in 0.2 Language of Labels and 0.3 Starlark Syntax Basics.
Package Encapsulation
Source files are owned by their package4. By default, files inside a package are not visible to other packages — you need either a rule target that wraps them or an explicit exports_files() call to expose raw source files across package boundaries. This encapsulation is intentional: it gives each package a clear API surface, preventing the codebase from becoming a tangled web of cross-directory file references.
How Many Packages?
In practice, most Bazel projects have a BUILD file in nearly every directory that contains source code7. If you see a BUILD file referencing source files in subdirectories (like srcs = ["a/b/C.java"]), that usually means the subdirectory will eventually get its own BUILD file. Finer-grained packages keep each BUILD file focused and improve loading parallelism. Bazel's incremental model tracks individual targets and actions, not whole packages — but smaller packages tend to produce more precise dependency declarations, which helps Bazel skip unnecessary work. The trade-offs of package granularity are covered in depth in 7.1 Repository Structure & Git Optimization.
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.
1.What creates a package boundary in Bazel?
2.What happens when you add a BUILD.bazel file to a subdirectory that didn't have one?
3.Why is BUILD.bazel preferred over BUILD as the filename?
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 ↩