Skip to content

feat: Update project, actions, Haskell format, and release more builds to GitHub release to support CI workflows#381

Open
RobertDeRose wants to merge 11 commits intoNixOS:masterfrom
RobertDeRose:feat/build-more-github-releases
Open

feat: Update project, actions, Haskell format, and release more builds to GitHub release to support CI workflows#381
RobertDeRose wants to merge 11 commits intoNixOS:masterfrom
RobertDeRose:feat/build-more-github-releases

Conversation

@RobertDeRose
Copy link
Copy Markdown

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-place and hk to install nixfmt without 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_64 and arm64. This led to updates to nixpkgs and the removal of the treefmt patch. Despite these efforts, GHC does not appear to reliably support static builds on arm64. 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:

  • Version detection now runs as a separate job. If no new version is found, the workflow exits early. This eliminates the need for conditional logic in individual steps.
  • Builds are executed via a matrix across supported operating systems and architectures, avoiding redundant checks per environment.
  • The release process is isolated into its own job. A release is only created if all builds succeed, and all generated artifacts are attached at creation time.
  • Additional, the release workflow can be triggered manually using a workflow_dispatch which will use the latest version number and build a pre-release with the tag being the ${version}-test, i.e. v1.2.0-test

Release artifacts are named by operating system and architecture. This enables tools like mise to 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 develop followed by cabal build.

Given the scope of changes, commits have been kept intentionally granular so that individual changes can be reviewed, modified, or dropped as needed.

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.
@github-actions
Copy link
Copy Markdown

Nixpkgs diff processing..

Will be available here

@dyegoaurelio
Copy link
Copy Markdown
Contributor

dyegoaurelio commented Apr 10, 2026

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:
looks like what you're trying to accomplish is having arm64 and darwin binaries, right?

@RobertDeRose
Copy link
Copy Markdown
Author

@dyegoaurelio — I may be off base, but my goal is to run nixfmt without requiring Nix to be installed, particularly on macOS (Intel and arm64).

I’m using nix-darwin and numtide:system-manager, with a bootstrap script that leverages mise-en-place to install linters and pre-commit hooks for hk, including nixfmt. This works when Nix is already installed, but fails otherwise.

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.

Copy link
Copy Markdown
Collaborator

@jfly jfly left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"

@nixos-discourse
Copy link
Copy Markdown

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

@RobertDeRose
Copy link
Copy Markdown
Author

@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 :)

@jfly
Copy link
Copy Markdown
Collaborator

jfly commented Apr 15, 2026

@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!

I honestly think that this PR solves the problem properly as is

What does "the problem" refer to here?

Comment on lines +91 to +108
- 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}"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 to nixfmt_linux_amd64)
  • nixfmt_linux_amd64
  • nixfmt_linux_arm64
  • nixfmt_darwin_amd64
  • nixfmt_darwin_arm64

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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]$

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

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

4 participants