4.3.2 Macro Expansion Inspection
Macro expansion inspection is the quickest way to answer a question that the BUILD file cannot answer by itself: "what targets did Bazel actually see after this macro ran?" Legacy macros from 4.1.2 Legacy Macros execute during loading and leave ordinary rule targets behind, so the macro call is only the source-level shorthand. bazel query --output=build prints the loaded target back in a BUILD-like form, with variables, glob() calls, and macro calls expanded far enough to show the effective rules Bazel is querying.1
Start With The Loaded Target
Use --output=build when the visible BUILD file contains a macro call but the error, dependency edge, or generated target name points at the rule below it:
bazel query --output=build //app:macro_select_error
The select-errors snippet includes a small legacy macro that emits a genrule. On Bazel 9.0.0, the query output shows the concrete rule, generator metadata, and the call stack where the target was instantiated:
# .../app/BUILD.bazel:48:20
genrule(
name = "macro_select_error",
generator_name = "macro_select_error",
generator_function = "legacy_macro_select",
generator_location = "app/BUILD.bazel:48:20",
outs = ["//app:macro_select_error.txt"],
cmd = "echo 'legacy macro saw a loading-phase value, not the resolved select() branch' >&2; exit 1",
)
# Rule macro_select_error instantiated at (most recent call last):
# .../app/BUILD.bazel:48:20 in <toplevel>
# .../app/select_macro.bzl:10:19 in legacy_macro_select
Read this as inspection output, not as a file to paste back into the repository. The query reference says the format represents targets "as if" they were handwritten in BUILD syntax, but it is not guaranteed to be a valid BUILD file.2 That distinction matters: the output is a debugging view over the loaded graph, not a source rewrite.
Use It When The Error Names The Wrong Thing
Macro-generated failures often mention the underlying rule kind rather than the macro the user called. A macro named extract_foo may fail inside a generated genrule. The reliable move is to inspect bazel query --output=build //work:extract_foo so you can see the attributes that were actually handed to Bazel.3
That is different from print() debugging in 4.3.1 Print Debugging. print() shows values while the macro or rule code is executing. --output=build shows the resulting target declaration after loading. In practice, use print() when you do not understand a branch inside the .bzl function, and use --output=build when you need to inspect the rule target that came out of that function.1
Filter By The Macro That Generated Targets
For a package full of generated targets, querying one label at a time is noisy. Bazel records generator metadata on effective rules, so you can search for targets generated by a macro function:
bazel query 'attr(generator_function, legacy_macro_select, //app:all)'
The same attr(generator_function, ...) pattern finds every target generated by a given macro function, and the companion generator_name field tracks the macro invocation's name value.4 A useful workflow is:
# Find the generated labels.
bazel query 'attr(generator_function, my_macro, //some/package:all)'
# Then inspect the generated BUILD-like form.
bazel query --output=build 'attr(generator_function, my_macro, //some/package:all)'
This gives you both the target set and the expanded declarations. In larger packages, keep the target pattern narrow first. //... is sometimes useful during investigation, but it loads far more packages than the macro you are debugging needs.
Stay Aware Of The Phase Boundary
Plain bazel query runs on the loaded, static target graph. It is the right tool for inspecting macro expansion because macros are loading-phase constructs, but it is not the final configured view of the build.5 If the question is "which branch of this select() is active for Linux?", move from query to cquery. That configured-query workflow is introduced later in 5.2.1 bazel query — Static Graph Analysis.
That boundary also explains why macro abstraction is expensive. The .bzl style guide warns that each abstraction layer makes BUILD files harder for humans and tools to inspect, and specifically calls out that query results can be harder to interpret when targets come from macro expansion.6 --output=build does not remove that cost, but it gives you a reliable map from the macro call back to the generated rule surface.
Use bazel query --output=build when you need to debug the rule target Bazel loaded, not the macro call you wrote. Pair it with attr(generator_function, ...) when you need to find every target created by a macro, and switch to cquery when configuration or select() resolution becomes the question.
Check your understanding · 3 questions
1.What is the main purpose of bazel query --output=build when debugging a legacy macro?
Select one answer
2.Which details can help connect a generated rule back to the macro that produced it?
Select all that apply
3.True or false: choosing between query tools for macro debugging.
Choose True or False for each sentence
bazel query is useful for inspecting legacy macro expansion because macros run during loading.bazel query --output=build is guaranteed to emit a valid BUILD file that can be pasted into the repository.cquery is the better tool when the active branch of a select() matters.print() and --output=build show the same information at the same point in evaluation.Footnotes
-
Legacy Macros — legacy macros disappear after loading, and
bazel query --output=buildshows expanded macro output. ↩1 ↩2 -
The Bazel Query Reference —
--output buildsemantics, generator metadata, and non-guarantee of valid BUILD output. ↩ -
Sponsored Session: Enough Bazel to Be Dangerous: A Debugging Cookbook - Instructor: Alejandro Gomez — macro debugging workflow with
bazel query --output=build. ↩ -
Query guide —
attr(generator_function, ...)recipe. Legacy Macros —generator_functionandgenerator_namedebugging notes. ↩ -
Bazel Query Deep Dive: From Basics to Advanced Use Cases - Łukasz Wawrzyk, VirtusLab — query as loading-phase graph inspection and cquery as configured graph inspection. ↩
-
.bzl style guide — macro abstraction costs and query interpretability caveat. ↩