feat: Update project, actions, Haskell format, and release more builds to GitHub release to support CI workflows#381
Conversation
The treefmt parallel execution fix has been merged upstream, so the fetchpatch overlay is no longer needed.
Bump upper bounds for directory (< 1.5), filepath (< 1.6), and megaparsec (< 9.8) to support building with newer GHC versions.
Whitespace-only changes (1-space indentation shift) resulting from the nixpkgs update. No functional changes.
pkgsStatic does not work on macOS, so conditionally define buildStatic only on Linux and use lib.optionalAttrs to expose nixfmt-static.
…ckout - Bump actions/checkout to v6, cachix actions to v31/v17, peter-evans actions to v4/v5 - Add Magic Nix Cache as fallback when Cachix token is unavailable - Consolidate duplicate checkout steps for pull_request_target into a single step with a conditional ref, preserving the PR merge branch checkout behavior needed for sandboxed builds - Add persist-credentials: false for security
Replace the single nix-based static binary release with a matrix build across 4 platforms (ubuntu x86/arm, macOS arm/intel) using cabal and GHC 9.10.3. Key changes: - Separate version detection into its own job with proper outputs - Build on macOS 14 runners to maximize OS compatibility via older dynamic libraries - Fail the release if no changelog section is found for the version - Upload per-platform artifacts and create a single GitHub release with all binaries
Allow manual dispatch to test the release build pipeline. Manual builds create a pre-release tagged v<version>-test to avoid overwriting real releases. The changelog step is lenient for pre-releases, using the commit SHA as the release note instead of failing.
GHC 9.10 enables -Wx-partial by default, which warns on uses of head and tail. Replace them with pattern matching: - Predoc.hs group: match on (first' : _) instead of head p - Predoc.hs goGroup: bind grp@(first' : rest') instead of head/tail - Types.hs walkSubprograms: use [single] pattern guard instead of length == 1 + head, which also avoids a redundant list traversal
Cabal's --enable-executable-stripping was not effective. Explicitly call strip on the binary after building, which removes ~17 MB from aarch64 and ~2.6 MB from x86_64 builds.
Repeated workflow_dispatch runs with the same cabal version would fail because the v<version>-test tag already exists. Delete the old pre-release and its tag first, only for pre-releases.
All stdin-piped nixfmt calls in test.sh now pass '-' as a positional argument, which is the non-deprecated way to read from stdin.
|
Nixpkgs diff processing.. Will be available here |
|
Thanks for the contribution. Can you explain why these changes are necessary and why the mentioned tools can't execute the static binaries we already provide? e.g https://github.com/NixOS/nixfmt/releases/download/v1.2.0/nixfmt Edit: |
|
@dyegoaurelio — I may be off base, but my goal is to run I’m using The current static build is limited to x86_64 Linux, which doesn’t help in my case since my Linux systems are arm64. Running it on macOS would require emulation (e.g., binfmt or Rosetta), which feels like the wrong approach for a tool like this. It should be straightforward to use across platforms. My thinking behind this PR is that once users have Nix set up, they don’t rely on these builds anyway. So providing only a single x86_64 Linux static binary has limited value. Instead, it seems more useful to support a broader set of targets. That’s the motivation for this PR. This started as an attempt to get static arm64 builds working, but the Haskell toolchain (at least from my limited experience) doesn’t seem to support that well. The additional changes were required in that effort; while the goal didn’t pan out, they still appeared to me to add value worth keeping. |
jfly
left a comment
There was a problem hiding this comment.
Discussed in today's team meeting. @RobertDeRose, thanks again for the contributions.
We're OK with providing static builds of nixfmt for more platforms than we already do, but we want to continue to use nix to do those builds.
Could you separate out your other changes into separate PRs? These 2 changes seem (almost) ready to go:
- "Update nixpkgs pin to nixos-25.11 and remove obsolete treefmt patch". But please keep us on the unstable branch instead.
- "Pass '-' to nixfmt in tests to avoid bare invocation warning"
|
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: https://discourse.nixos.org/t/formatting-team-meeting-2026-04-14/77037/1 |
|
@jfly So I made a PR based on just the two commits you suggested, but that breaks the builds because now I need a lot of the other changes I made to support the builds. How would you feel if I just revert most of the release workflow to just not include linux/arm64 static builds? Darwin already doesn't support static builds, so nothing can change there anyway. I honestly think that this PR solves the problem properly as is, since it's really just meant to help with things like CI workflows to be able to grab a build that works on Ubuntu or macOS without needing to install an entire nix environment and store a cache for everything. Perhaps I'm just doing my CI wrong though, I am a complete noob when it comes to Nix, I just started working on it in December on the as a hobby. I honestly just started down this rabbit hole because I wanted a formatter before I had a working nix setup :) |
|
@RobertDeRose, please just put together minimal, functional PRs. If it's easier for you to evolve this PR into one by removing commits, that's fine!
What does "the problem" refer to here? |
| - name: Setup Haskell | ||
| uses: haskell-actions/setup@v2.10.4 | ||
| with: | ||
| ghc-version: "9.10.3" | ||
|
|
||
| - name: Configure and build | ||
| run: | | ||
| echo "Extracting changelog section for version $VERSION" | ||
| cabal update | ||
| cabal configure \ | ||
| --enable-executable-stripping \ | ||
| --enable-optimization=2 | ||
| cabal build exe:nixfmt | ||
|
|
||
| name="nixfmt_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m)" | ||
|
|
||
| bin=$(cabal list-bin nixfmt) | ||
| strip "$bin" | ||
| cp "$bin" "${name}" |
There was a problem hiding this comment.
aside from the unrelated changes that deserve their own pr, this is the biggest issue with the approach taken.
All the target platforms are supported by nix, I don't see why we can't use nix to build these binaries on all these hosts.
Also, we're already providing a static binary for linux_amd64, so we should keep it to avoid breaking existing scripts.
the result should be something like this
nixfmt(alias tonixfmt_linux_amd64)nixfmt_linux_amd64nixfmt_linux_arm64nixfmt_darwin_amd64nixfmt_darwin_arm64
There was a problem hiding this comment.
Also, the provided binaries shouldn't be dynamically linked. They should be static binaries (so they don't depend on the user having specific libraries available)
[dyego@nixos-desktop:/tmp]$ curl -LO https://github.com/NixOS/nixfmt/releases/download/v1.2.0/nixfmt
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 0
100 5.98M 100 5.98M 0 0 6.48M 0 0
[dyego@nixos-desktop:/tmp]$ ldd ./nixfmt
ldd: warning: you do not have execution permission for `./nixfmt'
not a dynamic executable
[dyego@nixos-desktop:/tmp]$ curl -LO https://github.com/RobertDeRose/nixfmt/releases/download/v1.2.0-test/nixfmt_linux_x86_64
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 0
100 7.21M 100 7.21M 0 0 5.59M 0 00:01 00:01 0
[dyego@nixos-desktop:/tmp]$ ldd ./nixfmt_linux_x86_64
ldd: warning: you do not have execution permission for `./nixfmt_linux_x86_64'
linux-vdso.so.1 (0x00007f7f428da000)
libm.so.6 => /nix/store/pdwlyjkmc6icc0wb5gaw08m9ynqrg683-glibc-2.40-218/lib/libm.so.6 (0x00007f7f427ea000)
libgmp.so.10 => not found
libc.so.6 => /nix/store/pdwlyjkmc6icc0wb5gaw08m9ynqrg683-glibc-2.40-218/lib/libc.so.6 (0x00007f7f42400000)
/lib64/ld-linux-x86-64.so.2 => /nix/store/pdwlyjkmc6icc0wb5gaw08m9ynqrg683-glibc-2.40-218/lib64/ld-linux-x86-64.so.2 (0x00007f7f428dc000)
[dyego@nixos-desktop:/tmp]$
This PR is structured into a series of logically scoped commits to enable the GitHub release workflow to produce additional build artifacts. The goal is to allow tools such as
mise-en-placeandhkto installnixfmtwithout requiring a full Nix environment, particularly in constrained environments like GitHub Actions runners.A significant portion of the work focused on producing static builds for both
x86_64andarm64. This led to updates tonixpkgsand the removal of thetreefmtpatch. Despite these efforts, GHC does not appear to reliably support static builds onarm64. Given the limits of my Haskell experience, I stopped further investigation at that point.As a pragmatic alternative, this PR expands support across modern Linux and Darwin systems rather than pursuing full static portability.
The release workflow has been refactored to improve clarity and efficiency:
workflow_dispatchwhich will use the latest version number and build a pre-release with the tag being the${version}-test, i.e.v1.2.0-testRelease artifacts are named by operating system and architecture. This enables tools like
miseto resolve and install the correct binary automatically (e.g.,mise use github:NixOS/nixfmt).Validation was performed on Ubuntu Jammy and Noble, and macOS 15 and 26, across both arm64 and x86_64. All unit tests pass, and local builds succeed using
nix developfollowed bycabal build.Given the scope of changes, commits have been kept intentionally granular so that individual changes can be reviewed, modified, or dropped as needed.