Blog

Shipping Static Swift CLI Binaries via Homebrew Tap (macOS + Linux)

Published on Feb 21, 2026

This is the guide I wish I had before doing this for real.

It gives you a reproducible release pipeline for Swift CLIs:

  • prebuilt binaries for macOS + Linux
  • GitHub Releases
  • Homebrew formula updates in your own tap
  • recovery mode when a release partially fails

It also includes the practical edge cases that usually break first attempts.


What You Get

After setup, one local command:

./release.sh 0.1.0

will:

  1. Build macOS arm64 + amd64
  2. Build Linux static arm64 + amd64 (musl)
  3. Package archives with the correct internal binary name
  4. Create tag + GitHub release
  5. Update your Homebrew tap formula with fresh SHA256 values

And if release upload succeeded but formula update failed:

./release.sh --formula-only 0.1.0

First: Replace Placeholders

All file templates below use these placeholders:

  • __NAME__ (example: translate)
  • __REPO__ (example: atacan/translate)
  • __TAP_REPO__ (example: atacan/homebrew-tap)
  • __FORMULA_DESC__ (one-line formula description)
  • __VERSION_FILE__ (file where your CLI version string lives)

Expected version line format in __VERSION_FILE__:

version: "0.1.0"

If your project uses another format, update VERSION_REGEX in scripts/release/common.sh and the perl replacement in release.sh.


Repository Layout

Create these files in your CLI repo:

.
├── LICENSE
├── release.sh
├── .github/workflows/release.yml
└── scripts/release/
    ├── common.sh
    ├── build-macos.sh
    ├── Dockerfile.linux
    ├── build-linux.sh
    ├── test-linux-fedora.sh
    ├── package-archives.sh
    └── update-formula.sh

File: LICENSE

Use MIT (or your preferred license). Example MIT text:

MIT License
 
Copyright (c) [year] [fullname]
 
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
 
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
 
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

File: scripts/release/common.sh

#!/usr/bin/env bash
set -euo pipefail
 
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
 
NAME="__NAME__"
REPO="__REPO__"
TAP_REPO="__TAP_REPO__"
FORMULA_DESC="__FORMULA_DESC__"
VERSION_FILE="__VERSION_FILE__"
VERSION_REGEX='version: "[^"]+"'
 
OUTPUT_DIR="${REPO_ROOT}/output"
BIN_DIR="${OUTPUT_DIR}/binaries"
ARCHIVE_DIR="${OUTPUT_DIR}/archives"
PLATFORMS="macos-arm64 macos-amd64 linux-arm64 linux-amd64"
 
require_cmd() {
  local cmd="$1"
  if ! command -v "$cmd" >/dev/null 2>&1; then
    echo "Error: required command not found: $cmd" >&2
    exit 1
  fi
}
 
require_checksum_tool() {
  if command -v shasum >/dev/null 2>&1; then
    return
  fi
  if command -v sha256sum >/dev/null 2>&1; then
    return
  fi
  echo "Error: need shasum or sha256sum in PATH." >&2
  exit 1
}
 
compute_sha() {
  local path="$1"
  if command -v shasum >/dev/null 2>&1; then
    shasum -a 256 "$path" | awk '{print $1}'
    return
  fi
  sha256sum "$path" | awk '{print $1}'
}
 
class_name() {
  local raw="${1:-$NAME}"
  local first rest
  first="$(printf '%s' "${raw:0:1}" | tr '[:lower:]' '[:upper:]')"
  rest="${raw:1}"
  printf '%s%s\n' "$first" "$rest"
}
 
ensure_clean_repo() {
  if [[ -n "$(git -C "$REPO_ROOT" status --porcelain)" ]]; then
    echo "Error: working tree is dirty. Commit or stash changes first." >&2
    exit 1
  fi
}
 
ensure_archives_present() {
  local version="$1"
  local platform
  for platform in $PLATFORMS; do
    local archive="${ARCHIVE_DIR}/${NAME}-${version}-${platform}.tar.gz"
    if [[ ! -f "$archive" ]]; then
      echo "Error: missing archive: $archive" >&2
      exit 1
    fi
  done
}

