# Buck2 build system for Cabal projects

**URL:** <https://discourse.haskell.org/t/buck2-build-system-for-cabal-projects/14770>\
**Category:** Announcements\
**Created:** [September 30, 2026, 11:18am UTC](https://discourse.haskell.org/t/buck2-build-system-for-cabal-projects/14770 "2026-09-30T11:18:32Z")\
**Posts on this page:** 17\
**Page:** 2

<div class="post-metadata">

**Author:** ![Ambrose](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/ambrose/32/5672_2.png) [@Ambrose](https://discourse.haskell.org/u/Ambrose)\
**Post date:** [October 2, 2026, 12:47am UTC](https://discourse.haskell.org/t/buck2-build-system-for-cabal-projects/14770/21 "2026-10-02T00:47:20Z")

</div>

> [@bmobn00b](#):
>
> unified CLI

and while were at it, let’s clean up that CLI too 😛

---

<div class="post-metadata">

**Author:** ![nh2](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/nh2/32/59_2.png) [@nh2](https://discourse.haskell.org/u/nh2)\
**Post date:** [October 3, 2026, 12:36pm UTC](https://discourse.haskell.org/t/buck2-build-system-for-cabal-projects/14770/22 "2026-10-03T12:36:59Z")

</div>

I’d like to give a different perspective:

People using Haskell in their (small / normal-sized) companies:

- Are not looking to build their own custom build system with Bazel or Buck or whatever.
- Are not looking for 20% improvements.
- Instead we need a 10x build performance improvement, quite urgently!  
The compile times are bad.  
They feel like they are much worse than they should be (“this code isn’t doing anything fancy, it’s just a lot of simple web server logic, why does it take 10 minutes to build 50k lines?”).  
They feel worse than in other languages (“with Go or Typescript or even Rust I wouldn’t have to sit around twiddling my thumbs so long”).
- Need this for all Haskell projects, without lots of manual effort.  
Not everybody has a neobank’s money to throw at build systems.
- See that GHC has a history of improving its own build system (Make → Hadrian → ?) but all other Haskell users benefit none of that.  
We need efforts to make the compiler fast, not for the compiler to be compiled fast!

Technically:

- `ghc --make` seems like a very good idea to me.  
It seems to be necessary to achieve 10x, because compiler internals / language understanding seem necessary for that.  
If any build tool (cabal/buck/whatever) invokes `ghc` as it is today, the build probably won’t be fast!  
As a reminder, `ghc --make` has all inputs in memory and processes them once.  
Invoking multiple `ghc` processes, will have to re-read the same inputs (e.g. `import`s) repeatedly, like C++ with `#include`s. The C++ world for 30 years has found no way to make this approach fast, and is moving more into the direction of what `ghc --make` does (“unity builds” that concat all source code into one compilation module, “ThinLTO” which does only very superficial per-module compilation and instead moves all optimisation and codegen into the final link step where the linker calls the compiler as a library).  
Multi-process independent-compilation-unit build systems (Blaze/Bazel, Buck, Make etc) work well for languages where “includes are cheap and the compilation unit is independent and expensive”, such as C. They don’t work so well for languages with sophisticated imported generic code, such as C++ or Haskell.  
As a reminder, in C++, if you have 1000 `.cpp` files, each of which uses `vector<Mytype>` somewhere, all the code for `vector<Mytype>` gets compiled and codegenned 1000 times independently, and then thrown away at the link step. C++ programmers suffer from this codegen amplification since forever and unity builds (single compiler binary invocations) are the only available solution.  
They provide large integer speedups, but often use 1 core and lose incremental builds.
- It seems good move more smarts into `ghc --make`:  
One of the most promising proposals I’ve seen is the [Improve build parallelism](https://gitlab.haskell.org/ghc/ghc/-/work_items/14095#note_701855) issue #14095 (orignally called `Improve parallelism in --make mode, and granularity in one-shot mode`).  
It splits the parse/rename/typecheck/desugar/godegen phases and makes them fine-granular and overlapping across modules. So that for `B imports A`, `B`’s compilation can happen largely in parallel to `A`’s comilation, where as with current GHC they are sequential.  
This would make ghc scale better to multi-core, without creating duplicate work (which separate oneshot compilation creates).  
Doing that is only really possible inside the compiler because it depends on the exact semantics of the language, its constructs, how codegen works, etc.
- (Of course any state of pipelining in the compiler could also be “externalised” to multi-process with a sufficiently good caching system; but caching is difficult so it seems in-process is far easier to get the results. It seems that should be done first before making the small steps available as separate process invocations so external tools like Make/Bazel/Buck can use them.)
- GHC has various pathological performance degradations (`deriving` being slow, `Generics` being slow, large records or sum types being slow). If one of those is present in your codebase, no amount of moving-compilation-duty around between processes will fix that.

I’d find it a bit of a shame if we went this route towards more external compilation and end up in the unfixable state that C++ is in now.

Probably worth noting that TypeScript’s `tsc` mirrors `ghc --make`’s way of working, and it does pretty well with that.

---

<div class="post-metadata">

**Author:** ![TeofilC](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/teofilc/32/3987_2.png) [@TeofilC](https://discourse.haskell.org/u/TeofilC)\
**Post date:** [October 3, 2026, 1:01pm UTC](https://discourse.haskell.org/t/buck2-build-system-for-cabal-projects/14770/23 "2026-10-03T13:01:22Z")

</div>

> [@nh2](#):
>
> - GHC has various pathological performance degradations

Indeed, I’d like to add to that having a “bad” module/package graph that has a long critical path.

I feel like there’s a lot of performance left on the table by GHC not making it clear when these sorts of issues occur. Users merely perceive their builds as being slow without any reason. With better observability, users could more easily fix these issues themselves.

I’m excited by improvements like using buck2 or improving --make mode, but at the end of the day, the easiest way to improve performance is to do less

---

<div class="post-metadata">

**Author:** ![Shimuuar](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/shimuuar/32/4876_2.png) [@Shimuuar](https://discourse.haskell.org/u/Shimuuar)\
**Post date:** [October 3, 2026, 9:10pm UTC](https://discourse.haskell.org/t/buck2-build-system-for-cabal-projects/14770/24 "2026-10-03T21:10:01Z")

</div>

> [@nh2](#):
>
> - Instead we need a 10x build performance improvement, quite urgently!

Realistically we won’t see that sort of improvement. Much more likely is small improvement here and there. And 20% is nothing to sneeze at.

> [@nh2](#):
>
> why does it take 10 minutes to build 50k lines?

Why indeed! I agree with @TeofilC we don’t have good tool for build time profiling. So we don’t really know where exactly all that build time is spent. So neither GHC maintainers nor library authors nor application authors understand what they could do to improve compilation speed.

---

<div class="post-metadata">

**Author:** ![elaforge](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/elaforge/32/3576_2.png) [@elaforge](https://discourse.haskell.org/u/elaforge)\
**Post date:** [October 3, 2026, 9:29pm UTC](https://discourse.haskell.org/t/buck2-build-system-for-cabal-projects/14770/25 "2026-10-03T21:29:18Z")

</div>

I had a good experience with ghci for interactive loop, loading .o files. I think almost no one else does this, because .o loading tends to get fatal regressions that no one else notices. I now use ghci -fobject-code and recompiling is slower, but it works around the latest bug. But ghc’s ability to compile fast with bytecode is quite a nice feature. I gather the latest versions are able to write bytecode to files, I’m not sure if anyone will use that seeing as no one else uses .o files, but in theory you have access to quite a wide range of compile fast ↔ run fast.

My theory about why no one but me uses that is that people use `cabal repl` so whatever cabal doesn’t do may as well not exist. If the external bytecode stuff is used by cabal by default that would be great, but unlike .o files you can’t reuse them for your final artifact.

There was an old idea of a ghc server which is like `ghc --make` except it stays in memory and you throw source at it. I didn’t pursue it much because a persistent ghci (or ghcid) is effectively that same experience for me, and as far as end-to-end batch compile throughput, I’m actually content.

`--make` isn’t entirely incompatible with external build, just partially. E.g. you can build at the package level and use `--make` inside. It’s inefficient for single file packages since ghc adds a bunch of overhead making a package db. So you want many-file packages, but that increases your granularity, have to wait for _all_ of GiantPkg to complete before starting on its dependent. What would it take to recover file level granularity? E.g. the build system is going to have a fast way to say “these source files changed”, and will naturally want to say “bring up to date with `-c`, next step”, but with `--make` it would have to go back up the graph the nearest transition out of haskell to figure which executables or libraries are involved, then ask `ghc --make` to build those, then ghc does the change detection and graph construction all over again itself. Sounds complicated and inefficient.

One path is the external build remains the driver, that I suppose means an interactive conversation where ghc has only rebuild responsibilities, not graph and (rough) change detection. That could be like `-c` but stays in memory. But it might be hard to extend that to cross module parallelism if that exists some day, because a “one node at a time” system maybe doesn’t give you a way to say “feed the entire haskell subset of the graph above the changed files”, that’s getting back to what it would have to do for `--make`.

The other path is ghc is the driver, which it sort of does in that it kind of knows how to build C, and knows what to do with .hsc (or maybe it doesn’t, cabal processes all those in advance?), and it could a shake-like DSL to describe what to do with needs that are out of date or not present but it’s just never going to catch up with a dedicated system.

Meanwhile the rest of the world (rust, python, go, …? I’m not familiar though) seems to have settled on per-language systems which are all unique, and then you have a separate meta-build-system at the CI level which is some bash or yaml thing, which may not be efficient but gets the job done and keeps people employed and lets all these systems evolve independently.

Anyway, this topic branches out forever so sorry to diverge! I’ll try cabal-buck2 out if I ever get time for it…

---

<div class="post-metadata">

**Author:** ![Ambrose](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/ambrose/32/5672_2.png) [@Ambrose](https://discourse.haskell.org/u/Ambrose)\
**Post date:** [October 3, 2026, 9:40pm UTC](https://discourse.haskell.org/t/buck2-build-system-for-cabal-projects/14770/26 "2026-10-03T21:40:12Z")

</div>

buck2 actually does make builds a lot faster due to its caching…

…assuming your dependency graph is under control, but even without buck2, a bad dependency graph is often the cause of slower and slower builds. [it helps to regularly poke at it and make improvements.](https://github.com/dustin/graphex)

if your dependency graph is chill, then even in a gigantic codebase, you can iterate quickly with a lazy loading ghci.

for CI, buck2 is pretty much unbeatable. module-level granularity for caching and rebuilds across package boundaries ensures CI does the least work necessary. it only has to build a commit’s worth of changed code + its dep closure at a time.

---

<div class="post-metadata">

**Author:** ![simonmar](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/simonmar/32/5058_2.png) [@simonmar](https://discourse.haskell.org/u/simonmar)\
**Post date:** [October 4, 2026, 2:06pm UTC](https://discourse.haskell.org/t/buck2-build-system-for-cabal-projects/14770/27 "2026-10-04T14:06:12Z")

</div>

> [@nh2](#):
>
> - Instead we need a 10x build performance improvement, quite urgently!

I absolutely agree, but I don’t think we get there by optimising GHC. A build system with caching and remote execution could give you 10x though. I’d say it’s reasonable to hope that we could get 10x faster CI for GHC using Buck2 + caching / remote execution. What about regular users who can’t set up a build farm? You can still use a build cache - think about all the people building the same Cabal packages with the same compiler, it’s almost certainly faster to download the artifacts.

I’ll make one other observation: whether to use `--make` is orthogonal to the build system choice. My Buck2 prototype uses `--make` currently, and that’s what they’ve been using at Meta for ever (and still enjoying 10x faster builds due to caching and remote execution). You can move to single-module compilation which improves parallelism at the expense of raw compile time, which may or may not be a win depending on how much parallelism you have, and you can improve the odds by using a persistent GHC server that caches interface files in memory.

Interestingly most of the ways you make builds faster is by caching at some level, it’s just a case of choosing the right place(s) to get the most bang for buck.

---

<div class="post-metadata">

**Author:** ![jackdk](https://avatars.discourse-cdn.com/v4/letter/j/9f8e36/32.png) [@jackdk](https://discourse.haskell.org/u/jackdk)\
**Post date:** [October 4, 2026, 9:23pm UTC](https://discourse.haskell.org/t/buck2-build-system-for-cabal-projects/14770/28 "2026-10-04T21:23:40Z")

</div>

> [@Ambrose](#):
>
> i gotta be honest - it is The One. Between this work and Mercury’s, the community should lean into buck2 imo.

I was very impressed by some of the talks I saw, but everything I looked at seemed to be experimental software that didn’t explain anything. How does one get started with Buck2? I failed to find a description of its theory of operation, or how to understand its syntax for naming targets, or… anything really, and gave up. (And I have Nix under my belt, so I feel like I have a decent stomach for thinly documented good ideas.)

---

<div class="post-metadata">

**Author:** ![simonmar](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/simonmar/32/5058_2.png) [@simonmar](https://discourse.haskell.org/u/simonmar)\
**Post date:** [October 5, 2026, 9:28am UTC](https://discourse.haskell.org/t/buck2-build-system-for-cabal-projects/14770/29 "2026-10-05T09:28:10Z")

</div>

> [@jackdk](#):
>
> I was very impressed by some of the talks I saw, but everything I looked at seemed to be experimental software that didn’t explain anything. How does one get started with Buck2? I failed to find a description of its theory of operation, or how to understand its syntax for naming targets, or… anything really, and gave up

The documentation has historically been lacking. It’s a bit better now, although some of the more obscure parts are still obscure - I have a lot of trouble with configuration resolution for example. But anyway, you could try looking at

> **[Architectural Model | Buck2](https://buck2.build/docs/concepts/architecture/)**
>
> High-level Overview

and in particular for target naming: [Build Target | Buck2](https://buck2.build/docs/concepts/build_target/)

The essence of it all is staged computation. The first stage evaluates all the build files, which are written in [Starlark](https://github.com/bazelbuild/starlark/blob/master/spec.md) (a pure subset of Python), and running these produces a graph of build rules. The implementation of each rule type is also written in Starlark, and the second stage evaluates the rules to produce an action graph: a graph of build actions where the edges are artifacts. This is what’s new about Buck 2 - in Buck 1 the rule implementations were written in Java in Buck itself, which made it really hard to modify and test any changes. In Buck 2 I can fiddle with the Haskell rules and get instant feedback. The language and API design rules out side effects in the `BUCK` files, and makes it really hard to write build code with untracked dependencies in the rule implementations.

---

<div class="post-metadata">

**Author:** ![dyniec](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/dyniec/32/2869_2.png) [@dyniec](https://discourse.haskell.org/u/dyniec)\
**Post date:** [October 5, 2026, 10:33am UTC](https://discourse.haskell.org/t/buck2-build-system-for-cabal-projects/14770/30 "2026-10-05T10:33:11Z")

</div>

See 6.5 in [https://ndmitchell.com/downloads/paper-build\_systems\_a\_la\_carte\_theory\_and\_practice-21\_apr\_2020.pdf](https://ndmitchell.com/downloads/paper-build_systems_a_la_carte_theory_and_practice-21_apr_2020.pdf)  
As for differences between buck and buck2, buck2 starts building as it is discovering the dependency tree, it is better at caching evaluation, it’s written in rust and it is much faster.

---

<div class="post-metadata">

**Author:** ![simonmar](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/simonmar/32/5058_2.png) [@simonmar](https://discourse.haskell.org/u/simonmar)\
**Post date:** [October 6, 2026, 9:58am UTC](https://discourse.haskell.org/t/buck2-build-system-for-cabal-projects/14770/31 "2026-10-06T09:58:51Z")

</div>

Update: I made a PR to start the discussion upstream in Cabal

> <https://github.com/haskell/cabal/pull/12410>
>
> I'm creating this as a draft for now, for discussion. It's not ready for merging… yet, but before I spend the time to write docs etc. I'd like to get a sense of whether the Cabal maintainers think this is worth polishing as is or whether we should do something different.
> 
> Background:
> \* See the \[README for haskell-buck2\](https://github.com/simonmar/haskell-buck2) for what this is
> \* \[Discussion on discourse\](https://discourse.haskell.org/t/buck2-build-system-for-cabal-projects/14770/29)
> 
> I've done a round of cleaning up and refactoring the (originally AI-generated) code, so it's in not bad shape. The internals are pretty well factored into distinct pieces and the effects are reasonably localised. It doesn't look to be abusing any Cabal APIs. Someone more familiar with the Cabal internals might be able to find ways to factor things better though.
> 
> Main outstanding issues
> \* We need to have a story for shipping/deploying \[haskell-buck2\](https://github.com/simonmar/haskell-buck2), which needs to be cloned (or copied) into the root of the Cabal project. It needs to be versioned somehow. Ship a tarball with Cabal or clone it on demand? One branch per Cabal release? Should there be a submodule in the Cabal repo, as in this PR? There's also a versioning issue with Buck2 itself, which moves in sync with its prelude so only certain releases of Buck2 will work with a particular prelude.
> \* No support for the new build hooks yet
> \* Some other missing features: \`asm-sources\`, \`cmm-sources\`, \`js-sources\`, \`frameworks\`, \`c2hs\`, \`foreign-library\`.
> \* Some tidying up of code/comments still needed.
> 
> 
> \* \[X\] Patches conform to the \[coding conventions\](https://github.com/haskell/cabal/blob/master/CONTRIBUTING.md#other-conventions).
> \* \[NOT YET\] Any changes that could be relevant to users \[have been recorded in the changelog\](https://github.com/haskell/cabal/blob/master/CONTRIBUTING.md#changelog).
> \* \[X\] \[Is the change significant?\](https://github.com/haskell/cabal/blob/master/CONTRIBUTING.md#is-my-change-significant) If so, remember to add \`significance: significant\` in the changelog file.
> \* \[NOT YET\] The documentation has been updated, if necessary.
> \* \[NOT YET\] \[Manual QA notes\](https://github.com/haskell/cabal/blob/master/CONTRIBUTING.md#qa-notes) have been included.
> \* \[X\] Tests have been added. (\*Ask for help if you don’t know how to write them! Ask for an exemption if tests are too complex for too little coverage!\*)

---

<div class="post-metadata">

**Author:** ![jaror](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/jaror/32/3271_2.png) [@jaror](https://discourse.haskell.org/u/jaror)\
**Post date:** [October 6, 2026, 11:25am UTC](https://discourse.haskell.org/t/buck2-build-system-for-cabal-projects/14770/32 "2026-10-06T11:25:22Z")

</div>

Couldn’t you just use the external command system for the first iteration?

---

<div class="post-metadata">

**Author:** ![simonmar](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/simonmar/32/5058_2.png) [@simonmar](https://discourse.haskell.org/u/simonmar)\
**Post date:** [October 6, 2026, 12:28pm UTC](https://discourse.haskell.org/t/buck2-build-system-for-cabal-projects/14770/33 "2026-10-06T12:28:21Z")

</div>

I wasn’t actually aware of that, but probably yes. It will have to link against the `cabal-install` library and I’m a bit worried about that API being unstable, but I’ll try it and see.

---

<div class="post-metadata">

**Author:** ![simonmar](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/simonmar/32/5058_2.png) [@simonmar](https://discourse.haskell.org/u/simonmar)\
**Post date:** [October 7, 2026, 1:22pm UTC](https://discourse.haskell.org/t/buck2-build-system-for-cabal-projects/14770/34 "2026-10-07T13:22:57Z")

</div>

Done: [cabal-buck2: Build a Cabal project with buck2 (the `cabal buck2` external command)](https://hackage.haskell.org/package/cabal-buck2-0.1.0.0)

---

<div class="post-metadata">

**Author:** ![0rphee](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/0rphee/32/2517_2.png) [@0rphee](https://discourse.haskell.org/u/0rphee)\
**Post date:** [October 17, 2026, 10:12pm UTC](https://discourse.haskell.org/t/buck2-build-system-for-cabal-projects/14770/35 "2026-10-17T22:12:33Z")

</div>

Just read this blogpost on the subject, interesting navigation of desiderata and build system problems: [The postmodern build system (updated 2025) - jade's www site](https://jade.fyi/blog/the-postmodern-build-system/)

---

<div class="post-metadata">

**Author:** ![michaelpj](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/michaelpj/32/417_2.png) [@michaelpj](https://discourse.haskell.org/u/michaelpj)\
**Post date:** [October 19, 2026, 8:49pm UTC](https://discourse.haskell.org/t/buck2-build-system-for-cabal-projects/14770/36 "2026-10-19T20:49:15Z")

</div>

This is very cool! I have wanted to try out Tweag’s version but it doesn’t seem ready for general use, so it’s great to see a version that’s trying for that from the get-go.

Have you talked to the Tweag folks at all? I wonder if they’ve solved any of the limitations already (and I’m quite excited about unlocking finer-grained cross-package parallelism!).

---

<div class="post-metadata">

**Author:** ![michaelpj](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/michaelpj/32/417_2.png) [@michaelpj](https://discourse.haskell.org/u/michaelpj)\
**Post date:** [October 20, 2026, 3:27pm UTC](https://discourse.haskell.org/t/buck2-build-system-for-cabal-projects/14770/37 "2026-10-20T15:27:12Z")

</div>

I tried this on my work codebase, and got pretty significant speedups (with some patches). The main win seemed to be avoiding a bunch of linking that `cabal` [currently does unconditionally](https://github.com/haskell/cabal/issues/12476), so maybe that can be improved in `cabal` also.

[Previous page](https://discourse.haskell.org/t/buck2-build-system-for-cabal-projects/14770.md?page=1)
