Providers and Aspects in Modern Bazel
Understanding Providers
What Are Providers?
Providers are the mechanism for passing information between rules in Bazel. They enable structured data sharing and form the backbone of Bazel's dependency system.
Basic Provider Structure
python
# Define a provider
FooInfo = provider(
fields = ["sources", "includes", "deps_info"],
doc = "Information about Foo compilation",
)
# Rule implementation using provider
def _foo_library_impl(ctx):
# Collect information
sources = ctx.files.srcs
includes = ctx.attr.includes
# Create provider instance
return [
FooInfo(
sources = sources,
includes = includes,
deps_info = collect_deps(ctx.attr.deps),
),
]Common Built-in Providers
1. DefaultInfo
python
def _custom_binary_impl(ctx):
output = ctx.actions.declare_file(ctx.label.name)
return [DefaultInfo(
files = depset([output]), # Output files
runfiles = ctx.runfiles(files = [output]), # Runtime files
executable = output, # For binary targets
)]2. OutputGroupInfo
python
def _custom_library_impl(ctx):
# Different output groups for different purposes
return [OutputGroupInfo(
compilation = depset([compiled_lib]),
debug_info = depset([debug_symbols]),
documentation = depset([generated_docs]),
)]3. CcInfo
python
def _custom_cc_impl(ctx):
return [CcInfo(
compilation_context = cc_common.create_compilation_context(
headers = depset(ctx.files.hdrs),
includes = depset(ctx.attr.includes),
),
linking_context = cc_common.create_linking_context(...),
)]Provider Composition
1. Collecting from Dependencies
python
def _collect_providers(ctx):
# Gather providers from deps
dep_infos = [dep[FooInfo] for dep in ctx.attr.deps]
# Combine information
all_sources = depset(
direct = ctx.files.srcs,
transitive = [info.sources for info in dep_infos],
)
return FooInfo(
sources = all_sources,
includes = ctx.attr.includes,
deps_info = dep_infos,
)2. Provider Chains
python
def _library_impl(ctx):
# Get providers from dependencies
cc_info = ctx.attr.dep[CcInfo]
foo_info = ctx.attr.dep[FooInfo]
# Create new compilation context
new_cc_info = cc_common.merge_cc_infos(
cc_infos = [cc_info, process_foo_info(foo_info)]
)
return [new_cc_info]Understanding Aspects
What Are Aspects?
Aspects are a way to extend rules with additional behavior and outputs. They can traverse the dependency graph and collect or generate information.
Basic Aspect Structure
python
# Define an aspect
collect_sources = aspect(
implementation = _collect_sources_impl,
attr_aspects = ["deps"],
attrs = {
"_tool": attr.label(
default = "//tools:collector",
executable = True,
cfg = "exec",
),
},
)
def _collect_sources_impl(target, ctx):
# Collect sources from this target
srcs = target.files.to_list()
# Collect from dependencies
dep_sources = [dep[OutputGroupInfo].sources
for dep in ctx.rule.attr.deps]
return [OutputGroupInfo(
sources = depset(srcs, transitive = dep_sources)
)]Common Aspect Use Cases
1. Code Generation
python
proto_gen = aspect(
implementation = _proto_gen_impl,
attr_aspects = ["deps"],
attrs = {
"_protoc": attr.label(
default = "@com_google_protobuf//:protoc",
executable = True,
cfg = "exec",
),
},
)
def _proto_gen_impl(target, ctx):
# Generate code for each proto file
outputs = []
for src in target.files.to_list():
output = ctx.actions.declare_file(
src.basename.replace(".proto", ".pb.go")
)
outputs.append(output)
ctx.actions.run(
outputs = [output],
inputs = [src],
executable = ctx.executable._protoc,
arguments = ["--go_out=" + output.path, src.path],
)
return [OutputGroupInfo(
generated = depset(outputs)
)]2. Documentation Generation
python
doc_gen = aspect(
implementation = _doc_gen_impl,
attr_aspects = ["deps"],
attrs = {
"_doc_tool": attr.label(
default = "//tools:doc_generator",
executable = True,
cfg = "exec",
),
},
)
def _doc_gen_impl(target, ctx):
# Generate documentation
doc_file = ctx.actions.declare_file(
target.label.name + ".md"
)
ctx.actions.run(
outputs = [doc_file],
inputs = target.files.to_list(),
executable = ctx.executable._doc_tool,
arguments = ["-o", doc_file.path] +
[f.path for f in target.files.to_list()],
)
return [OutputGroupInfo(
docs = depset([doc_file])
)]Best Practices
1. Provider Design
python
# Good: Clear, focused provider
CompileInfo = provider(
fields = ["objects", "includes", "flags"],
doc = "Compilation artifacts and settings",
)
# Bad: Too broad/vague
BuildInfo = provider(
fields = ["stuff", "more_stuff"],
)2. Aspect Performance
python
# Good: Minimal traversal
collect_headers = aspect(
implementation = _collect_headers_impl,
attr_aspects = ["deps"], # Only traverse deps
)
# Bad: Excessive traversal
collect_all = aspect(
implementation = _collect_all_impl,
attr_aspects = ["*"], # Traverses everything
)3. Provider Propagation
python
def _merge_providers(ctx):
# Good: Explicit provider handling
dep_infos = [dep[MyInfo] for dep in ctx.attr.deps
if MyInfo in dep]
# Bad: Assuming provider exists
dep_infos = [dep[MyInfo] for dep in ctx.attr.deps]Key Takeaways
Provider Usage
- Use providers for structured data sharing
- Design providers with clear purpose
- Handle missing providers gracefully
Aspect Design
- Use aspects for cross-cutting concerns
- Minimize dependency traversal
- Cache results when possible
Best Practices
- Keep providers focused
- Design for performance
- Handle errors gracefully
Related Documentation
- Rules and Evaluation
- Dependencies and Actions
- Official Bazel Providers Documentation
- Official Bazel Aspects Documentation
Next Steps
- Learn about Rules and Evaluation to understand how providers and aspects fit into rule implementation
- Explore Dependencies and Actions to see how providers help share information between targets
- Study Build Rules to see providers and aspects in action
- Read about Toolchains to understand how providers help with tool configuration