File: scripts/release/build-macos.sh

#!/usr/bin/env bash
set -euo pipefail
 
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/common.sh"
 
if [[ "$(uname -s)" != "Darwin" ]]; then
  echo "Error: scripts/release/build-macos.sh must be run on macOS." >&2
  exit 1
fi
 
require_cmd swift
mkdir -p "$BIN_DIR"
 
build_one() {
  local arch="$1"
  local suffix="$2"
  local target="${BIN_DIR}/${NAME}-macos-${suffix}"
 
  echo "==> Building macOS ${suffix} (${arch})"
  swift build -c release --arch "$arch"
 
  local bin_path
  bin_path="$(swift build -c release --arch "$arch" --show-bin-path)/${NAME}"
  cp "$bin_path" "$target"
  chmod +x "$target"
 
  if command -v strip >/dev/null 2>&1; then
    strip "$target" || true
  fi
 
  "$target" --version >/dev/null
  echo "    wrote: $target"
}
 
build_one arm64 arm64
build_one x86_64 amd64
 
if command -v lipo >/dev/null 2>&1; then
  echo "==> Built macOS binaries"
  for suffix in arm64 amd64; do
    local_path="${BIN_DIR}/${NAME}-macos-${suffix}"
    echo "    $(basename "$local_path"): $(lipo -archs "$local_path")"
  done
fi

File: scripts/release/Dockerfile.linux

# Build static Linux binaries for both amd64 and arm64.
#
# Usage:
#   DOCKER_BUILDKIT=1 docker build \
#     --output type=local,dest=./output/binaries \
#     -f scripts/release/Dockerfile.linux .
#
# Output files:
#   __NAME__-linux-amd64
#   __NAME__-linux-arm64
 
FROM swift:6.2.3 AS builder
 
RUN swift sdk install \
    https://download.swift.org/swift-6.2.3-release/static-sdk/swift-6.2.3-RELEASE/swift-6.2.3-RELEASE_static-linux-0.0.1.artifactbundle.tar.gz \
    --checksum f30ec724d824ef43b5546e02ca06a8682dafab4b26a99fbb0e858c347e507a2c
 
WORKDIR /build
 
COPY Package.swift Package.resolved ./
RUN swift package resolve
 
COPY Sources/ Sources/
COPY Tests/ Tests/
 
# Optional musl patch example for dependencies that hard-code Glibc (e.g. TOMLKit).
# If your build fails with "Unsupported Platform" under musl, enable and adapt this.
# RUN find .build/checkouts/TOMLKit/Sources -type f -name '*.swift' \
#     -exec perl -i -pe 's/#elseif canImport\(Glibc\)/#elseif canImport(Glibc) || canImport(Musl)/g; s/^\s*import Glibc$/#if canImport(Musl)\n\timport Musl\n#else\n\timport Glibc\n#endif/' {} +
 
RUN swift build --disable-automatic-resolution -c release --swift-sdk x86_64-swift-linux-musl \
    && mkdir -p /output \
    && cp "$(swift build --disable-automatic-resolution -c release --swift-sdk x86_64-swift-linux-musl --show-bin-path)/__NAME__" \
       /output/__NAME__-linux-amd64
 
RUN swift build --disable-automatic-resolution -c release --swift-sdk aarch64-swift-linux-musl \
    && cp "$(swift build --disable-automatic-resolution -c release --swift-sdk aarch64-swift-linux-musl --show-bin-path)/__NAME__" \
       /output/__NAME__-linux-arm64
 
