A from-scratch Buck

Meta has open sourced Buck2, its large-scale build system, via the Buck2 website and GitHub repository. Although Buck2 shares some concepts with Buck1 and Bazel, it is a complete rewrite written in Rust. The core design separates the build engine from all language-specific rules, supports remote execution and virtual file systems, and uses a single incremental dependency graph to increase parallelism and reduce redundant work.

At Meta, thousands of developers already run millions of Buck2 builds per day. Internal tests show builds completing about twice as fast as with Buck1, and engineers produce meaningfully more code when their builds run on Buck2.

Design principles

Buck2's architecture rests on a few core decisions that distinguish it from its predecessor:

  • Language-agnostic core. The core is written in Rust and has no knowledge of any language-specific rules. Rules such as how to build C++ are written in Starlark. This contrasts with Buck1, where all rules live in the core, and Bazel, which keeps C++/Java in the core.
  • One incremental dependency graph. Buck2 avoids the phases (target graph, action graph, execution) that Buck1 and Bazel use. A single graph eliminates a class of bugs and improves parallelism.
  • Performance-oriented rules API. The API includes advanced features for speed plus dynamic (monadic) dependencies for expressibility, while restricting them to preserve properties like fast queries and hermeticity.
  • Near-identical open source release. The public version matches Meta's internal version, with only toolchains and remote execution endpoints swapped for open source alternatives. The rules are released exactly as used internally, and some logical components (Starlark, Superconsole, Allocative, Gazebo) are split into separate crates.
  • Remote execution support. Buck2 uses the same API as Bazel for running actions on remote machines and has been tested with Buildbarn and EngFlow. It computes recursive digests efficiently for remote execution.
  • Virtual file system integration. Buck2 works with Sapling-based file systems where files are fetched on demand. It uses Watchman for file notifications and requests files and digests without direct file operations, making partial checkouts as fast as full ones.

The user experience

For day-to-day use, Buck2 behaves much like Buck1. Users define targets in a BUCK file:

rust_binary(
    name = “my_binary”,
    srcs = [“main.rs”],
    deps = [“:my_library”],
)

Building looks familiar: buck2 build //:my_binary. The main.rs file is a source and :my_library is a dependency in the same BUCK file. Buck2 is mostly compatible with Buck1's BUCK files.

Two user-visible changes stand out beyond speed. First, the console was redesigned using the Superconsole library, which Meta built for Buck2:

Second, a persistent daemon maintains the single dependency graph. When a BUCK file, dependency, or source file changes, Buck2 invalidates only the affected nodes instead of discarding entire graphs as Buck1 does. Each graph node has a key, a value, and a function that computes the value from the key and related keys, following the model in the "Build Systems a la Carte" paper.

Writing rules differently

While the user model closely follows Buck1, the rules model is entirely different. In Buck1, a rule was a Java class baked into the build system. In Buck2, rules such as rust_binary are decoupled and written in Starlark. Buck1 rules accumulated optimizations and powerful features like graph traversal over time, but they also had to obey complex invariants that were sometimes broken. Buck2's Starlark API forced those features to be abstracted into generic, reusable building blocks that are safe, expressive, and performant. Two examples illustrate the tradeoffs.

OCaml dependencies

OCaml libraries have an awkward dependency structure: files must compile in dependency order, and the dependencies are implicit. Bazel requires the dependency of A.ml on B.ml to be declared explicitly in the BUCK file. Buck1 and Buck2 both run the ocamldep tool to infer these dependencies automatically. Buck1 ran this tool just after parsing the BUCK file, which was outside the rules, and didn't track dependencies, so changed imports could lead to spurious compilation failures. Buck2 uses the dynamic_output primitive: it runs a command, reads the output file, then wires up the rest of the graph with the correct dependencies between the .ml files.

Producing a C++ link library requires combining build output with the transitive closure of its dependencies' output. Naively duplicating that set at every level of the graph causes O(n²) memory usage. Buck1 handled this with custom code in many rules, relying on shared Java values in memory. Buck2 has stronger abstraction boundaries, so this pattern is captured with transitive sets (tsets). Because tsets are abstract, they wire directly into the underlying dependency graph, keeping both memory and computation costs efficient—a pattern not possible in Buck1.

Getting started

Buck2 is most useful for moderately sized multi-language projects. The getting started guide covers installation and first steps, and feedback can be filed via GitHub issues.