5.2.2 bazel cquery — Configured Graph
bazel cquery answers the configuration-dependent version of a graph question:
not “what dependencies can this rule declare?”, but “which configured targets does
this build actually use with these flags and platforms?” It runs analysis, so
select() choices, transitions, and resolved toolchain edges are already part of
the graph it inspects.1
For a browser view of retained configured-target relationships, continue to 5.6.8 Skyscope — Skyframe Visualizer after narrowing the configuration and universe here.
Ask the same question under a real configuration
The runnable select-errors snippet gives
//app:server a configurable srcs edge. Its
select() chooses
//app:linux_runtime, //app:macos_runtime, or //app:generic_runtime, while
//platforms:linux
is a real target platform accepted by --platforms.
An ordinary query must conservatively include every possible branch because it
works before build options are evaluated:
bazel query 'deps(//app:server)'
The captured target output includes all three runtime branches:
//app:generic_config.txt
//app:generic_runtime
//app:linux_config.txt
//app:linux_runtime
//app:macos_config.txt
//app:macos_runtime
//app:server
//app:server.txt
//platforms:demo_linux
//platforms:demo_macos
//platforms:demo_os
//platforms:linux_target
//platforms:macos_target
cquery accepts normal build options and follows the branch selected by that
configuration.2
bazel cquery 'deps(//app:server)' --platforms=//platforms:linux
The captured configured-target output is:
//app:server (3a0f53e)
//platforms:macos_target (3a0f53e)
//app:linux_runtime (3a0f53e)
//app:server.txt (null)
//platforms:linux_target (3a0f53e)
@@platforms//host:host (3030ed4)
//platforms:linux (3030ed4)
@bazel_tools//tools:host_platform (3030ed4)
@@platforms//os:linux (3030ed4)
//platforms:demo_macos (3030ed4)
@@platforms//cpu:x86_64 (3030ed4)
//app:linux_config.txt (null)
//platforms:demo_linux (3030ed4)
//platforms:demo_os (3030ed4)
@@platforms//cpu:cpu (3030ed4)
@@platforms//os:os (3030ed4)
The config_setting nodes remain part of the analyzed graph, but only
//app:linux_runtime and its file remain from the three configurable runtime
branches. Configuration IDs are invocation-local details, so expect their exact
values to differ in another workspace state.
This is the right first question when a dependency appears on one platform but
not another, or when a refactor behaves differently under two named .bazelrc
configurations. 3.3.1 Configurable Attributes (select()) explains how the conditions themselves match.
here the diagnostic task is to observe the resulting graph.
Use the same query expression and change only the target platform to make a clean comparison:
bazel cquery 'deps(//app:server)' \
--platforms=//platforms:linux
bazel cquery 'deps(//app:server)' \
--platforms=//platforms:macos
--noimplicit_deps is useful when the immediate question is about explicit
BUILD-file edges. Leave implicit dependencies enabled when resolved toolchains
and Bazel-added edges are part of the investigation.3
A label is not a configured target
The default output pairs each label with a short configuration identifier in parentheses. Treat the identifier as opaque. One such pair is a configured target: a label analyzed with one set of build options. The same label may appear more than once when different dependency edges apply configuration transitions. A tool used during the build, for example, can be analyzed in an execution configuration while another path reaches the same label in the target configuration.1
That distinction prevents a common diagnostic mistake: deduplicating results by
label and accidentally hiding a transition. It also explains why cquery is
more precise but generally slower and more memory-intensive than query: Bazel
must analyze configured targets instead of only loading declared targets.1
When configured-target evidence becomes an input to CI work selection, carry
the universe, roots, flags, and configuration identity into the production
selector contract in 6.5.1 Affected-Target Service Contract. A cquery result without that
scope is not a reusable affected-target answer.
When a result contains an unexpected configuration, keep the identifier. A
later article, 5.2.4 bazel config — Configuration Inspection, shows how to inspect its complete option set.
For transition-specific investigations, cquery can annotate graph edges:
bazel cquery 'deps(//app:server)' \
--universe_scope=//app:server \
--transitions=lite
Use --transitions=lite to identify where a transition occurs. Escalate to
--transitions=full when you need the option diff. Its output is much noisier.1
This is especially useful after the rule-authoring model in
4.6.3 Toolchain Resolution: resolved toolchains and their execution
configurations become visible as concrete graph nodes. A target rejected as
incompatible may disappear from the configured graph, connecting the result to
4.6.7 IncompatiblePlatformProvider.
Control the universe before interpreting the answer
cquery needs top-level targets to analyze. By default, it derives them from
the target patterns in the query expression. For anything more involved than a
simple deps(//target), set --universe_scope explicitly to the targets whose
build context you mean to investigate.1
Consider a code generator whose tools attribute points to //tools:compiler.
These commands ask different questions:
# Analyze the compiler as a top-level target.
bazel cquery //tools:compiler
# Find the compiler as configured beneath the generator.
bazel cquery //tools:compiler \
--universe_scope=//generated:sources
The first command sees the compiler in the top-level target configuration. The
second builds the universe beneath the generator and can select its
execution-configured instance. --universe_scope is therefore not merely a
performance filter: it can change which configured instance is available and
which transitions lead to it.1
If several instances are present, config() selects one deliberately:
bazel cquery 'config(//tools:compiler, anyexec)' \
--universe_scope=//generated:sources
target, anyexec, null for unconfigured source-file targets, and a known
configuration-ID prefix are valid selectors. A missing configured instance is
information: that label was not analyzed in the requested configuration within
this universe.1
Inspect resolved values, not actions
For a quick human-readable view of a rule after configurable attributes have been resolved, use:
bazel cquery //app:server \
--platforms=//platforms:linux \
--output=build
For focused extraction, --output=starlark evaluates a formatting expression
once per configured target. The cquery dialect exposes providers(target) and
build_options(target) but is not a BUILD-file environment: it has no load,
glob, native, or rule declarations.1
This prints the selected target platform option alongside each result:
bazel cquery //app:server \
--platforms=//platforms:linux \
--output=starlark \
--starlark:expr='str(build_options(target)["//command_line_option:platforms"])'
This prints the output paths advertised through DefaultInfo:
bazel cquery //app:server \
--output=starlark \
--starlark:expr='"\n".join([f.path for f in providers(target)["DefaultInfo"].files.to_list()])'
Use --starlark:file=inspect.cquery with a format(target) function when the
logic no longer fits comfortably on one command line. For automation, prefer a
structured protocol output over scraping the human-readable label-and-hash
form.4
There is a firm boundary: cquery sees configured targets and providers, but it
is not the best view of exact compiler invocations, action inputs, or action
outputs. Continue to 5.2.3 bazel aquery — Action Graph when the question changes from “which
configured dependency?” to “which action will Bazel execute?”1
A repeatable diagnostic sequence
For an unexpected platform-dependent dependency:
- Reproduce the build options exactly, including
--config,--platforms, and custom build settings. - Run
cquery 'deps(...)'with an explicit--universe_scopematching the top-level build. - Compare configurations without removing the hashes from the output.
- Add
--transitions=liteif a dependency changes configuration. - Inspect a suspicious configuration with
bazel config, or switch toaqueryif the remaining question is about actions.
This sequence keeps the evidence at the same graph layer as the symptom. It avoids both the over-approximation of the loading graph and the overwhelming detail of the action graph.
cquery is the configured-graph lens: it resolves select() and follows
configuration transitions under the build options you provide. Read every
result as label plus configuration, set the top-level context with
--universe_scope, and use Starlark output to extract focused configured-target
data. Move to bazel config for option values and to aquery for actions.
Check your understanding · 4 questions
1.A cquery result lists //tools:compiler twice with different configuration IDs. What is the most useful interpretation?
Select one answer
2.You need to inspect //tools:compiler as it is used by //generated:sources, rather than as a top-level target. Which approach preserves that build context?
Select one answer
3.A dependency changes configuration on an unexpected edge. Which diagnostic choices are appropriate?
Select all that apply
4.Match each investigation to the most appropriate next view:
Drag each answer onto the matching prompt, or click an answer and then click a prompt
Footnotes
-
Configurable Query (cquery) — configured-target semantics, configuration IDs, universe scope, transitions, output formats, and
config()↩1 ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 -
Configurable Build Attributes — loading-time
queryversus analysis-timecqueryresolution ofselect()↩ -
Command-Line Reference — current
cqueryoption semantics for implicit dependencies, tool dependencies, and universe scope ↩ -
Deep dive into Bazel queries: from basics to advanced use cases — practical Starlark extraction and machine-readable output guidance ↩