Finding Unexpected Transitive Dependencies with Bazel Query
Use Bazel's query language to trace unexpected transitive dependencies without a full build. Learn deps, rdeps, and somepath operators with a concrete worked example, plus limitations around runtime deps and configuration sensitivity.
25 Sept 2025, 09:44 UTC

The Problem: Mystery Libraries in Your Binary
You build a simple CLI tool and notice a heavyweight logging framework in the final binary. Your BUILD file doesn't reference it directly. A full build takes ten minutes, and you need to know why that dependency appears before you can decide whether to remove it. Bazel's query command answers this without executing any build actions.
Core Query Operators for Dependency Analysis
Bazel's query language operates on the configured target graph — the result of evaluating select() branches and platform constraints for the current configuration. Three operators cover most investigative needs:
deps(target)— all transitive dependencies of a target, including the target itself.rdeps(universe, target)— reverse dependencies: which targets in universe depend on target.somepath(start, end)— a single dependency path from start to end, useful for tracing how a library sneaks in.
Run queries from your workspace root. No special permissions beyond read access to the source tree. Output formats include --output=label (one label per line), --output=build (full rule definitions), --output=xml, and --output=graph (GraphViz DOT).
Worked Example: Tracing a Surprise Logging Dependency
Suppose your workspace has:
//tools/cli:main # cc_binary, depends on //lib:util
//lib:util # cc_library, depends on //lib:config
//lib:config # cc_library, depends on //third_party:log4cpp
//third_party:log4cpp # cc_library (heavyweight logging)
You expect //tools/cli:main to be lightweight. Run:
bazel query 'deps(//tools/cli:main)' --output=label
Expected output (order may vary):
//tools/cli:main
//lib:util
//lib:config
//third_party:log4cpp
The logging library appears transitively via //lib:config. To see the exact path:
bazel query 'somepath(//tools/cli:main, //third_party:log4cpp)' --output=label
Output shows one path:
//tools/cli:main
//lib:util
//lib:config
//third_party:log4cpp
Now you know //lib:config is the culprit. You can decide whether to split //lib:config into a lightweight variant or accept the dependency.
Assessing Impact Before a Change
Before refactoring //third_party:log4cpp, estimate the blast radius:
bazel query 'rdeps(//..., //third_party:log4cpp)' --output=label
The //... universe means "all targets in the workspace." On large repos, restrict the universe to a subdirectory (e.g., //tools/...) to keep the query fast. The result lists every target that transitively depends on the logging library — your test targets, other binaries, and possibly generated code rules.
Limitations and Gotchas
- Analysis only. Query sees the static configured graph. It cannot detect dependencies loaded at runtime via
dlopen, plugin systems, or generated files outside Bazel's control (e.g., a code generator that writes#includestatements Bazel doesn't track). - Configuration sensitivity. Results reflect the active platform and
select()branches. A query run with--cpu=arm64may show different edges than the default host config. Pin the configuration in CI:bazel query --config=release 'deps(//tools/cli:main)'. - Performance on large graphs.
allpathsand unrestrictedrdeps(//..., ...)can consume significant memory and time. Limit the universe:rdeps(//my/project/..., //third_party:log4cpp). - Version drift. Query syntax and default output evolved across Bazel 6.x and 7.x. Pin your Bazel version with a
.bazelversionfile and re-validate automation scripts after upgrades.
Verification Checklist
- Run
bazel query 'deps(//your/target:binary)' --output=labeland confirm the listed libraries match what you expect in the built artifact. - If a specific library looks suspicious, run
bazel query 'somepath(//your/target:binary, //suspect:lib)'to see the transitive chain. - After any Bazel upgrade, re-run your query scripts and diff the output to catch syntax or semantic changes.
Next Steps
Add a small script to your repository that runs the key queries for your critical binaries and fails CI if unexpected heavyweight dependencies appear. Store the expected dependency set in a checked-in file and diff against live query output. This turns a one-off investigation into a continuous guardrail without slowing down builds.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.