Fine-grained Haskell builds with Nix's dynamic derivations
Haskell
53
65 commits
updated Aug 27, 2026
Compile each Haskell module as its own content-addressed Nix derivation, so changing one module rebuilds one module, not the world, and everyone who shares a cache shares the compiled results.
[!WARNING] Status: early prototype. Sandstone builds the bundled example project end to end, but it relies on unreleased Nix features and a work-in-progress fork of
hnix-store, assumes Linux (x86_64-linux), and still has sharp edges throughout. It is ready for experimentation and contribution, not for production builds.
Nix builds a Haskell package as a single derivation: GHC compiles the whole package at once, and the smallest source change invalidates the entire build. Cabal's incremental rebuilds soften this locally, but that work is invisible to Nix and unshared between machines and CI.
Sandstone makes the unit of caching a module rather than a package. Each module becomes its own derivation, so:
Sandstone is glue between two existing mechanisms:
-M dependency dump produces the inter-module dependency graph.The pipeline, from a source tree to a linked binary:
ghc -M to emit a Makefile describing each module's .o/.hi dependencies.Sandstone.GhcMakefile).object and interface outputs, each depending only on the interfaces of the modules it imports;hs-boot files, mutually-recursive modules, and nested module hierarchies are all handled. See example/ for a small project that exercises each case.
Sandstone needs a build of Nix with dynamic derivations and several experimental features enabled: ca-derivations, dynamic-derivations, recursive-nix, and nix-command. The repository pins a suitable Nix (dep/nix) and the hnix-store fork it builds against (dep/hnix-store), so you do not have to assemble them yourself.
Build the pinned Nix, then compile the bundled example project module-by-module:
# 1. Build the pinned Nix that supports dynamic derivations.
nix-build -A nix
# 2. Compile and link example/ through Sandstone, into an alternate store at /tmp/sand.
out=$(./result/bin/nix build -f . dyn-drvs-test-res \
--store /tmp/sand \
--substituters https://cache.nixos.org \
--extra-experimental-features 'ca-derivations dynamic-derivations recursive-nix nix-command' \
-L -v --print-out-paths)
# 3. The build used the /tmp/sand store, so prefix it to run the resulting binary.
/tmp/sand"$out"
dyn-drvs-test-res (defined in default.nix) runs Sandstone inside a Nix build under recursive-nix: it generates the per-module derivation graph for example/, emits it as a dynamic derivation, and Nix then builds that into a runnable executable.
src/Sandstone/: the library. Makefile parsing and the module graph (GhcMakefile), derivation generation (WriteDerivation), the Nix CLI backend (NixCLI), and accumulating errors (Error).src-bin/: two demos. demo-ca drives a build from the outside against a local store; demo-dyn-drv runs inside a Nix build and is the body of the dynamic derivation.example/: a small Haskell project used as a build fixture (nested modules and an hs-boot cycle included).dep/: pinned dependencies as nix-thunk thunks (nixpkgs, nix, and the hnix-store fork).default.nix / release.nix: Nix entry points and the dyn-drvs-test wiring.Sandstone is built and maintained by Obsidian Systems. We provide frontier engineering for high-assurance systems: we build production software in Haskell and Nix, and we're long-time stewards of open-source tooling like Obelisk, Reflex, and nix-thunk. We also contribute to Nix itself and to hnix-store, both of which Sandstone builds on.
If you're working on build systems, Nix, or Haskell and want a partner to help design, build, or ship it, we'd love to hear from you.
Sandstone is released under the BSD-3-Clause License, © 2025 Obsidian Systems LLC.
Haskell
79.1%
Nix
20.9%
Fine-grained Haskell builds with Nix's dynamic derivations
Haskell
53
65 commits
updated Aug 27, 2026
Compile each Haskell module as its own content-addressed Nix derivation, so changing one module rebuilds one module, not the world, and everyone who shares a cache shares the compiled results.
[!WARNING] Status: early prototype. Sandstone builds the bundled example project end to end, but it relies on unreleased Nix features and a work-in-progress fork of
hnix-store, assumes Linux (x86_64-linux), and still has sharp edges throughout. It is ready for experimentation and contribution, not for production builds.
Nix builds a Haskell package as a single derivation: GHC compiles the whole package at once, and the smallest source change invalidates the entire build. Cabal's incremental rebuilds soften this locally, but that work is invisible to Nix and unshared between machines and CI.
Sandstone makes the unit of caching a module rather than a package. Each module becomes its own derivation, so:
Sandstone is glue between two existing mechanisms:
-M dependency dump produces the inter-module dependency graph.The pipeline, from a source tree to a linked binary:
ghc -M to emit a Makefile describing each module's .o/.hi dependencies.Sandstone.GhcMakefile).object and interface outputs, each depending only on the interfaces of the modules it imports;hs-boot files, mutually-recursive modules, and nested module hierarchies are all handled. See example/ for a small project that exercises each case.
Sandstone needs a build of Nix with dynamic derivations and several experimental features enabled: ca-derivations, dynamic-derivations, recursive-nix, and nix-command. The repository pins a suitable Nix (dep/nix) and the hnix-store fork it builds against (dep/hnix-store), so you do not have to assemble them yourself.
Build the pinned Nix, then compile the bundled example project module-by-module:
# 1. Build the pinned Nix that supports dynamic derivations.
nix-build -A nix
# 2. Compile and link example/ through Sandstone, into an alternate store at /tmp/sand.
out=$(./result/bin/nix build -f . dyn-drvs-test-res \
--store /tmp/sand \
--substituters https://cache.nixos.org \
--extra-experimental-features 'ca-derivations dynamic-derivations recursive-nix nix-command' \
-L -v --print-out-paths)
# 3. The build used the /tmp/sand store, so prefix it to run the resulting binary.
/tmp/sand"$out"
dyn-drvs-test-res (defined in default.nix) runs Sandstone inside a Nix build under recursive-nix: it generates the per-module derivation graph for example/, emits it as a dynamic derivation, and Nix then builds that into a runnable executable.
src/Sandstone/: the library. Makefile parsing and the module graph (GhcMakefile), derivation generation (WriteDerivation), the Nix CLI backend (NixCLI), and accumulating errors (Error).src-bin/: two demos. demo-ca drives a build from the outside against a local store; demo-dyn-drv runs inside a Nix build and is the body of the dynamic derivation.example/: a small Haskell project used as a build fixture (nested modules and an hs-boot cycle included).dep/: pinned dependencies as nix-thunk thunks (nixpkgs, nix, and the hnix-store fork).default.nix / release.nix: Nix entry points and the dyn-drvs-test wiring.Sandstone is built and maintained by Obsidian Systems. We provide frontier engineering for high-assurance systems: we build production software in Haskell and Nix, and we're long-time stewards of open-source tooling like Obelisk, Reflex, and nix-thunk. We also contribute to Nix itself and to hnix-store, both of which Sandstone builds on.
If you're working on build systems, Nix, or Haskell and want a partner to help design, build, or ship it, we'd love to hear from you.
Sandstone is released under the BSD-3-Clause License, © 2025 Obsidian Systems LLC.
Haskell
79.1%
Nix
20.9%