RUN apt-get update \
    && apt-get install -y --no-install-recommends binutils-x86-64-linux-gnu binutils-aarch64-linux-gnu \
    && rm -rf /var/lib/apt/lists/* \
    && x86_64-linux-gnu-strip /output/__NAME__-linux-amd64 \
    && aarch64-linux-gnu-strip /output/__NAME__-linux-arm64
 
FROM scratch
COPY --from=builder /output/ /

File: scripts/release/test-linux-fedora.sh

#!/usr/bin/env bash
set -euo pipefail
 
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/common.sh"
 
require_cmd docker
if ! docker info >/dev/null 2>&1; then
  echo "Error: Docker daemon is not running." >&2
  exit 1
fi
 
AMD64_BIN="${BIN_DIR}/${NAME}-linux-amd64"
ARM64_BIN="${BIN_DIR}/${NAME}-linux-arm64"
 
for bin in "$AMD64_BIN" "$ARM64_BIN"; do
  if [[ ! -f "$bin" ]]; then
    echo "Error: missing Linux binary: $bin" >&2
    echo "Run scripts/release/build-linux.sh first." >&2
    exit 1
  fi
  chmod +x "$bin"
done
 
echo "==> Testing linux/amd64 binary in Fedora"
docker run --rm --platform linux/amd64 \
  -v "$AMD64_BIN":/app:ro \
  fedora:latest \
  /app --version
 
echo "==> Testing linux/arm64 binary in Fedora"
docker run --rm --platform linux/arm64 \
  -v "$ARM64_BIN":/app:ro \
  fedora:latest \
  /app --version
 
echo "==> Fedora runtime check passed for both Linux binaries"

File: scripts/release/build-linux.sh

#!/usr/bin/env bash
set -euo pipefail
 
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/common.sh"
 
require_cmd docker
if ! docker info >/dev/null 2>&1; then
  echo "Error: Docker daemon is not running." >&2
  exit 1
fi
 
DOCKERFILE="${SCRIPT_DIR}/Dockerfile.linux"
if [[ ! -f "$DOCKERFILE" ]]; then
  echo "Error: Dockerfile not found at $DOCKERFILE" >&2
  exit 1
fi
 
mkdir -p "$BIN_DIR"
 
echo "==> Building static Linux binaries with Docker"
DOCKER_BUILDKIT=1 docker build \
  --file "$DOCKERFILE" \
  --output "type=local,dest=${BIN_DIR}" \
  "$REPO_ROOT"
 
echo
echo "==> Verifying binaries exist"
for arch in amd64 arm64; do
  bin="${BIN_DIR}/${NAME}-linux-${arch}"
  if [[ ! -f "$bin" ]]; then
    echo "Error: missing built binary: $bin" >&2
    exit 1
  fi
  chmod +x "$bin"
  if command -v du >/dev/null 2>&1; then
    echo "  $(basename "$bin"): $(du -h "$bin" | awk '{print $1}')"
  else
    echo "  $(basename "$bin"): built"
  fi
done
 
echo
"${SCRIPT_DIR}/test-linux-fedora.sh"
 
echo
echo "==> Linux build and Fedora runtime tests passed"
echo "  ${BIN_DIR}/${NAME}-linux-amd64"
echo "  ${BIN_DIR}/${NAME}-linux-arm64"

File: scripts/release/package-archives.sh

#!/usr/bin/env bash
set -euo pipefail
 
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/common.sh"
 
VERSION="${1:?Usage: scripts/release/package-archives.sh VERSION}"
 
mkdir -p "$ARCHIVE_DIR"
 
for platform in $PLATFORMS; do
  source_bin="${BIN_DIR}/${NAME}-${platform}"
  archive_path="${ARCHIVE_DIR}/${NAME}-${VERSION}-${platform}.tar.gz"
 
  if [[ ! -f "$source_bin" ]]; then
    echo "Error: missing binary $source_bin" >&2
    exit 1
  fi
 
  tmpdir="$(mktemp -d)"
  cp "$source_bin" "${tmpdir}/${NAME}"
  chmod +x "${tmpdir}/${NAME}"
  tar -czf "$archive_path" -C "$tmpdir" "$NAME"
 
  if ! tar -tzf "$archive_path" | grep -qx "$NAME"; then
    echo "Error: archive contents invalid for $archive_path (expected internal file: $NAME)" >&2
    rm -rf "$tmpdir"
    exit 1
  fi
 
  rm -rf "$tmpdir"
  echo "    wrote: $archive_path"
done

File: scripts/release/update-formula.sh

#!/usr/bin/env bash
set -euo pipefail
 
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/common.sh"
 
VERSION="${1:?Usage: scripts/release/update-formula.sh VERSION TAG}"
TAG="${2:?Usage: scripts/release/update-formula.sh VERSION TAG}"
 
require_cmd git
require_checksum_tool
ensure_archives_present "$VERSION"
 
sha_macos_arm64="$(compute_sha "${ARCHIVE_DIR}/${NAME}-${VERSION}-macos-arm64.tar.gz")"
sha_macos_amd64="$(compute_sha "${ARCHIVE_DIR}/${NAME}-${VERSION}-macos-amd64.tar.gz")"
sha_linux_arm64="$(compute_sha "${ARCHIVE_DIR}/${NAME}-${VERSION}-linux-arm64.tar.gz")"
sha_linux_amd64="$(compute_sha "${ARCHIVE_DIR}/${NAME}-${VERSION}-linux-amd64.tar.gz")"
 
formula_class="$(class_name "$NAME")"
 
tap_dir="$(mktemp -d)"
trap 'rm -rf "$tap_dir"' EXIT
 
if [[ -n "${GH_TOKEN:-}" ]]; then
  echo "==> Cloning tap ${TAP_REPO} with GH_TOKEN"
  git clone "https://x-access-token:${GH_TOKEN}@github.com/${TAP_REPO}.git" "$tap_dir" --depth 1
else
  require_cmd gh
  echo "==> Cloning tap ${TAP_REPO} with gh"
  gh repo clone "$TAP_REPO" "$tap_dir" -- --depth 1
fi
 
mkdir -p "${tap_dir}/Formula"
 
cat > "${tap_dir}/Formula/${NAME}.rb" <<RUBY
class ${formula_class} < Formula
  desc "${FORMULA_DESC}"
  homepage "https://github.com/${REPO}"
  version "${VERSION}"
  license "MIT"
 
  on_macos do
    on_arm do
      url "https://github.com/${REPO}/releases/download/${TAG}/${NAME}-${VERSION}-macos-arm64.tar.gz"
      sha256 "${sha_macos_arm64}"
    end
    on_intel do
      url "https://github.com/${REPO}/releases/download/${TAG}/${NAME}-${VERSION}-macos-amd64.tar.gz"
      sha256 "${sha_macos_amd64}"
    end
  end
 
  on_linux do
    on_arm do
      url "https://github.com/${REPO}/releases/download/${TAG}/${NAME}-${VERSION}-linux-arm64.tar.gz"
      sha256 "${sha_linux_arm64}"
    end
    on_intel do
      url "https://github.com/${REPO}/releases/download/${TAG}/${NAME}-${VERSION}-linux-amd64.tar.gz"
      sha256 "${sha_linux_amd64}"
    end
  end
 
  def install
    bin.install "${NAME}"
  end
 
  test do
    assert_match version.to_s, shell_output("#{bin}/${NAME} --version")
  end
end
RUBY
 
git -C "$tap_dir" add "Formula/${NAME}.rb"
if git -C "$tap_dir" diff --cached --quiet; then
  echo "==> Formula unchanged; skipping tap commit"
  exit 0
fi
 
if ! git -C "$tap_dir" config --get user.name >/dev/null; then
  git -C "$tap_dir" config user.name "release-bot"
fi
if ! git -C "$tap_dir" config --get user.email >/dev/null; then
  git -C "$tap_dir" config user.email "[email protected]"
fi
 
git -C "$tap_dir" commit -m "${NAME} ${VERSION}"
git -C "$tap_dir" push origin main
 
echo "==> Tap formula updated: Formula/${NAME}.rb"

File: release.sh (repo root)

#!/usr/bin/env bash
set -euo pipefail
 
RELEASE_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${RELEASE_SCRIPT_DIR}/scripts/release/common.sh"
 
FORMULA_ONLY=false
if [[ "${1:-}" == "--formula-only" ]]; then
  FORMULA_ONLY=true
  shift
fi
 
VERSION="${1:?Usage: ./release.sh [--formula-only] VERSION}"
TAG="v${VERSION}"
 
require_cmd git
require_cmd gh
require_cmd perl
require_cmd tar
require_checksum_tool
 
gh auth status >/dev/null 2>&1 || {
  echo "Error: gh is not authenticated. Run: gh auth login" >&2
  exit 1
}
 
if [[ "$FORMULA_ONLY" == false ]]; then
  ensure_clean_repo
 
  current_branch="$(git -C "$REPO_ROOT" branch --show-current)"
  if [[ "$current_branch" != "main" ]]; then
    echo "Error: release must run from main branch. Current branch: $current_branch" >&2
    exit 1
  fi
 
  if git -C "$REPO_ROOT" rev-parse "$TAG" >/dev/null 2>&1; then
    echo "Error: local tag already exists: $TAG" >&2
    echo "Use --formula-only to update the formula from existing release archives." >&2
    exit 1
  fi
 
  if git -C "$REPO_ROOT" ls-remote --tags origin "refs/tags/$TAG" | grep -q "$TAG"; then
    echo "Error: remote tag already exists: $TAG" >&2
    echo "Use --formula-only to update the formula from existing release archives." >&2
    exit 1
  fi
 
  version_file="${REPO_ROOT}/${VERSION_FILE}"
  tmpfile="$(mktemp)"
  trap 'rm -f "$tmpfile"' EXIT
 
  perl -0777 -pe 's/'"$VERSION_REGEX"'/version: "'"$VERSION"'"/' "$version_file" > "$tmpfile"
  if ! grep -q "version: \"${VERSION}\"" "$tmpfile"; then
    echo "Error: failed to update version in ${VERSION_FILE}" >&2
    exit 1
  fi
  mv "$tmpfile" "$version_file"
  trap - EXIT
 
  echo "==> Building binaries"
  "${RELEASE_SCRIPT_DIR}/scripts/release/build-macos.sh"
  "${RELEASE_SCRIPT_DIR}/scripts/release/build-linux.sh"
 
  echo "==> Packaging archives"
  "${RELEASE_SCRIPT_DIR}/scripts/release/package-archives.sh" "$VERSION"
 
  echo "==> Committing and tagging"
  git -C "$REPO_ROOT" add "$VERSION_FILE"
  git -C "$REPO_ROOT" commit -m "Release ${VERSION}"
  git -C "$REPO_ROOT" tag -a "$TAG" -m "Release ${VERSION}"
  git -C "$REPO_ROOT" push origin main "$TAG"
 
  echo "==> Creating GitHub release"
  gh release create "$TAG" "${ARCHIVE_DIR}"/*.tar.gz \
    --repo "$REPO" \
    --title "$TAG" \
    --generate-notes
else
  echo "==> Formula-only mode: downloading existing release archives"
  mkdir -p "$ARCHIVE_DIR"
  rm -f "${ARCHIVE_DIR}"/*.tar.gz
 
  gh release download "$TAG" \
    --repo "$REPO" \
    --dir "$ARCHIVE_DIR" \
    --pattern '*.tar.gz'
fi
 
ensure_archives_present "$VERSION"
 
echo "==> Updating Homebrew formula"
"${RELEASE_SCRIPT_DIR}/scripts/release/update-formula.sh" "$VERSION" "$TAG"
 
echo
echo "Release complete. Install with: brew install ${TAP_REPO#*/}/${NAME}"

