Skip to content

fix(release): build static musl binaries for Linux artifacts - #537

Merged
agavra merged 5 commits into
agavra:mainfrom
mvanhorn:fix/503-static-linux-binaries
Aug 13, 2026
Merged

fix(release): build static musl binaries for Linux artifacts#537
agavra merged 5 commits into
agavra:mainfrom
mvanhorn:fix/503-static-linux-binaries

Conversation

@mvanhorn

@mvanhorn mvanhorn commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Builds the Linux release artifacts against musl instead of glibc, so the published binaries are statically linked and run on distros older than the builder. Closes #503.

The Linux jobs built with cargo build --release on ubuntu-latest, producing binaries dynamically linked against that runner's glibc. Anyone on an older distro got a glibc version error rather than a working binary.

Changes:

  • .github/workflows/release.yml: each matrix entry gains a build_target; Linux maps to the musl triple while macOS and Windows keep their existing target. Installs musl-tools, points the musl linker and CC at musl-gcc, builds with --target, and adds a verification step that runs --version on the produced binary and fails if readelf still shows an INTERP segment. The published artifact names are unchanged, so download URLs stay stable.
  • Cargo.toml: git2 moves to target-conditional dependencies. Non-musl keeps the current default-features-off build; musl adds zlib-ng-compat, because the vendored zlib otherwise links against the host library and defeats the static build.
  • Cargo.lock: picks up zlib-ng-compat's tree.
  • .github/workflows/ci.yml: a musl build job so a change that breaks the static build fails in CI rather than at release time.
  • RELEASE.md: note that Linux artifacts are static musl builds.

Validation

From a clean checkout of this branch:

cargo build           -> ok
cargo fmt --check     -> clean
cargo metadata        -> ok (target-conditional deps resolve)

I could not exercise the musl path locally; that needs a Linux runner with musl-tools, and I am on macOS. The parts I could not run are the ones the new CI job and the readelf INTERP check now cover, which is deliberate: the verification step is written to fail loudly on the runner rather than silently ship a dynamic binary again.

Worth your eye before merge:

  1. x86_64-unknown-linux-gnu and aarch64-unknown-linux-gnu are kept as the artifact target names while building musl underneath. That preserves existing download URLs, but the filename now says gnu for a musl binary. If you would rather rename the artifacts and accept the URL break, that is a one-line change per matrix entry.
  2. The libssl-dev/pkg-config install is replaced by musl-tools. Nothing in the current dependency tree needs OpenSSL (git2 is default-features-off), so this should be safe, but you know the tree better than I do.

AI was used for assistance.

Comment thread RELEASE.md Outdated
- Builds and uploads binaries for:
- `x86_64-unknown-linux-gnu` (Linux x64)
- `aarch64-unknown-linux-gnu` (Linux ARM64)
- `x86_64-unknown-linux-gnu` (Linux x64, static musl payload)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I really don't think lying about the libc is a good idea.

@mvanhorn

mvanhorn commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

Fair point, dropped the gnu names in 4339ba2.

The matrix had a separate target field that existed only so the archives could keep their old -unknown-linux-gnu names while building against musl. That's gone; the artifact name now comes from build_target, so the Linux archives are x86_64-unknown-linux-musl and aarch64-unknown-linux-musl.

That does break the existing Linux download URLs, which is what the old naming was protecting. RELEASE.md is updated to match, including the checksum commands for the Homebrew formula. Flagging it in case the install script or tuicr update pin those URLs anywhere I haven't found.

@lllamnyp lllamnyp left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks for picking up #503, this looks great!

@lllamnyp

lllamnyp commented Aug 3, 2026

Copy link
Copy Markdown

Thanks for taking #503.

I was testing out the build and found that running tuicr update replaces the statically linked binary with a dynamically linked one. Here is where this appears to be pinned:

("linux", "x86_64") => "x86_64-unknown-linux-gnu",
("linux", "aarch64") => "aarch64-unknown-linux-gnu",

Two things that might bite once this lands, though you'd know better than me: after a release that ships no gnu Linux archive, tuicr update on existing installs looks like it would fail outright rather than upgrade, since the asset is looked up by exact name. And the triple is baked in at compile time, so a fix landing in that same release wouldn't reach the binaries already installed.

@mvanhorn

mvanhorn commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Good catch on both counts, thanks for actually running the built binary.

The release workflow now builds x86_64-unknown-linux-gnu and aarch64-unknown-linux-gnu alongside the musl targets, with -C target-feature=+crt-static so the gnu archives are statically linked too. musl-tools and the musl linker config are gated on the -musl suffix so the gnu jobs skip them.

That keeps the exact asset names existing installs look for, so tuicr update on a gnu install still resolves and now lands a static binary rather than swapping in a dynamic one.

The compile-time triple point stands: anything already installed keeps whatever triple it was built with, so this only takes effect from the next release onward. Nothing to do about already-shipped binaries short of a re-install, which seems acceptable given the alternative was update breaking outright.

@agavra

agavra commented Aug 5, 2026

Copy link
Copy Markdown
Owner

We now rely on the official hombrew tap, I'm not sure if we need to change anything to get it to update after that. How are you intending to install the static binaries? Otherwise LGTM, I'm happy to merge it anyway.

@agavra

agavra commented Aug 5, 2026

Copy link
Copy Markdown
Owner

CI failure is unrelated, rebasing on main should fix this.

@lllamnyp

lllamnyp commented Aug 7, 2026

Copy link
Copy Markdown

How are you intending to install the static binaries?

Seeing as I brought up the original issue, I'll answer this. I usually prefer to install them by directly downloading them from GitHub's releases. The pipe-curl-to-shell approach also appears to be doing more or less the same thing. I don't think much if anything would change, regardless of install method.

@mvanhorn
mvanhorn force-pushed the fix/503-static-linux-binaries branch from 3e955f2 to 66a6420 Compare August 8, 2026 05:14
mvanhorn and others added 5 commits August 11, 2026 22:46
…uilt from

@wcampbell0x2a: "I really don't think lying about the libc is a good idea."

The matrix kept a separate 'target' field purely so the published archives
could keep their old -unknown-linux-gnu names while actually building
against musl. Dropped that field entirely; the artifact name now derives
from build_target, so a musl binary is named musl.

Download URLs for the Linux archives change as a result. RELEASE.md is
updated to match, including the checksum commands used for the Homebrew
formula.
tuicr update resolves its download by exact target-triple asset name, so a
release that shipped only musl archives would leave existing gnu installs
unable to upgrade.

Build x86_64/aarch64 -gnu alongside the musl targets with
-C target-feature=+crt-static so those archives stay statically linked, and
gate the musl-tools install and linker configuration on the -musl suffix so
the gnu jobs skip them.
@mvanhorn
mvanhorn force-pushed the fix/503-static-linux-binaries branch from 66a6420 to 08aa4e9 Compare August 12, 2026 05:47
@mvanhorn

Copy link
Copy Markdown
Contributor Author

Rebased onto main at 08aa4e9, so the unrelated CI failure should be gone. cargo check is clean and both workflow files still parse.

@lllamnyp thanks, that settles it. Direct download from the releases page is what the static musl artifacts are built for, and the curl-to-shell path pulls the same archives, so neither install method needs anything extra here.

@agavra
agavra merged commit 18c8a0d into agavra:main Aug 13, 2026
5 checks passed
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.

The binaries are not static

4 participants