Bazel Book

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.

key takeaway

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.

Check your understanding

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 //...?

Answer all questions to check

Footnotes

  1. BUILD filesglob() contract: sorted file list, include / exclude, and directories excluded by default

  2. Dependenciessrcs and data are different dependency roles even when both are populated with file lists

  3. BUILD Style Guide — use [] for no files, non-recursive globs are acceptable, and recursive source globs are discouraged 1 2 3 4 5

  4. Repositories, workspaces, packages, and targets — a package includes subdirectories only until one of them has its own BUILD file

  5. Bazel Training 101 (Part 12): Manually using rules in BUILD files — glob convenience versus performance and subpackage-boundary pitfalls 1 2