Starlark in Buck2
Starlark in Buck2
Section titled “Starlark in Buck2”Buck2 is Meta’s next-generation build system, a complete rewrite of Buck1 in Rust. Unlike Bazel, Buck2 implements 100% of its rules in Starlark through its prelude system.
Key Concepts
Section titled “Key Concepts”File Types
Section titled “File Types”| File | Purpose | Example |
|---|---|---|
BUCK / TARGETS | Defines build targets in a package | cxx_binary(name = "app", srcs = ["main.cpp"]) |
*.bzl | Starlark extension files | def my_macro(name): ... |
*.bxl | BXL scripts (introspection) | def _main(ctx): ... |
.buckconfig | Project/cell configuration | [cells]\nprelude = prelude |
PACKAGE | Package-level defaults | package(visibility = ["PUBLIC"]) |
Targets and Labels
Section titled “Targets and Labels”Buck2 labels follow a similar pattern to Bazel:
cell//package/path:target_name│ │ ││ │ └── Target name│ └── Package path (directory with BUCK file)└── Cell nameExamples:
# Same package":my_lib"
# Different package"//src/lib:utils"
# Different cell"prelude//rust:defs.bzl"Cells and Projects
Section titled “Cells and Projects”Buck2 organizes code into cells:
[cells]root = .prelude = preludetoolchains = toolchainsEach cell can have its own prelude and configuration.
The DICE Model
Section titled “The DICE Model”Buck2’s core innovation is DICE (Distributed Incremental Computation Engine), a single unified computation graph:
┌────────────────────────────────────────────────────────────────────┐│ DICE Graph ││ ││ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ││ │ Parse │───▶│Configure│───▶│ Analyze │───▶│ Execute │ ││ │ BUCK │ │ Target │ │ Target │ │ Action │ ││ └─────────┘ └─────────┘ └─────────┘ └─────────┘ ││ ▲ ▲ ▲ ▲ ││ │ │ │ │ ││ └──────────────┴──────────────┴──────────────┘ ││ Incremental invalidation │└────────────────────────────────────────────────────────────────────┘Unlike Bazel’s phased model, DICE:
- Computes everything incrementally in a single graph
- Enables fine-grained invalidation
- Was designed for remote execution from day one
- Provides better parallelism through Rust’s async runtime
The Prelude System
Section titled “The Prelude System”The prelude is Buck2’s standard library—all rules are implemented in Starlark:
prelude/├── rust/│ ├── rust_binary.bzl│ ├── rust_library.bzl│ └── ...├── cxx/│ ├── cxx_binary.bzl│ ├── cxx_library.bzl│ └── ...├── python/├── go/├── java/└── ... (50+ directories)Prelude Structure
Section titled “Prelude Structure”def rust_binary_impl(ctx: AnalysisContext) -> list[Provider]: # All rule logic is here in Starlark toolchain = ctx.attrs._rust_toolchain[RustToolchainInfo]
output = ctx.actions.declare_output(ctx.attrs.name)
ctx.actions.run( cmd_args( toolchain.compiler, ctx.attrs.srcs, "-o", output.as_output(), ), category = "rustc", )
return [ DefaultInfo(default_output = output), RunInfo(args = cmd_args(output)), ]
rust_binary = rule( impl = rust_binary_impl, attrs = { "name": attrs.string(), "srcs": attrs.list(attrs.source()), "deps": attrs.list(attrs.dep()), "_rust_toolchain": attrs.toolchain_dep( default = "toolchains//:rust", providers = [RustToolchainInfo], ), },)Writing Rules
Section titled “Writing Rules”Rule Definition
Section titled “Rule Definition”def _my_rule_impl(ctx: AnalysisContext) -> list[Provider]: output = ctx.actions.declare_output(ctx.attrs.out)
ctx.actions.run( cmd_args( "my_tool", ctx.attrs.srcs, "-o", output.as_output(), ), category = "my_tool", )
return [ DefaultInfo(default_output = output), ]
my_rule = rule( impl = _my_rule_impl, attrs = { "out": attrs.string(), "srcs": attrs.list(attrs.source()), },)Type Annotations
Section titled “Type Annotations”Buck2’s starlark-rust supports full type annotations:
def _my_rule_impl(ctx: AnalysisContext) -> list[Provider]: srcs: list[Artifact] = ctx.attrs.srcs output: Artifact = ctx.actions.declare_output("out.txt")
content: str = "Files: " + ", ".join([s.short_path for s in srcs]) ctx.actions.write(output, content)
return [DefaultInfo(default_output = output)]Records and Enums
Section titled “Records and Enums”starlark-rust provides structured types:
# Define a record (like a struct)MyRecord = record( name = str, count = int, optional_field = field(str, default = "default"),)
# Use itdata = MyRecord(name = "example", count = 42)print(data.name) # "example"
# Define an enumStatus = enum("pending", "running", "complete", "failed")
# Use itcurrent = Status("running")if current == Status("complete"): print("Done!")Core APIs
Section titled “Core APIs”Providers
Section titled “Providers”Providers in Buck2 work similarly to Bazel:
# Define a providerMyInfo = provider(fields = { "output": provider_field(Artifact), "data": provider_field(list[Artifact], default = []),})
def _my_rule_impl(ctx: AnalysisContext) -> list[Provider]: output = ctx.actions.declare_output("out.txt")
# Collect from deps all_data = [] for dep in ctx.attrs.deps: if MyInfo in dep: all_data.extend(dep[MyInfo].data)
return [ DefaultInfo(default_output = output), MyInfo(output = output, data = all_data), ]Transitive Sets
Section titled “Transitive Sets”Transitive sets are Buck2’s equivalent to Bazel’s depsets:
# Define a transitive set typeMyTSet = transitive_set()
def _my_rule_impl(ctx: AnalysisContext) -> list[Provider]: # Create a transitive set my_files = ctx.actions.tset( MyTSet, value = ctx.attrs.srcs, children = [dep[MyInfo].files for dep in ctx.attrs.deps], )
return [MyInfo(files = my_files)]Transitive sets support projections for efficient transformations:
# Define with projectionsMyTSet = transitive_set(args_projections = { "as_args": lambda srcs: cmd_args(srcs),})
# Use projection in actionctx.actions.run( cmd_args( "process", my_tset.project_as_args("as_args"), ),)Actions
Section titled “Actions”# Run a commandctx.actions.run( cmd_args( toolchain.compiler, "--input", input, "--output", output.as_output(), ), category = "compile", identifier = input.short_path,)
# Write a filectx.actions.write(output, content)
# Copy a filectx.actions.copy_file(output, input)
# Create a symlinkctx.actions.symlinked_dir(output, { "lib": lib_dir, "include": include_dir,})BXL (Buck Extension Language)
Section titled “BXL (Buck Extension Language)”BXL scripts provide powerful introspection and custom commands:
def _impl(ctx: bxl.Context) -> None: # Query the build graph targets = ctx.uquery().deps("//my:target")
for target in targets: ctx.output.print(target.label)
# Access providers analysis = ctx.analysis(target) if DefaultInfo in analysis.providers(): info = analysis.providers()[DefaultInfo] ctx.output.print(f" outputs: {info.default_outputs}")
main = bxl_main( impl = _impl, cli_args = { "target": cli_args.target_expr(), },)Run with:
buck2 bxl //my:my_query.bxl:main -- --target //my:targetBXL Use Cases
Section titled “BXL Use Cases”- Custom query commands
- Build graph analysis
- Migration scripts
- CI/CD integrations
- Code generation pipelines
Dynamic Dependencies
Section titled “Dynamic Dependencies”Buck2 supports dynamic dependencies through anonymous targets:
def _my_rule_impl(ctx: AnalysisContext) -> list[Provider]: # Create a dynamic action that depends on file contents def dynamic_impl(ctx, artifacts, outputs): content = artifacts[input].read_string() deps = parse_deps(content)
for dep in deps: # Process each discovered dependency pass
ctx.actions.dynamic_output( dynamic = [input], inputs = [], outputs = [output.as_output()], f = dynamic_impl, )Starlark Dialect
Section titled “Starlark Dialect”Buck2’s starlark-rust has unique features:
| Feature | Buck2 (starlark-rust) |
|---|---|
| Recursion | Supported |
| Type annotations | Full support with runtime checking |
record type | Built-in |
enum type | Built-in |
Top-level for | Supported |
| DAP debugging | Supported |
Further Reading
Section titled “Further Reading”Source References
Section titled “Source References”This documentation is based on Buck2 source code at commit 824de34:
- starlark-rust (embedded)
- prelude rules - 87 directories of Starlark rules
- record.rs - Record type implementation
- Writing rules guide