5.2.5 genquery — Query as Build Artifact

recommended

Sometimes 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

When another target needs a dependency query as an input
genquery evaluates an unconfigured query inside a declared scope and writes its result to one build artifact.
GENQUERY RULE INPUTS
expressiondeps(//app:server) scope["//app:server"] boundary policystrict = True
ALLOWED TRANSITIVE CLOSURE OF SCOPE
//app:server
//lib:core
strict = True rejects a traversal that leaves this closure. The expression and scope are separate rule attributes.
BUILD ARTIFACT
One generated file
The query result
//app:server
//lib:core
DOWNSTREAM TARGET
Consumes the file
//reports:dependency_manifest
Bazel tracks the producer-to-consumer edge.
Scope bounds the walk. The selected labels become a tracked artifact that another target can consume.

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

Anti-pattern
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.

key takeaway

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

With the default strict = True, leaving the declared scope is an error.
With strict = False, Bazel expands scope to include the escaped path.
With strict = False, Bazel warns and can emit an incomplete in-scope result.

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

Answers
Explore dependencies during an investigation
Provide a dependency list as another target's input
Inspect configured providers in custom analysis logic
0 of 4 answered

Footnotes

  1. General Rulesgenquery rule semantics, scope and strictness, forbidden wildcard patterns and options, output naming and ordering, and compression behavior 1 2 3 4 5 6 7 8