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.

One label can identify several configured targets
The named universe supplies the top-level build context. A label plus one configuration identifies each analyzed node.
NAMED UNIVERSE · //generated:sources
--universe_scope=//generated:sources
rule label · //tools:compiler
TARGET CONFIGURATION
Application-facing instance
//tools:compiler (7f21…)
config(//tools:compiler, target)
Analysis resolves this instance's select() branches under the target configuration.
same rule label · //tools:compiler
EXECUTION CONFIGURATION
Tool instance
//tools:compiler (a904…)
config(//tools:compiler, anyexec)
A transition created a separate node. Its configurable values are resolved under the execution configuration.
source-file label · //tools:compiler.sh
NULL CONFIGURATION
Unconfigured file instance
//tools:compiler.sh (null)
config(//tools:compiler.sh, null)
Source files carry no build-options configuration.
Configured target identity = label + configuration. The universe controls which configured instances Bazel analyzes for this query.

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:

  1. Reproduce the build options exactly, including --config, --platforms, and custom build settings.
  2. Run cquery 'deps(...)' with an explicit --universe_scope matching the top-level build.
  3. Compare configurations without removing the hashes from the output.
  4. Add --transitions=lite if a dependency changes configuration.
  5. Inspect a suspicious configuration with bazel config, or switch to aquery if 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.

key takeaway

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

Answers
See resolved attributes after select() under the supplied configuration
Extract selected provider or build-option fields per configured target
Inspect the exact compiler invocation, action inputs, and action outputs
Inspect the complete option set behind a suspicious configuration ID
0 of 4 answered

Footnotes

  1. Configurable Query (cquery) — configured-target semantics, configuration IDs, universe scope, transitions, output formats, and config() 1 2 3 4 5 6 7 8 9

  2. Configurable Build Attributes — loading-time query versus analysis-time cquery resolution of select()

  3. Command-Line Reference — current cquery option semantics for implicit dependencies, tool dependencies, and universe scope

  4. Deep dive into Bazel queries: from basics to advanced use cases — practical Starlark extraction and machine-readable output guidance