4.3.1 Print Debugging

print() is the quickest way to ask, "what did this Starlark code see while Bazel was planning the build?" It writes debug output with a DEBUG prefix and the source location of the call, so it is useful for temporary probes in macros and rule implementations.1 It is not an execution log. It tells you what happened while Bazel loaded or analyzed Starlark code, not what a compiler, test, or generated action printed later.

What It Shows

Use print() when the question is about Starlark values: which branch a macro took, what attribute value a rule implementation received, which files ctx.files.srcs resolved to, or what a provider contains.

def _mini_rule_impl(ctx):
    print("analyzing", ctx.label)
    print("srcs", ctx.files.srcs)
    print("deps", [dep.label for dep in ctx.attr.deps])
    # ...declare outputs, register actions, return providers...

For a macro, the print happens while Bazel is loading the package that calls the macro. The legacy macro docs describe this as a DEBUG log line during the loading phase, and recommend removing it or making it conditional before submitting shared code.2

For a custom rule, the print happens when the implementation function runs during analysis. That makes it a good companion to the rule-authoring concepts from 4.2.1 Rule Function: ctx.attr, ctx.file, ctx.files, ctx.executable, dependency providers, and labels are analysis-time objects. It also means the print cannot show generated file contents, action stdout, sandbox paths, or the final command a tool actually ran. Use 5.2.3 bazel aquery — Action Graph to inspect the post-analysis action graph. Use execution flags such as --subcommands and --sandbox_debug when the question is what happened while actions actually ran.3

Format Values Deliberately

Bazel's conversion of print() arguments to strings is debug-oriented and unspecified. It may differ from str() or repr() and may change over time.4 If you care about the exact shape, format the value yourself before printing:

print("provider fields:", sorted(dir(dep[MyInfo])))
print("raw value:", repr(ctx.attr.mode))
print("value type:", type(ctx.attr.mode))

That is especially useful when debugging configurable attributes or optional values. A compact type(...), repr(...), or selected provider field is usually more useful than dumping a large object and searching the terminal scrollback.

Remember Caching

A missing print line does not always mean your code path disappeared. It may mean Bazel did not re-evaluate that Starlark function in this invocation. This is the main printf-debugging gotcha: prints run only when the function executes, so cached results can hide a line you expected to see again.5

For macro debugging, a small source edit or changed macro argument is usually enough to force the package to reload. For rule implementation debugging, changing the rule code, the target's relevant inputs, or the requested target set can force analysis back through the implementation. Treat bazel clean as a last resort: it may make the print reappear, but it also destroys evidence about the incremental state you may be trying to understand.

Keep It Out Of Production

print() is intentionally noisy. It is only intended for debugging, because it spams all direct and indirect users of the .bzl file.6 If a message matters to users, use a proper error with fail() when Bazel should stop, or document the behavior through rule docs and validation. print() is for the rule author while investigating.

When you must keep a debug hook temporarily, make it off by default:

DEBUG = False

def _mini_rule_impl(ctx):
    if DEBUG:
        print("debug:", ctx.label, ctx.attr.mode)

The legacy macro docs allow the same pattern through an explicit debugging parameter that defaults to False, but the production default should still be silence.7 Shared rulesets and macros are transitively loaded by many users. A forgotten print in one helper can become noise across an entire workspace.

Know When To Switch Tools

Use print() for a narrow question about Starlark evaluation. Switch tools when the question moves to a different layer:

QuestionBetter tool
What targets did my macro actually emit?4.3.2 Macro Expansion Inspection with bazel query --output=build
What providers or configured deps does this target expose?cquery --output=starlark, covered deeper in 5.2.2 bazel cquery — Configured Graph
What command, inputs, outputs, or mnemonic did an action get?5.2.3 bazel aquery — Action Graph
Why does the action fail in the sandbox?--sandbox_debug and execution debugging from 5.8 Sandboxing
I need breakpoints and stepping through .bzl code.4.3.5 Starlark Debugger

That boundary keeps print debugging useful instead of turning it into a substitute for the whole debugging stack.

key takeaway

print() is a temporary probe for loading and analysis. Use it to inspect Starlark values while macros and rule implementations run. Do not use it to understand action execution.

Keep prints small, format values deliberately, remove or guard them before sharing the code, and switch to query, cquery, aquery, sandbox debugging, or the Starlark debugger when the question belongs to another phase.

Check your understanding · 3 questions

1.What does print() in a Starlark rule implementation tell you?

Select one answer

2.Which are appropriate uses for temporary print() debugging?

Select all that apply

3.True or false: production hygiene for print().

Choose True or False for each sentence

A missing repeat print can mean Bazel reused cached loading or analysis state.
print() is a good way to emit warnings to all users of a shared ruleset.
If a debug hook must remain temporarily, it should be guarded behind an off-by-default switch.
print() can replace aquery for inspecting an action's inputs and command line.
0 of 3 answered

Footnotes

  1. All Bazel filesprint() emits debug output with DEBUG and source location.

  2. Legacy Macros — macro debugging with print() during the loading phase.

  3. Sponsored Session: Enough Bazel to Be Dangerous: A Debugging Cookbook - Instructor: Alejandro Gomezaquery, --subcommands, and --sandbox_debug as separate debugging tools for actions and sandbox behavior.

  4. All Bazel files — argument formatting for print() is debug-oriented and unspecified.

  5. Sponsored Session: Writing Bazel Rules - Instructor: Jay Conrod — printf debugging and the caching gotcha.

  6. .bzl style guideprint() is only for debugging and spams direct and indirect users.

  7. Legacy Macros — remove prints or guard them behind debugging disabled by default.