coldwa.st_

Haskell · Cabal · build tooling

Cabal 2.0: the Release that Reshaped Haskell Builds

A topic this domain covered in 2017 · rewritten and brought up to date for 2026 · Coldwast Programming Guides

A dependency graph and a terminal running cabal new-build, illustrating Cabal 2.0 nix-style builds
Cabal 2.0 turned nix-style, per-project planning into the foundation of the modern toolchain.
Community-maintained, updated rewrite — original content, not authored by or affiliated with the domain's previous owner. Where 2017 facts have since changed, the 2026 status is called out explicitly.

Cabal 2.0 shipped in September 2017 alongside GHC 8.2, and in hindsight it is the release that set the direction of Haskell tooling for the next decade. It is the moment three things became real at once: nix-style local builds, Backpack, and a modern .cabal format with a saner version syntax. Here is what each one was, and where it stands today.

1. Nix-style local builds (new-build)

The headline feature. Earlier, sandboxes isolated dependencies per project but rebuilt everything from scratch and weren't reproducible. Cabal 2.0 promoted cabal new-build: a content-addressed model where each unique combination of package + version + flags + dependencies is built once, stored under a hash in a shared global store, and reused by every project whose build plan needs it.

$ cabal new-build      # 2017 spelling
$ cabal build          # 2026: this IS new-build — it became the default in Cabal 3.0

2026 status: the new-* prefix is gone. cabal build, cabal run, cabal repl and cabal test are all nix-style by default. The v2- aliases still exist for scripts; the old v1- commands are deprecated. What Cabal 2.0 introduced as opt-in is now simply how Cabal works.

2. Backpack — mixin packages and module signatures

Backpack added genuine parametric modularity to Haskell: a library can declare a module signature (an interface) and be type-checked against it without committing to a concrete implementation; a consumer then "mixes in" the implementation it wants. The classic example is a library written against an abstract Str signature that can be instantiated with String, strict Text or ByteString without code duplication.

-- in a .cabal file
library
    signatures:   Str
    exposed-modules: Generic.Algorithm

library impl-text
    mixins: indef-lib (Generic.Algorithm as Text.Algorithm)
                      requires (Str as Data.Text)

2026 status: Backpack works and is still supported, but it stayed a specialist tool rather than a mainstream pattern — type classes and ordinary parameterisation cover most needs, and tooling/IDE support for signatures remained thin. Reach for it when you genuinely need to compile the same code against several concrete types; otherwise you will rarely meet it.

3. A modern .cabal format

Setting cabal-version: 2.0 unlocked format improvements that are now everyday Haskell:

2026 status: all standard. The recommendation now is to set cabal-version: 3.0 or later (it implies everything 2.0 gave you, plus common stanzas from 2.2, the ** wildcard for data files, and more). Use 2.0 only if you must support a very old toolchain.

4. The dependency solver got better

Cabal 2.0 shipped solver improvements that made it both faster and more likely to find a valid install plan in a deep dependency graph, with clearer explanations when no plan exists. The solver has continued to improve since, but 2.0 is where "the solver usually just works" started to be true for non-trivial projects.

Why it mattered

Before 2.0, the Haskell build story was "install globally and pray", patched by sandboxes. After 2.0, it became "declare what you want, get a reproducible, shared, isolated build" — the same mental model as Nix, Cargo or modern package managers elsewhere. Every cabal build you run in 2026 is the direct descendant of what 2.0 made the default path.

If you're upgrading an old project

# Bump the format and modernise bounds:
cabal-version: 3.0          # was 1.x or 2.0

# Replace hand-written ranges with caret bounds where it fits:
build-depends:  base ^>= 4.18
              , text ^>= 2.0

# Drop any v1-/sandbox era commands from CI; use:
cabal build --enable-tests
cabal test
cabal freeze                # commit cabal.project.freeze for reproducibility

Related: Cabal sandboxes and what replaced them · Parallelising cabal-install · all guides