File: .github/workflows/release.yml

This reuses the same scripts as local to avoid CI/local drift.

name: Release
 
on:
  push:
    tags:
      - "v*"
 
permissions:
  contents: write
 
jobs:
  build-macos:
    runs-on: macos-14
 
    steps:
      - uses: actions/checkout@v4
 
      - uses: swift-actions/setup-swift@v2
        with:
          swift-version: "6.2"
 
      - name: Build macOS binaries
        run: scripts/release/build-macos.sh
 
      - uses: actions/upload-artifact@v4
        with:
          name: macos-binaries
          path: output/binaries/__NAME__-macos-*
 
  build-linux:
    runs-on: ubuntu-latest
 
    steps:
      - uses: actions/checkout@v4
 
      - name: Build Linux binaries
        run: scripts/release/build-linux.sh
 
      - uses: actions/upload-artifact@v4
        with:
          name: linux-binaries
          path: output/binaries/__NAME__-linux-*
 
  release:
    needs:
      - build-macos
      - build-linux
    runs-on: ubuntu-latest
 
    steps:
      - uses: actions/checkout@v4
 
      - uses: actions/download-artifact@v4
        with:
          path: output/binaries
          merge-multiple: true
 
      - name: Check if release already exists
        id: check_release
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          if gh release view "$GITHUB_REF_NAME" --repo "${{ github.repository }}" >/dev/null 2>&1; then
            echo "exists=true" >> "$GITHUB_OUTPUT"
          else
            echo "exists=false" >> "$GITHUB_OUTPUT"
          fi
 
      - name: Package archives
        if: steps.check_release.outputs.exists == 'false'
        run: |
          VERSION="${GITHUB_REF_NAME#v}"
          scripts/release/package-archives.sh "$VERSION"
 
      - name: Create GitHub Release
        if: steps.check_release.outputs.exists == 'false'
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          gh release create "$GITHUB_REF_NAME" output/archives/*.tar.gz \
            --repo "${{ github.repository }}" \
            --title "$GITHUB_REF_NAME" \
            --generate-notes
 
      - name: Download existing release archives
        if: steps.check_release.outputs.exists == 'true'
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          mkdir -p output/archives
          rm -f output/archives/*.tar.gz
          gh release download "$GITHUB_REF_NAME" \
            --repo "${{ github.repository }}" \
            --dir output/archives \
            --pattern '*.tar.gz'
 
      - name: Update formula in homebrew tap
        env:
          GH_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
        run: |
          VERSION="${GITHUB_REF_NAME#v}"
          TAG="$GITHUB_REF_NAME"
          scripts/release/update-formula.sh "$VERSION" "$TAG"

One-Time GitHub Setup (Manual)

  1. Create tap repo if needed:
gh repo create yourname/homebrew-tap --public
  1. In your CLI repo, add secret:
  • Name: HOMEBREW_TAP_TOKEN
  • Value: a fine-grained PAT (github_pat_...) with:
    • repository access to your tap repo
    • Contents: Read and write
  1. Push the workflow file to main.

Local Release Steps

Dry run without releasing

scripts/release/build-macos.sh
scripts/release/build-linux.sh
scripts/release/package-archives.sh 0.1.0-test

Real release

./release.sh 0.1.0

Recovery mode

./release.sh --formula-only 0.1.0

Install Verification

brew update
brew tap yourname/tap
brew install yourname/tap/__NAME__
__NAME__ --version

Why This Structure Works

  • release.sh in repo root is the one human entrypoint.
  • scripts/release/* are reusable building blocks.
  • CI calls the same scripts as local, so behavior stays aligned.
  • --formula-only gives clean recovery from partial failures.

Pitfalls You Should Assume Will Happen

  1. Archive internal filename mismatch
  • Tarball can be __NAME__-0.1.0-linux-amd64.tar.gz
  • But internal file must be exactly __NAME__
  1. musl dependency failures
  • Some packages compile on glibc but fail on musl static SDK
  • Typical symptom: #error("Unsupported Platform")
  • Patch or replace dependency
  1. Static Swift Linux size
  • 60-80MB binaries are common with static runtime
  1. Formula tests are sandboxed
  • Keep test minimal: __NAME__ --version
  1. Branch/tag safety
  • Always check clean tree and existing tags before release

Minimal Checklist Per Release

  • --version outputs target version
  • macOS arm64/amd64 binaries built
  • Linux arm64/amd64 static binaries built
  • Fedora runtime test passed
  • Archives created with internal name __NAME__
  • GitHub release created
  • Tap formula updated with new SHA256 values
  • Fresh install works via Homebrew on macOS and Linux