From f6832993842b73e85f5fe799fa00f1cb67b019e4 Mon Sep 17 00:00:00 2001 From: Moritz Angermann Date: Thu, 4 Jun 2026 11:55:03 +0900 Subject: [PATCH] trim -minimal-ghc closure: build happy/alex with the shell's GHC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `withGHCTooling` block in dynamic.nix sourced `happy` and `alex` from nixpkgs's `pkgs.haskellPackages`, which builds them with a different GHC than the shell's `compiler` (currently ghc-9.10.3 in nixpkgs vs ghc-9.8.4 in our shell). The Haskell library outputs of both packages live under `lib/ghc-/lib/*.dylib`; their .dylib path strings anchor the foreign GHC in the closure via Nix's reference scanner. On aarch64-darwin this dragged in ghc-9.10.3 (1.40 GB) and ghc-9.10.3-doc (753 MB) — ~2.15 GB of essentially-unused payload. Switch to haskell.nix's `tool` builder (same pattern as cross-js.nix and cross-windows.nix), which builds happy/alex with the shell's `compiler-nix-name`. The resulting library outputs reference the GHC that's already in the closure rather than dragging in a second one. While here: * swap `git` → `gitMinimal` — drops the heavyweight perl-modules and git-doc that aren't useful inside the dev shell (~150 MB cascade). * tool-map.nix: drop `inherit cabalProjectLocal` from happy/alex. They're standard mainline packages and build cleanly from regular hackage; the inherited cabalProjectLocal pinned a head.hackage SHA that was stale and broke fresh evaluations of `(tool "happy")` / `(tool "alex")`. While there, bump happy 1.20.1.1 → 2.1.7 and alex 3.2.7.3 → 3.5.4.0 to match what nixpkgs.haskellPackages was shipping previously, so users see no behavioural change. Measured on aarch64-darwin (ghc98-minimal-ghc): before: 6.12 GB / 228 paths after : 4.00 GB / 196 paths saved : 2.12 GB (34.7% smaller) Top removed paths: ghc-9.10.3 1399 MB ghc-9.10.3-doc 753 MB git-2.51.2 49 MB git-2.51.2-doc 15 MB perl5.40.0-SSLeay/Mozilla/IO-Socket-SSL (gitMinimal cascade) Verified inside the patched shell: $ ghc --version # 9.8.4 $ cabal --version # 3.17.0.0 $ happy --version # 2.1.7 (same as before) $ alex --version # 3.5.4.0 (same as before) $ git --version # 2.51.2 --- dynamic.nix | 19 ++++++++++++++++++- tool-map.nix | 7 +++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/dynamic.nix b/dynamic.nix index 5d2728c..a8b00f2 100644 --- a/dynamic.nix +++ b/dynamic.nix @@ -135,7 +135,24 @@ pkgs.mkShell { ) ++ attrValues haskell-tools ++ optionals withGHCTooling ( - with pkgs; [ python3 automake autoconf alex happy git libffi.dev ] + # `pkgs.alex` and `pkgs.happy` from nixpkgs are built with a different + # GHC than the shell's `compiler` (currently ghc-9.10.3 in nixpkgs + # haskellPackages), and their library outputs anchor that foreign GHC + # in the closure (~2.15 GB on Darwin). Use haskell.nix's `tool` builder + # — same pattern as cross-js.nix and cross-windows.nix — to build them + # with the shell's `compiler-nix-name`, so library outputs reference + # the GHC that's already in the closure rather than dragging in a + # second one. `gitMinimal` swaps out heavyweight git (saves ~150 MB + # of perl-modules + git-doc that aren't used inside the shell). + with pkgs; [ + python3 + automake + autoconf + (tool "alex") + (tool "happy") + gitMinimal + libffi.dev + ] ) ; diff --git a/tool-map.nix b/tool-map.nix index 1a3a152..14fe6c7 100644 --- a/tool-map.nix +++ b/tool-map.nix @@ -43,8 +43,11 @@ compiler-nix-name: tool: { cabalProject = __readFile (src + "/cabal.project"); configureArgs = "--disable-benchmarks --disable-tests"; }; - happy = { version = "1.20.1.1"; inherit cabalProjectLocal; }; - alex = { version = "3.2.7.3"; inherit cabalProjectLocal; }; + # happy/alex don't need head.hackage (they're standard mainline packages and + # build cleanly from regular hackage). Dropping `inherit cabalProjectLocal` + # also avoids depending on the head.hackage SHA pin going stale. + happy = { version = "2.1.7"; }; + alex = { version = "3.5.4.0"; }; cabal = rec { src = self.inputs.cabal; # We use the cabal.boostrap.project file, as we don't