4.12.4 Dormant Dependency Materialization

extra
Experimental, behind --experimental_dormant_deps

Dormant dependency materialization is an experimental rule-author API for dependency edges that are possible, but not immediately resolved. Instead of making every dependency in an attribute become an analyzed configured target up front, a rule can route selected edges through a materializer target, and Bazel substitutes the materialized dependencies for that placeholder when another target consumes it.1 The motivation is large builds where some expensive targets should be analyzed only on demand, such as dependency-injection-style graphs with dynamically determined dependencies.2

How can Bazel avoid eagerly analyzing every possible dependency?
The consumer names a materializer target, the materializer returns deps, and Bazel exposes only the selected deps.
Experimental Requires --experimental_dormant_deps. Prefer ordinary attrs, select(), and transitions unless deferred dependency analysis is the real requirement. Attribute materializer callbacks are a related experimental surface. The flow below focuses only on substitution through a materializer_rule() target.
CONSUMER ATTR
Attr names the placeholder
materializer target
attr.label_list(...)
deps = [
":resolve_impls",
]

The edge points to a resolver target, not directly to every possible dependency.

MATERIALIZER
Rule returns the selection
analysis provider
materializer_rule() target
return (
MaterializedDepsInfo(
deps = [
impl_a,
impl_b,
],
)
)

It must return exactly one provider. Each dep is a ConfiguredTarget or DormantDependency object, not a label string.

CONSUMER SEES
Bazel substitutes the deps
selected targets
:impl_a

provider data visible

:impl_b

provider data visible

:resolve_impls

not exposed as the dep

The dependency attr names a materializer target. Bazel analyzes it, reads MaterializedDepsInfo(deps = ...), and gives the consumer the materialized dependencies.

What Changes In The Graph

The normal rule-authoring model from 2.2.3 Static Action Graph is deliberately static: dependency attributes are known during analysis, configured targets are analyzed, and actions are registered before execution starts. select() and transitions are the mainstream ways to make that graph configuration-aware. select() chooses attribute values from configuration conditions in 3.3.1 Configurable Attributes (select()), and 4.7.2 Starlark Transitions rewrites configurations along edges.

Dormant dependency materialization is narrower. It does not let a tool discover arbitrary new labels during execution, and it does not make the action graph fully dynamic like the tree-artifact action-template case in 4.12.1 Dynamic Actions with ctx.actions.map_directory(). It changes how selected dependency edges are resolved: a target may depend on a materializer target, but see the materialized dependencies instead of the materializer target itself.1

That distinction matters for naming. This is target dependency materialization, not repository materialization from 4.9.1 Repository Rule Fundamentals. Repository rules create external repository directories before ordinary target analysis. Materializer rules operate inside the target analysis dependency graph.

The Materializer Rule

materializer_rule() creates a target kind that can be called from a BUILD file or macro to create materializer targets.1 Its implementation function has the same broad shape rule authors already know: it receives one ctx parameter and runs during analysis for each materializer target instance.1 The special contract is the return value: the implementation must return exactly one MaterializedDepsInfo provider.1

MaterializedDepsInfo is intentionally small. Its deps field is the list of dependencies to materialize, and the documented element types are ConfiguredTarget or DormantDependency objects.3 In other words, the materializer target is not the semantic dependency that consumers should reason about. It is a resolver target whose provider tells Bazel which real dependency objects stand in its place.

The API reference also exposes an allow_real_deps parameter on materializer_rule(), defaulting to False, for allowing materializer instances to have real, non-dormant dependencies. The docs mark this as allowlist-controlled.1 Treat that as a warning about maturity, not a convenience switch to design around. A materializer API should make its dependency-resolution boundary obvious to downstream rule authors.

Attribute Hooks And Resolution Rules

The other half of the feature is on attributes. attr.label() and attr.label_list() have an experimental materializer parameter, enabled by --experimental_dormant_deps.4 The documented behavior is that the materializer function can access the rule's non-dependency attributes, plus dependency attributes explicitly marked as available for dependency resolution, and returns either a dormant dependency or a list of dormant dependencies depending on the attribute type.4

