Globs
glob() lets a BUILD file match a class of files without spelling out every filename by hand. In Bazel, though, it is narrower than shell wildcarding: it returns a sorted list of files in the current package that match include and not exclude, and directories are left out by default.1 The useful mental model is "file selection inside one package", not "search the whole tree below me."
What glob() Actually Selects
In practice, glob() is usually fed into attributes such as srcs or data from 0.3.3 Attributes & Semantic Roles:
filegroup(
name = "configs",
srcs = glob(
["*.json"],
exclude = ["draft-*.json"],
),
)
This says: take every .json file in this package except the drafts. glob() answers only the question "which files?"; the surrounding attribute still answers "what role do these files play?" A glob() inside srcs means compile-time inputs, while the same pattern inside data means runtime files.2
That also makes glob() different from CLI selectors such as //... or :all. Those are target patterns from 0.2.3 Target Patterns. They choose targets on the command line. glob() chooses files inside one package definition.
If you mean "no files", the style guide recommends writing [] directly instead of a glob that happens to match nothing.3
Recursive Does Not Mean "Across Packages"
The trap is **. Beginners often read glob(["**/*.txt"]) as "all .txt files anywhere below this directory". In Bazel it really means "all .txt files anywhere below this directory that still belong to this package." A package owns its directory and subdirectories only until a nested directory gets its own BUILD file, which turns that subtree into a separate package.4 Recursive globbing stops there.3,5
The glob-retraction snippet shows this with two nearly identical packages. In //before, the nested directory is just a directory, so the recursive glob sees both text files:
$ bazel query 'labels(srcs, //before:texts)'
//before:README.txt
//before:nested/note.txt
In //after, the only structural difference is after/nested/BUILD.bazel. That one file turns nested/ into a subpackage, and the parent glob retracts immediately:
$ bazel query 'labels(srcs, //after:texts)'
//after:README.txt
Nothing about the pattern changed. The package boundary changed. That behavior follows directly from the package model introduced in 0.1.3 Package.
When Globs Help
Non-recursive globs are generally acceptable.3 They work well when a package has a boring, local set of similarly named files that should travel together: templates, fixtures, static data, or many same-kind sources in one directory. They save maintenance work without hiding too much structure.
If several rules need the same matched set, glob() and 0.3.6 filegroup Rule fit together naturally: glob() selects the files, and filegroup gives that set a reusable target name.
Where They Become Dangerous
The official warning is mostly about recursive globs for source files such as glob(["**/*.java"]).3 They make BUILD files harder to reason about because adding or removing a nested BUILD file silently changes the parent target's inputs. They are also generally less efficient than the package-per-directory structure Bazel prefers, where each directory has its own BUILD file and dependencies are explicit between packages.3,5
That does not mean glob() is bad. It means recursive globs widen the hidden change surface of a package. A local glob(["*.json"]) is usually clear. A broad glob(["**/*"]) can blur package boundaries back into a filesystem crawl. If you find yourself reaching for a recursive source glob, that is often a cue to revisit the package structure instead of the pattern.
Practical Default
Use explicit file lists when there are only a few files. Use non-recursive globs when the package has many same-kind local files. Avoid recursive source globs. When the matched set deserves a stable name that other targets can depend on, wrap it in 0.3.6 filegroup Rule.
glob() is a convenience for selecting files inside one package, not a way to ignore Bazel's package structure. The key rule to remember is simple: ** recurses through directories, but never through subpackages. If a recursive glob feels surprising, the real thing to inspect is usually the package boundary, not the pattern syntax.
1.A recursive glob("**/*.java") stops matching files when it reaches a subdirectory that:
2.What is the recommended approach when you need a broad set of source files across many subdirectories?
3.How does glob() differ from target patterns like //...?
Footnotes
-
BUILD files —
glob()contract: sorted file list,include/exclude, and directories excluded by default ↩ -
Dependencies —
srcsanddataare different dependency roles even when both are populated with file lists ↩ -
BUILD Style Guide — use
[]for no files, non-recursive globs are acceptable, and recursive source globs are discouraged ↩1 ↩2 ↩3 ↩4 ↩5 -
Repositories, workspaces, packages, and targets — a package includes subdirectories only until one of them has its own
BUILDfile ↩ -
Bazel Training 101 (Part 12): Manually using rules in BUILD files — glob convenience versus performance and subpackage-boundary pitfalls ↩1 ↩2