4.12.2 Rule Extension API: parent + ctx.super()
extractx.super() is Bazel's experimental rule-inheritance hook. A child rule can declare that it extends a parent rule, run the parent implementation during analysis, receive the parent's providers, and then add or adjust the result before returning its own providers.1 It is for rule families with a real IS-A relationship, not for BUILD-file convenience wrappers. Use the macro-vs-rule boundary from 4.1.1 Macro vs Rule Decision Framework before reaching for it.
Why This Exists
The motivating problem is ruleset forking. Baseline language rules are moving toward independent rulesets, where users may need company-specific behavior without maintaining a full parallel fork.2 The proposed shape is to extend the baseline rule: keep common logic in the shared rule, then layer local behavior on top.
The concrete mental model is a Google-flavored Java library extending a baseline Java library. The child has its own implementation function, inherits the parent's public attributes, can override attributes such as adding an aspect, must call the parent rule with ctx.super(), and can return additional providers.3 The point is compatibility of contract: a specialized Java library should still mostly behave like a Java library.
That makes ctx.super() different from a macro wrapper. A macro can inherit or forward attribute shape in the loading phase, then declare one or more ordinary targets. It cannot run another rule's analysis implementation and edit the provider set returned by that target. Rule extension is analysis-phase reuse of another rule's contract.
the child inherits the supported target surface
Bazel merges attrs, fragments, constraints, and exec groups into the child rule definition
metadata merging does not run it. The child must call ctx.super()
None permits extension. False blocks it. An allowlist restricts callers.
Bazel merges public rule shape
child-specific surface stays small
The Rule Relationship
The relationship is declared on the child rule with the parent parameter to rule().4 The parent controls whether it can be extended with extendable, which may be an allowlist label or a boolean. The current API reference says Bazel defaults to allowing extensions.5
def _java_like_with_lint_impl(ctx):
parent_providers = ctx.super()
# Register child-specific actions or adjust the provider list here.
return parent_providers + [LintMetadataInfo(...)]
java_like_with_lint = rule(
implementation = _java_like_with_lint_impl,
parent = base_java_library,
attrs = {
"lint_config": attr.label(allow_single_file = True),
},
)
Treat that as API shape, not stable copy-paste. base_java_library here is a conceptual Starlark parent rule, not a promise that any particular built-in or external Java rule is currently extendable. This area is experimental and version-sensitive, and the details should be checked against the Bazel version your ruleset supports.6
What Bazel Merges
When parent is set, the official .bzl API reference says Bazel merges public attributes and advertised providers from the parent into the child.4 The child also matches the parent's executable and test settings, while fragments, toolchains, exec_compatible_with, and exec_groups are merged.4
The same reference calls out two boundaries that matter for production ruleset design. Legacy or deprecated rule() parameters may not be set in this mode, and the parent's incoming configuration transition is applied after the child's incoming transition.4 Do not leave that transition ordering implicit in a ruleset API: if the parent and child both affect configuration, test the exact supported Bazel versions before publishing the extension.
What The Child Can Change
ctx.super() itself is small: it calls the parent's implementation function and returns its providers.1 The child implementation then decides what to return. The child can add providers, and child rules can also add actions.7
Outputs are part of the provider contract: an extension can add outputs by modifying DefaultInfo.8 That is powerful, but it is also where the IS-A claim gets tested. If downstream users expect the parent rule's default files, runfiles, executable behavior, or language provider shape, the child should not casually replace them with a different semantic contract.
The safest mental model is "call parent, preserve the parent contract, add a narrow extension." If the child needs to delete core providers, reinterpret most attributes, or turn the target into a different kind of target, it is probably not an extension. Write a separate rule or use a macro facade over separate targets.
When To Use It
Reach for rule extension when all of these are true:
- The child target should be substitutable for the parent in most dependency positions.
- The parent contains expensive or complicated analysis logic you do not want to fork.
- The child needs analysis-time power: providers, actions, outputs, toolchains, or configured attributes.
- The parent rule intentionally allows extension through its
extendablepolicy.
Do not use it just to avoid writing a macro. If the goal is a nicer BUILD API over an existing rule call, a symbolic macro with inherited attributes is often the cleaner loading-phase abstraction. If the goal is to reuse a smaller implementation piece across multiple rules, the next item, 4.12.3 Subrules, covers the complementary HAS-A pattern.
Relationship To Subrules
ctx.super() composes whole rules by inheritance. The child is a kind of the parent. Subrules compose pieces of implementation inside one or more rules: a rule has a lint helper, a validation helper, or a compile helper. The two APIs are often presented together, but they solve different shapes of reuse.9
That distinction keeps rule architecture honest. Use ctx.super() when users should see one rule kind as an extension of another public rule kind. Use subrules when the shared code is an internal implementation service with private tools or dependencies.
rule(parent = ...) plus ctx.super() is experimental IS-A composition for rule authors. It reuses a parent rule's analysis implementation and provider contract, then lets the child add carefully scoped behavior.
Prefer it over forking when a specialized rule should remain compatible with a baseline rule. Prefer macros or subrules when the relationship is only BUILD-file convenience or internal implementation reuse.
Check your understanding · 3 questions
1.What does ctx.super() do in an extended rule implementation?
Select one answer
2.Which production boundaries should a ruleset author check before publishing a ctx.super() extension?
Select all that apply
3.True or false: choosing ctx.super() for rule reuse.
Choose True or False for each sentence
Footnotes
-
ctx -
ctx.super()calls the parent's implementation function and returns its providers. ↩1 ↩2 -
Bazel Rules - motivation to extend baseline rulesets instead of maintaining forks. ↩
-
Bazel Rules - Google Java Library extending Java Library example and
ctx.super()call requirement. ↩ -
.bzl files -
rule(parent = ...)merge contract for public attributes, advertised providers, rule kind settings, configuration fragments, toolchains, execution constraints, exec groups, and parent transition ordering. ↩1 ↩2 ↩3 ↩4 -
.bzl files -
extendableallowlist or boolean policy for rules that can be extended. ↩ -
Bazel Rules - roadmap notes describing the API as experimental and under pressure-testing. ↩
-
Bazel Rules - Q&A answer that rule extensions can add actions and providers. ↩
-
Bazel Rules - Q&A answer that adding outputs means modifying
DefaultInfo. ↩ -
Bazel Rules - presents rule extension and subrules as separate APIs for extending and structuring rules. ↩