That "available for dependency resolution" part is explicit too. Label-shaped attributes document for_dependency_resolution, which makes the attribute available for materializers. Only rules marked with the corresponding rule-level flag may be referenced through those attributes.5 On the rule declaration side, rule(dependency_resolution_rule = True) marks a rule as one that can be used through attributes available in materializers, and the docs require every attribute of such rules to be marked available in materializers so those rules do not depend on ordinary rules outside that boundary.6

Those constraints are the core design signal. The feature is not "any rule can peek at any dependency later." A rule author has to declare which attributes participate in dependency resolution, which rule kinds may appear there, and which materializer target produces the replacement dependency list.

How To Design Around It

Start from the same public-surface discipline as 4.2.7 Custom Provider Declaration and 4.2.8 Provider-as-Interface Pattern. If a consumer rule expects materialized dependencies to provide a provider, keep that provider contract documented and tested. The materializer may be experimental, but provider shape, attribute names, and failure modes are still the API your users will copy into BUILD files.

Good materializer candidates model a real choice in the dependency graph: optional implementation modules, generated dependency-injection edges, or other large sets where most possible edges are not needed in a given build. The operational question of when that complexity pays for itself belongs in 6.6.9 Bottleneck-Driven Optimization. At Level 4, the rule-author question is smaller: can you express the resolver target, its attrs, its provider output, and the consuming attrs without making the rule surface surprising?

Test at analysis time where possible. The important contract is not "did a compiler run". It is "did this target expose the expected provider data after dependency materialization." That makes 4.5.1 Analysis-Phase Testing a better first fit than an execution-heavy fixture. Add integration tests only when the materialized dependency affects real downstream rule behavior.

Current Status

Every public surface here says "experimental." The attribute hook is documented as experimental, may change at any time, and is gated by --experimental_dormant_deps.4 The API pages are also sparse: MaterializedDepsInfo documents only the provider purpose and its deps field.3 Use this as a design-study topic or a controlled ruleset experiment, not a default abstraction for published rules.

There is deliberately no runnable workspace linked from this page yet. Against the repository's pinned Bazel 9.1.0, even a materializer target that returns an empty MaterializedDepsInfo stops before useful analysis because the bundled @bazel_tools lacks the required materializer_rule_allowlist target. The flag alone is therefore not enough to provide a command that works with this project's Bazel distribution. The code still shows the documented API, but it is not runnable here. Before adopting it, verify the Bazel distribution, flag, allowlist policy, and the attribute that will use the materializer.

key takeaway

Dormant dependency materialization replaces a placeholder dependency edge with dependencies chosen by a materializer rule. The rule-author contract is materializer_rule() plus exactly one MaterializedDepsInfo, attribute materializer hooks, and dependency_resolution_rule / for_dependency_resolution boundaries.

Keep the mechanism isolated behind an experimental surface. Reach for ordinary attrs, select(), transitions, providers, and explicit dependency lists first. Use materializers only when the graph is large enough that deferred dependency analysis is the point.

Check your understanding · 3 questions

1.When a consumer target depends on a materializer target, what is the intended result?

Select one answer

2.Which statements describe the dormant dependency materialization API?

Select all that apply

3.Match each API surface to its role:

Drag each answer onto the matching prompt, or click an answer and then click a prompt

Answers
materializer_rule()
MaterializedDepsInfo
attr.label(materializer = ...)
for_dependency_resolution
rule(dependency_resolution_rule = True)
allow_real_deps
0 of 3 answered

Footnotes

  1. .bzl files - materializer_rule() creates materializer targets, substitutes materialized dependencies for the materializer target, and requires exactly one MaterializedDepsInfo return value. 1 2 3 4 5 6

  2. State of the Union - John Field, Engineering Manager & Tobias Werth, Software Engineer, Google - product-level motivation for dormant dependencies: on-demand analysis for expensive targets and dependency-injection-style dynamic dependencies.

  3. MaterializedDepsInfo - provider purpose and deps field containing ConfiguredTarget or DormantDependency objects. 1 2

  4. attr - experimental materializer parameter on label attributes, --experimental_dormant_deps gate, and materializer callback access/return constraints. 1 2 3

  5. attr - for_dependency_resolution marks label-shaped attributes as available for materializers.

  6. .bzl files - rule(dependency_resolution_rule = True) and the requirement that such rules use dependency-resolution-available attributes.