Skip to content

Static, self-contained binary releases (linux/macos/windows)#17

Merged
cornish merged 14 commits into
mainfrom
feat/binary-releases
Jun 27, 2026
Merged

Static, self-contained binary releases (linux/macos/windows)#17
cornish merged 14 commits into
mainfrom
feat/binary-releases

Conversation

@cornish

@cornish cornish commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds statically-linked, download-and-run wsitools binaries (all six codecs) for five OS/arch targets, attached to every vX.Y.Z GitHub Release. cgo forbids cross-compilation, so each target builds on a native runner; the six codec C libs are sourced as static libs via vcpkg static triplets and linked through cgo's existing pkg-config directives.

Design: docs/superpowers/specs/2026-06-26-static-binary-releases-design.md
Plan: docs/superpowers/plans/2026-06-26-static-binary-releases.md

What's here so far (incremental)

  • htj2k portability fix — OpenJPH via pkg-config instead of hardcoded /opt/homebrew (also fixes Intel-Mac/clean-env builds).
  • vcpkg manifest + static overlay triplets — pins the 6 codec libs; locally proven on macOS (all 6 build static, wsitools links, only system dylibs).
  • build-static composite action — shared recipe: vcpkg bootstrap + actions/cache + static build + doctor smoke (all 6 codecs) + per-OS static-linkage assertion.
  • release-canary.yml — this PR's CI: builds wsitools statically on linux/amd64 (musl/Alpine) to guard the static path.

Still to land on this branch: the 5-target release.yml matrix, macOS sign+notarize, SHA256SUMS + docs.

Test Plan

  • Canary (this PR) builds wsitools statically on linux/musl, green
  • Full 5-target matrix dry-run via workflow_dispatch into a prerelease
  • Download an artifact on a clean machine; doctor lists all 6 codecs with no codec libs installed

🤖 Generated with Claude Code

cornish and others added 14 commits June 26, 2026 13:14
Brainstormed design for attaching statically-linked, download-and-run
wsitools binaries to every vX.Y.Z GitHub Release across 5 OS/arch targets
(linux amd64+arm64, darwin arm64+amd64, windows amd64).

Decisions: native-runner hand-rolled GHA matrix (cgo defeats goreleaser's
cross-compile model); vcpkg static triplets for the 6 codec C libs uniformly
across platforms; musl/Alpine for fully-portable Linux; macOS sign+notarize;
htj2k kept on all 5 (fix the hardcoded /opt/homebrew path → pkg-config openjph).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Releasing stays tag-only, but add a one-target (linux/amd64 musl) build-only
canary on PRs/pushes touching release-relevant paths (release*.yml, vcpkg.json,
internal/codec/**, go.mod) so static/vcpkg-build rot is caught before a tag is
cut. Release matrix + canary share one vcpkg/build/smoke recipe to avoid drift.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
8 tasks: htj2k pkg-config fix; vcpkg manifest + static overlay triplets;
build-static composite action; linux/musl canary; 5-target release matrix;
macOS sign+notarize (secret-gated); SHA256SUMS + codec-matrix notes + docs;
final verification. Each CI task pushes + watches a real run; canary and
windows/openjph flagged as expected fix-forward points with documented fallbacks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Hardcoded -I/opt/homebrew paths only built on an Apple-Silicon Mac with
Homebrew. Switch to pkg-config: openjph (works on Homebrew + vcpkg) and
supply the C++ stdlib that openjph.pc omits via GOOS-conditional LDFLAGS
(-lc++ on darwin/clang, -lstdc++ on linux+windows/gcc). Prereq for portable
static CI builds; also fixes Intel-Mac and clean-env builds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pin the 6 codec C libs (libjpeg-turbo, openjpeg, libjxl, libavif, libwebp,
openjph) via a vcpkg manifest with a builtin baseline, plus static overlay
triplets (LIBRARY_LINKAGE=static) for osx/linux. The controller verifies the
static build links end-to-end as a separate gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
vcpkg bootstrap (pinned baseline) + GHA binary cache + static codec install
+ static go build + doctor smoke (asserts all 6 codecs) + per-OS static-
linkage assertion + artifact staging. Single recipe for canary + release
matrix so they cannot drift.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Runs the shared build-static composite on linux/amd64 (musl/Alpine container,
GOTOOLCHAIN=local) build-only — no upload/notarize — when release-relevant
paths change, so static/vcpkg build rot is caught before a tag is cut.
apk includes tar+zstd for actions/cache in-container.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
vcpkg downloads a glibc-linked cmake that cannot execute on musl/Alpine
(sh: cmake: not found, exit 127). Force vcpkg to use the apk-installed
musl-native cmake/ninja instead. Root-caused from the first canary run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
vcpkg's musl/Alpine support kept breaking (downloads glibc cmake that can't
exec on musl; Alpine 'ninja' is samurai which rejects vcpkg's 'ninja install
-v'). Switch Linux legs to glibc on the standard ubuntu runner where vcpkg is
first-class: static codec libs + dynamic glibc (mostly-static), runs on all
mainstream distros. Composite's linux linkage assertion now checks that no
codec shared lib is referenced (codecs static) rather than fully-static.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a build matrix job reusing the build-static composite across linux
amd64+arm64 (glibc/ubuntu, mostly-static), darwin arm64+amd64, windows amd64
(mingw, fully-static); archives (tar.gz / zip) and uploads each to the
release. workflow_dispatch dry-runs the matrix against an existing prerelease;
the notes job is gated to tag pushes. macOS signing added next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Windows: cgo resolved the broken Strawberry-Perl pkg-config.bat. Put the real
MSYS2 mingw64/bin on PATH (via setup-msys2 output, not git-bash /mingw64) and
set PKG_CONFIG=pkgconf so cgo reads the vcpkg .pc files.

darwin/amd64: the macos-13 Intel runner queues for hours (pool sunset). Build
the Intel binary on macos-latest (arm64) cross-targeting x86_64 via CGO_*FLAGS
=-arch x86_64 (vcpkg x64-osx-static triplet already pins x86_64); Rosetta runs
the smoke test. Eliminates the Intel-runner dependency.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Imports the Developer ID cert into an ephemeral keychain, codesigns with
hardened runtime + secure timestamp, notarizes via notarytool (App Store
Connect API key), staples + validates. Gated on the MACOS_CERT_P12_BASE64
sentinel: absent (forks / pre-provisioning) → skip with a warning and ship an
unsigned binary so the matrix stays green. Scrubs cert/key/keychain after.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
checksums job downloads the 5 archives, writes SHA256SUMS, uploads it. Release
notes gain a prebuilt-binary footer (codec matrix + verify/Gatekeeper note).
docs/RELEASING.md is the maintainer runbook (secrets, cut-a-release, dry-run,
troubleshooting incl. the musl/windows/cross-compile lessons). README gains a
'Prebuilt binaries (recommended)' install section.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Final-review hardening: set -e meant a mid-step failure (e.g. a notarization
rejection) skipped the trailing scrub, leaving the decoded .p12/.p8 and the
signing keychain on the runner for the rest of the job. Move cleanup into a
trap EXIT so it runs on every exit path. (Ephemeral on hosted runners; a real
fix for self-hosted.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cornish cornish merged commit 4daf350 into main Jun 27, 2026
4 of 5 checks passed
@cornish cornish deleted the feat/binary-releases branch June 27, 2026 00:47
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