Skip to content

Build from ekapkgs instead of nixpkgs, keeping GHC 9.10.3 - #152

Open
hhefesto wants to merge 2 commits into
Stand-In-Language:masterfrom
hhefesto:hhefesto/ekapkgs
Open

Build from ekapkgs instead of nixpkgs, keeping GHC 9.10.3#152
hhefesto wants to merge 2 commits into
Stand-In-Language:masterfrom
hhefesto:hhefesto/ekapkgs

Conversation

@hhefesto

@hhefesto hhefesto commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Build from ekapkgs instead of nixpkgs

The Nix build moves to the ekala package ecosystem: corepkgs for the base system and the compiler, haskell-pkgs for the Hackage snapshot (Stackage LTS 24). flake-parts, haskell-flake, flake-compat and the nixpkgs input go; the flake has two inputs and uses corepkgs.lib.mkFlake.

GHC stays at 9.10.3. corepkgs carried only the 9.8.4 and 9.0.2 bindists; the 9.10.3 bindist and its haskell.packages.ghc9103Binary set are now upstream (ekala-project/corepkgs#178, corrected in #180), so corepkgs points at github:ekala-project/corepkgs. cabal-version: 3.12 and the sources are unchanged.

haskell-language-server builds on this set once its whole closure is put in one Cabal-syntax 3.14 scope (ormolu, fourmolu, cabal-add and the server want 3.14 where GHC 9.10 bundles 3.12). nix/haskell.nix carries that override (nixpkgs' configuration-common.nix recipe, the same as ekala-project/haskell-pkgs#4) so both inputs can point at ekala-project today; it goes when the fix lands upstream, in haskell-pkgs or corepkgs. nix develop .#full has HLS 2.13.0.0.

nixpkgs-free. No input, no import-from-derivation, no nixpkgs path in the closure. corepkgs' treefmt-nix input would pull a nixpkgs checkout into the lock for a formatter this flake never evaluates; it follows nix-lib instead. (Two contacts remain that are Nix's, not the flake's: fixed-output source tarballs may be substituted from cache.nixos.org, and nix develop takes its interactive bash from the user's nixpkgs registry entry.)

What is preserved — every entry point: nix build, nix run . / .#repl / .#lsp / .#format / .#format-lint / .#push-cachix, the per-executable apps CI and the Emacs mode use (.#telomare-repl, .#telomare-lsp), nix develop, nix flake check (the package with its five suites, plus format-lint), nix-build and nix-shell. The project and packaged Haskell tools use GHC 9.10.3; the cache-publishing helper uses the caller's installed Cachix.

Why mkFlake and not flake-parts + haskell-flake over corepkgs. Both were built and compared against the same pins: the package derivations are identical apart from the sdist source derivation and the telomare binaries are byte-identical. The hybrid is 51 lines shorter but needs four more locked inputs (flake-parts, nixpkgs-lib, haskell-flake, flake-compat), a checked-in cabal2nix file anyway (corepkgs defines no cabal2nix-unwrapped, so haskell-flake cannot run cabal2nix at evaluation time), settings.telomare.check = true (corepkgs' builder skips tests by default), and nulling three of haskell-flake's default tools that are broken or absent on this snapshot. Plain Nix over mkFlake is what corepkgs offers its consumers and needs none of that.

What changes

  • The Nix code lives in nix/: haskell.nix names the compiler and the tools, telomare.nix is the package policy (built from cabal sdist, tests on, no haddock or profiling — what haskell-flake did), tools.nix the shell apps, devshell.nix, and pins.nix/pkgs.nix/legacy.nix for default.nix and shell.nix, which read the pins from flake.lock instead of going through flake-compat.
  • The package expression is a checked-in cabal2nix file (nix/telomare-cabal2nix.nix, header has the regeneration command). A dependency missing from the file fails Setup configure, which is the drift check. No import-from-derivation anywhere (master ran cabal2nix in a derivation at every evaluation).
  • Shell apps carry an explicit shellcheck checkPhase: writeShellApplication's default refers to a shellcheck-minimal corepkgs does not define.
  • cabal-install is markUnbroken (the snapshot flags it with no broken dependency) and gets the snapshot's Cabal 3.16 in its scope: cabal-install 3.16 requires it, and GHC 9.10 bundles 3.12. shellcheck, which validates the shell apps, comes from the same 9.10.3 set rather than haskell-pkgs' top level, avoiding a second GHC closure. The pinned Cachix dependency cannot configure amazonka-core-2.0 against GHC 9.10.3, so push-cachix uses the caller's installed Cachix and fails immediately with installation guidance if it is missing.
  • The apps use gitMinimal: they only list and read tracked files, and the full git brings its manual into the closure. CI's install-nix-action and cachix-action move to v31 and v17 (the old ones warn about Node 20).
  • apps.telomare-lsp carries the version stamp like apps.lsp. The default dev shell is the minimum that builds the project (GHC and cabal-install); nix develop .#full adds haskell-language-server, hlint, stylish-haskell and ghcid. Hoogle is no longer built for either (one line to re-enable). systems is x86_64-linux until corepkgs marks aarch64-linux supported.
  • CI pulls from ekala-corepkgs as well and accepts the flake's nixConfig; the flake names both caches.
  • push-cachix enumerates the declared development shells and publishes and verifies both their outputs and their nix print-dev-env closures, including full with HLS and the editor tools. checks.push-cachix builds and shellchecks the helper in CI without uploading anything.

Verified (x86_64-linux, corepkgs 4ef4208d, haskell-pkgs 427f504)

  • nix flake check: checks.telomare / checks.default (the library, three executables and five test suites, built from the sdist tarball, all suites passing inside the derivation), checks.format-lint, and the new checks.push-cachix.

  • nix run .#telomare-repl -- --expr 'succ 7'8; nix run . -- --certificate simpleplus.tel renders the report; nix run .#lsp starts the server (exits on EOF).

  • nix-build yields the same store path as the flake's package; nix-shell --run 'cabal --version' → 3.16.1.0.

  • nix develop .#full -c haskell-language-server --version2.13.0.0 (GHC: 9.10.3); haskell-language-server src in that shell → Completed (26 files worked, 0 files failed).

  • nix run .#format-lint: no hints under hlint 3.10 and stylish-haskell 0.15.1.0; the pre-commit hook ran for every commit.

  • Against the nixpkgs build of master (2md7b96x…): tictactoe.telc (57,651 bytes), the simpleplus run transcript, its certificate and its --meter output are byte-identical to this branch's.

  • Cache-helper regression checks using mocked commands: both shell outputs and environment closures are submitted and verified; a failed full environment prevents publishing; missing Cachix produces the expected error. The real full environment was materialized and its closure checked for HLS, ghcid, hlint, and stylish-haskell. No live upload was performed during these checks.

Cost — nothing Haskell is cached upstream; nix run .#push-cachix from a green build fills the telomare cache with the compiler, the package, the shells and the tools, and CI pulls rather than builds afterwards.

@hhefesto hhefesto changed the title Build from ekapkgs instead of nixpkgs, keeping GHC 9.10.3 WIP Build from ekapkgs instead of nixpkgs, keeping GHC 9.10.3 DO NOT MERGE Sep 8, 2026
@hhefesto hhefesto changed the title WIP Build from ekapkgs instead of nixpkgs, keeping GHC 9.10.3 DO NOT MERGE Build from ekapkgs instead of nixpkgs, keeping GHC 9.10.3 Sep 8, 2026
The Nix build moves to the ekala package ecosystem: corepkgs for the base
system and the compiler, haskell-pkgs for the Hackage snapshot (Stackage
LTS 24). flake-parts, haskell-flake, flake-compat and the nixpkgs input go;
the flake has two inputs and uses `corepkgs.lib.mkFlake`. Everything, the
tools included, is built with one compiler, GHC 9.10.3.

GHC stays at 9.10.3: corepkgs carried only 9.8.4 and 9.0.2 bindists, so the
9.10.3 bindist and a `haskell.packages.ghc9103Binary` set were added
upstream (ekala-project/corepkgs#178, corrected in #180), with the bindists'
terminfo made to link (without that, nothing using haskeline links). The
corepkgs input is `github:ekala-project/corepkgs`. `cabal-version: 3.12`
and the sources are unchanged.

haskell-language-server needs its whole closure built in one Cabal-syntax
3.14 scope (ormolu, fourmolu, cabal-add and the server want 3.14 where GHC
9.10 bundles 3.12, and `extensions` is pinned to 3.12; Cabal refuses the
mix). nix/haskell.nix carries that override, the recipe of nixpkgs'
configuration-common.nix and of ekala-project/haskell-pkgs#4; it goes when
the fix lands upstream, in haskell-pkgs or corepkgs. Both inputs point at
ekala-project. corepkgs' treefmt-nix input drags nixpkgs into the lock for
a formatter this flake never evaluates; it follows nix-lib instead, so the
lock and the cache push carry no nixpkgs checkout.

Every entry point is preserved: `nix build`, `nix run .`/`.#repl`/`.#lsp`/
`.#format`/`.#format-lint`/`.#push-cachix`, the per-executable apps CI and
the Emacs mode use, `nix develop`, `nix flake check`, `nix-build` and
`nix-shell`. The default dev shell is the minimum that builds the project
(GHC and cabal-install); `nix develop .#full` adds haskell-language-server,
hlint, stylish-haskell and ghcid; hoogle is no longer built for either.

The Nix code lives in nix/: haskell.nix names the compiler and the tools,
telomare.nix is the package policy (built from `cabal sdist`, tests on, no
haddock or profiling, as haskell-flake did), tools.nix the shell apps,
devshell.nix, and pins.nix/pkgs.nix/legacy.nix for default.nix and
shell.nix, which read the pins from flake.lock. The package expression is a
checked-in cabal2nix file: ekapkgs does not define `cabal2nix-unwrapped`,
so `callCabal2nix` cannot evaluate; a dependency missing from the file
fails `Setup configure` loudly, and nothing is imported from a derivation
any more. Shell apps carry an explicit shellcheck check phase, since
`writeShellApplication`'s default refers to a `shellcheck-minimal` ekapkgs
does not define; shellcheck and cachix come from the 9.10.3 set rather than
haskell-pkgs' 9.8.4-built top level. The apps use `gitMinimal`: they only
list and read tracked files, and the full git brings its manual into the
closure. CI's install-nix-action and cachix-action move to v31 and v17. `cabal-install` is marked unbroken (the snapshot flags it with no
broken dependency) and built against the snapshot's Cabal 3.16, which
cabal-install 3.16 requires and GHC 9.10's bundled 3.12 is not.
`apps.telomare-lsp` carries the version stamp like `apps.lsp`; `systems`
is x86_64-linux until corepkgs marks aarch64-linux supported. CI pulls
from `ekala-corepkgs` too and accepts the flake's nixConfig.

Verified on x86_64-linux: the package builds from the sdist tarball with
its five suites passing; `nix run .#telomare-repl -- --expr 'succ 7'` gives
8; `nix-build` yields the flake's store path; `nix develop .#full -c
haskell-language-server --version` reports 2.13.0.0 on GHC 9.10.3 and the
server typechecks the 26 modules under src/; against the nixpkgs build of
master, `tictactoe.telc`, the simpleplus run transcript, its certificate
and its meter are byte-identical.
@hhefesto

Copy link
Copy Markdown
Contributor Author

For this project, nixpkgs offers better-integrated Haskell tooling and broader platform support, at the cost of a large monolithic package set. Ekapkgs lets us consume smaller, independently maintained core and Haskell repositories; this PR also makes build policy explicit and removes evaluation-time Cabal-to-Nix generation. The tradeoff is more local maintenance: HLS/Cabal overrides, a checked-in dependency expression, an externally installed Cachix, and currently x86_64-linux-only outputs. The result is easier to inspect, but not more succinct overall: the migration adds Nix code even though the flake entry point is smaller.

@hhefesto

Copy link
Copy Markdown
Contributor Author

The previous comment was written by astra.

One of the biggest upsides is that the ekapkgs implementation doesn't use IFD. It is a bit clearer, but we lose a bit of succinctness (although gain clarity?). I was also a bit partial to flake-parts' (haskell-flake) module use (it lets you <> nix configurations)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant