5.2.5 genquery — Query as Build Artifact
recommendedSometimes a dependency query is not merely a diagnostic you run in a terminal.
A packager, documentation generator, or policy check needs the answer as a
declared input. genquery turns a query expression into one output artifact,
so Bazel can track that answer in the same graph as its consumer.1
This is narrower than “run any Bazel query during a build.” genquery evaluates
the unconfigured target graph, like 5.2.1 bazel query — Static Graph Analysis. It does not expose the
configured-target view of cquery or the action view of aquery.
Declare the question and its boundary
genquery is a built-in rule, so a BUILD file needs no load() statement:
genquery(
name = "server_dependencies",
expression = "deps(//app:server)",
scope = ["//app:server"],
opts = ["--output=label"],
)
Building //reports:server_dependencies produces one file named
server_dependencies. Its contents are the labels returned by the expression.
The target can then appear in another rule's file-valued attribute just like
another generated artifact. That downstream dependency is the important
difference from redirecting bazel query in a shell: Bazel knows which target
produces the file and which targets consume it.1 The
example declarations
show //reports:server_dependencies feeding the downstream
//reports:dependency_manifest rule. Build that consumer, then print both
artifacts:
$ bazel build //reports:dependency_manifest && cat bazel-bin/reports/server_dependencies && cat bazel-bin/reports/dependency_manifest.txt
//app:server
//lib:core
dependency: //app:server
dependency: //lib:core
The first two lines are the raw genquery file. The prefixed lines are the
downstream consumer's dependency_manifest.txt output.
The rule performs its graph query while Bazel analyzes the genquery target.
It registers a deterministic file-write action for the already computed result.
building the target materializes that result as the output artifact. This
means genquery can describe the graph used by another build step, but it cannot
inspect what actions have already executed or discover files produced dynamically
during execution.
scope is a dependency closure, not a search pattern
The required scope attribute defines the allowed graph. More precisely, the
query may visit only targets in the transitive dependency closure of the labels
listed in scope.1
For the example above, scope = ["//app:server"] admits //app:server and
everything reachable from it. It does not mean “only the //app package,”
and adding a package to scope is not a substitute for choosing a precise query
expression. A useful default is to put every explicit query root in scope,
then add another scope root only when the intended query genuinely needs its
closure.
The default strict = True makes an escape from that closure an error. With
strict = False, Bazel warns, skips the query path that left the closure, and
continues with the remaining in-scope result.1
genquery(
name = "partial_architecture_report",
expression = "somepath(//frontend:app, //storage:database)",
scope = ["//frontend:app"],
strict = False,
)
Do not use strict = False to make an underspecified report “succeed.” The
artifact may be syntactically valid while omitting exactly the edge the report
was meant to detect. Keep the default and make the intended graph boundary
explicit:
genquery(
name = "architecture_report",
expression = "somepath(//frontend:app, //storage:database)",
scope = [
"//frontend:app",
"//storage:database",
],
)
Scope is also why wildcard target patterns are not a supported interface for
this rule. The documented contract excludes expressions such as //pkg:* and
//pkg:all. Recursive //pkg/... patterns are rejected as well. BUILD-file
dependency attributes cannot declare matching wildcards, so they cannot express
a corresponding stable scope.1
Keep command options separate from rule attributes
expression, scope, strict, and compressed_output are attributes of the
BUILD rule. opts is only for options understood by the query engine:
genquery(
name = "server_dependency_kinds",
expression = "deps(//app:server)",
scope = ["//app:server"],
opts = [
"--output=label_kind",
"--noimplicit_deps",
],
)
Most ordinary bazel query options keep their command-line defaults when they
are absent. Exclusions from opts include --keep_going, --query_file,
--universe_scope, --order_results, and --order_output.1 In particular,
scope is not an
embedded spelling of --universe_scope: it is a declared rule dependency
boundary enforced by genquery.
Choose the output format for the consumer. label is a simple line-oriented
manifest. label_kind adds target kinds. Structured query formats are safer
when another program needs fields rather than display text. Unless the selected
format has its own graph ordering, Bazel sorts genquery results
lexicographically for deterministic output. The documented exceptions are
graph, minrank, and maxrank, plus an expression whose top-level function is
somepath.1
Set compressed_output = True only when the consumer expects gzip bytes. It
changes the artifact's file format. It is not merely an internal memory tuning
flag. It can also avoid a memory-intensive decompression step for large query
results.1
Prefer formats with deterministic, machine-oriented semantics, and make the consumer tolerant of the complete valid result set. A dependency closure can change when any in-scope BUILD declaration changes, even if the queried root's own BUILD file did not.
Decide whether the query belongs in the graph
Use genquery when its output is genuinely part of another target's declared
inputs: for example, a packaged dependency manifest or generated architecture
inventory. The artifact then rebuilds under Bazel's normal dependency tracking.
Keep exploratory investigations at the command line. Also keep multi-step repository workflows outside the build graph when they select what Bazel should build, mutate source files, or orchestrate several commands. Those workflows need the explicit boundary described in 3.7 Workflow Orchestration (Outside the Graph). Making a collector target depend on an enormous query result only hides orchestration inside the graph.
Finally, use a custom rule or aspect when the desired data comes from configured
providers rather than the declared dependency graph. genquery is intentionally
a query bridge. It is not an analysis-time replacement for cquery, aquery,
or provider-aware Starlark traversal.
genquery evaluates an unconfigured query expression and exposes the result
as one build artifact. Treat scope as an enforced transitive-closure boundary,
keep strict = True, put query-engine flags in opts, and choose a deterministic
output format that matches the downstream consumer. Use it only when the query
answer is itself a declared build input. Keep investigation and workflow
orchestration at the command line or outside the graph.
Check your understanding · 4 questions
1.What does scope = ["//app:server"] allow a genquery expression to traverse?
Select one answer
2.How does strictness affect an out-of-scope traversal?
Choose True or False for each sentence
3.Which choices support a deterministic, consumer-ready genquery artifact?
Select all that apply
4.Match each need to the appropriate Bazel interface:
Drag each answer onto the matching prompt, or click an answer and then click a prompt