Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 68 additions & 6 deletions .github/workflows/release-binaries.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ on:
permissions:
contents: write

env:
# Highest glibc a *-gnu binary may require. Linux builds are cross-linked
# against this floor so the artifacts do not inherit the runner's glibc.
GLIBC_FLOOR: "2.17"
ZIG_VERSION: "0.13.0"
CARGO_ZIGBUILD_VERSION: "0.23.0"

jobs:
native-binaries:
name: Build and Upload Native Binaries
Expand All @@ -24,9 +31,12 @@ jobs:
- os: ubuntu-latest
target: x86_64-unknown-linux-musl
binary_name: corgea
- os: ubuntu-24.04-arm
- os: ubuntu-latest
target: aarch64-unknown-linux-gnu
binary_name: corgea
- os: ubuntu-latest
target: aarch64-unknown-linux-musl
binary_name: corgea
- os: macos-latest
target: x86_64-apple-darwin
binary_name: corgea
Expand All @@ -44,16 +54,68 @@ jobs:
with:
ref: ${{ inputs.tag || github.ref }}

- name: Install musl tools
if: matrix.target == 'x86_64-unknown-linux-musl'
run: sudo apt-get install -y musl-tools

- name: Install Rust Target
run: rustup target add ${{ matrix.target }}

- name: Build Binary
- name: Install Zig
if: runner.os == 'Linux'
uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2.2.1
with:
version: ${{ env.ZIG_VERSION }}

- name: Install cargo-zigbuild
if: runner.os == 'Linux'
uses: taiki-e/install-action@18b1216eba7f8039b0f8d131d5473787f0edce68 # v2.85.3
with:
tool: cargo-zigbuild@${{ env.CARGO_ZIGBUILD_VERSION }}

- name: Build Binary (Linux)
if: runner.os == 'Linux'
shell: bash
run: |
build_target="${{ matrix.target }}"
if [[ "$build_target" == *-gnu ]]; then
build_target="${build_target}.${GLIBC_FLOOR}"
fi
cargo zigbuild --release --target "$build_target"

- name: Build Binary (macOS and Windows)
if: runner.os != 'Linux'
run: cargo build --release --target ${{ matrix.target }}

- name: Verify Linux Runtime Compatibility
if: runner.os == 'Linux'
shell: bash
run: |
scripts/check-linux-binary.sh \
"target/${{ matrix.target }}/release/${{ matrix.binary_name }}" \
"${{ matrix.target }}"

# The aarch64 artifacts are cross-built, so nothing else in CI would ever
# execute them. Run them under qemu to catch a binary that passes the
# header checks but cannot actually start.
- name: Smoke Test Linux Binary
if: runner.os == 'Linux'
shell: bash
run: |
binary="target/${{ matrix.target }}/release/${{ matrix.binary_name }}"

case "${{ matrix.target }}" in
aarch64-*-gnu)
sudo apt-get update -qq
sudo apt-get install -y -qq qemu-user-static libc6-arm64-cross
qemu-aarch64-static -L /usr/aarch64-linux-gnu "$binary" --version
;;
aarch64-*-musl)
sudo apt-get update -qq
sudo apt-get install -y -qq qemu-user-static
qemu-aarch64-static "$binary" --version
;;
*)
"$binary" --version
;;
esac

- name: Archive Binary (Unix)
if: runner.os != 'Windows'
shell: bash
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*.zip
*.profraw
node_modules/
/vendor/
/packages/*/vendor/

.claude/
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "corgea"
version = "1.9.2"
version = "1.9.3"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ pip install corgea-cli
### Manual Installation
You can get the latest binaries for your OS from https://github.com/Corgea/cli/releases.

On Linux, both a `-gnu` and a `-musl` binary are published for x86_64 and
aarch64. The `-gnu` builds run on glibc 2.17 and newer; the `-musl` builds are
fully static and have no libc requirement at all, which makes them the safer
pick for minimal containers and CI runners.

### Setup
Once the binary is installed, login with your token from the Corgea app.
```
Expand Down
25 changes: 23 additions & 2 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,29 @@ Pushing an annotated `vX.Y.Z` tag to `main` fans out to three GitHub Actions wor
## What gets built

- **PyPI wheels** (`release.yml`): Linux (x86_64, x86), Windows (x64, x86), macOS (x86_64, aarch64), plus an sdist. Asserts `manylinux2014` tags for broad Linux compatibility.
- **Native binaries** (`release-binaries.yml`): `x86_64-unknown-linux-gnu`, `x86_64-unknown-linux-musl`, `aarch64-unknown-linux-gnu`, `x86_64-apple-darwin`, `aarch64-apple-darwin`, `x86_64-pc-windows-msvc`.
- **npm binary bundling** (`npm-publish.yml` → `scripts/npm/bundle-binaries.js`): downloads the GitHub Release zips and lays them out as `vendor/<target>/corgea/corgea`. At runtime `bin/corgea.js` selects the binary for the host OS/arch. npm ships 5 of the 6 targets (Linux x64 uses the musl build).
- **Native binaries** (`release-binaries.yml`): `x86_64-unknown-linux-gnu`, `x86_64-unknown-linux-musl`, `aarch64-unknown-linux-gnu`, `aarch64-unknown-linux-musl`, `x86_64-apple-darwin`, `aarch64-apple-darwin`, `x86_64-pc-windows-msvc`.
- **npm binary bundling** (`npm-publish.yml` → `scripts/npm/bundle-binaries.js`): downloads the GitHub Release zips and lays them out as `vendor/<target>/corgea/corgea`. At runtime `bin/corgea.js` selects the binary for the host OS/arch. npm ships 5 of the 7 targets (both Linux arches use the musl build).

### Linux glibc floor

Building a `*-gnu` target natively stamps the *runner's* glibc into the binary's
ELF version-need table, and the loader then refuses to start it on any host with
an older glibc. Every release from `v1.7.2` through `v1.9.2` shipped Linux gnu
binaries that required `GLIBC_2.39`, so they failed on Ubuntu 22.04 (glibc 2.35)
and older with `version 'GLIBC_2.38' not found`.

All four Linux targets are therefore cross-built on `ubuntu-latest` with
[`cargo-zigbuild`](https://github.com/rust-cross/cargo-zigbuild), pinning the gnu
targets to the `GLIBC_FLOOR` declared in `release-binaries.yml` (currently
`2.17`, i.e. RHEL 7 / Ubuntu 14.04 and newer). `scripts/check-linux-binary.sh`
then re-reads each artifact and fails the build if a gnu binary exceeds the
floor, or if a musl binary is not fully static. Run it locally with:

```bash
scripts/check-linux-binary.sh target/aarch64-unknown-linux-gnu/release/corgea aarch64-unknown-linux-gnu
```

Raising `GLIBC_FLOOR` drops support for older distros — change it deliberately.

## Release notes

Expand Down
2 changes: 1 addition & 1 deletion bin/corgea.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ function resolveTargetTriple() {
case "linux":
case "android":
if (process.arch === "x64") return "x86_64-unknown-linux-musl";
if (process.arch === "arm64") return "aarch64-unknown-linux-gnu";
if (process.arch === "arm64") return "aarch64-unknown-linux-musl";
return null;
case "darwin":
if (process.arch === "x64") return "x86_64-apple-darwin";
Expand Down
32 changes: 23 additions & 9 deletions build_release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,21 @@

BUILD_OS="$(uname)"

# Keep in sync with GLIBC_FLOOR in .github/workflows/release-binaries.yml. Linux
# binaries are cross-linked against this floor so they do not pick up whatever
# glibc the build host happens to ship.
GLIBC_FLOOR="${GLIBC_FLOOR:-2.17}"

if [ $BUILD_OS == "Darwin" ]; then
TARGETS=("aarch64-apple-darwin" "x86_64-apple-darwin")
elif [ $BUILD_OS == "Linux" ]; then
TARGETS=("aarch64-unknown-linux-gnu" "x86_64-unknown-linux-gnu" "x86_64-pc-windows-gnu")
TARGETS=("aarch64-unknown-linux-gnu" "aarch64-unknown-linux-musl" "x86_64-unknown-linux-gnu" "x86_64-unknown-linux-musl")

if ! command -v cargo-zigbuild >/dev/null 2>&1; then
echo "cargo-zigbuild is required to build the Linux targets."
echo "Install it with: cargo install cargo-zigbuild && pip install ziglang"
exit 1
fi
else
echo "Are you building from a supported OS? (Darwin/Linux)"
exit 1
Expand All @@ -14,9 +25,16 @@ fi
for target in "${TARGETS[@]}"
do
if [ $BUILD_OS == "Darwin" ]; then
cargo build -r --target $target
cargo build -r --target $target || exit 1
else
cross build -r --target $target
build_target="$target"
if [[ "$target" == *-gnu ]]; then
build_target="${target}.${GLIBC_FLOOR}"
fi

cargo zigbuild -r --target "$build_target" || exit 1
GLIBC_FLOOR="$GLIBC_FLOOR" \
./scripts/check-linux-binary.sh "target/$target/release/corgea" "$target" || exit 1
fi

zip_file_name="corgea-$target.zip"
Expand All @@ -26,9 +44,5 @@ do
rm $zip_file_name
fi

if [ $target == "x86_64-pc-windows-gnu" ]; then
zip -j $zip_file_name "target/$target/release/corgea.exe"
else
zip -j $zip_file_name "target/$target/release/corgea"
fi
done
zip -j $zip_file_name "target/$target/release/corgea"
done
84 changes: 84 additions & 0 deletions scripts/check-linux-binary.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/usr/bin/env bash
#
# Gate on the runtime requirements of a released Linux binary.
#
# Linking against the build runner's glibc stamps that version into the ELF
# version-need table, and the loader refuses the binary on any host with an
# older glibc. Runner images move forward on their own schedule, so without
# this check the supported floor silently follows them.
#
# scripts/check-linux-binary.sh <binary> <target-triple>
#
# GLIBC_FLOOR (default 2.17) is the highest glibc version a *-gnu binary may
# require. *-musl binaries must be fully static instead.

set -euo pipefail

binary=${1:-}
triple=${2:-}
floor=${GLIBC_FLOOR:-2.17}

if [ -z "$binary" ] || [ -z "$triple" ]; then
echo "usage: $0 <binary> <target-triple>" >&2
exit 2
fi

if [ ! -f "$binary" ]; then
echo "FAIL: $binary does not exist" >&2
exit 1
fi

case "$triple" in
aarch64-*) want_machine="AArch64" ;;
x86_64-*) want_machine="X86-64" ;;
*)
echo "FAIL: unsupported target triple: $triple" >&2
exit 1
;;
esac

machine=$(readelf -h "$binary" | sed -n 's/^ *Machine: *//p')
case "$machine" in
*"$want_machine"*) ;;
*)
echo "FAIL: $binary is '$machine', expected $want_machine for $triple" >&2
exit 1
;;
esac

case "$triple" in
*-musl)
if readelf -lW "$binary" | grep -q INTERP; then
echo "FAIL: $binary is dynamically linked; musl builds must be static" >&2
exit 1
fi
echo "OK: $binary ($triple) is statically linked"
;;

*-gnu)
# A binary with no versioned glibc references is fine, but grep exits 1 on
# no match, so run it outside the pipeline that feeds the assignment.
# Requiring a digit keeps unversioned tags such as GLIBC_PRIVATE out.
version_info=$(readelf -V "$binary")
needed=$(grep -oE 'GLIBC_[0-9]+(\.[0-9]+)*' <<<"$version_info" | sed 's/^GLIBC_//' | sort -uV || true)

if [ -z "$needed" ]; then
echo "OK: $binary ($triple) requires no versioned glibc symbols"
exit 0
fi

highest=$(echo "$needed" | tail -1)
if [ "$(printf '%s\n%s\n' "$highest" "$floor" | sort -V | tail -1)" != "$floor" ]; then
echo "FAIL: $binary requires glibc $highest, above the $floor floor" >&2
echo " versions required: $(echo "$needed" | tr '\n' ' ')" >&2
exit 1
fi

echo "OK: $binary ($triple) requires at most glibc $highest (floor $floor)"
;;

*)
echo "FAIL: unsupported libc in target triple: $triple" >&2
exit 1
;;
esac
2 changes: 1 addition & 1 deletion scripts/npm/bundle-binaries.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const { unzipSync } = require("fflate");

const TARGETS = [
{ triple: "x86_64-unknown-linux-musl", binary: "corgea" },
{ triple: "aarch64-unknown-linux-gnu", binary: "corgea" },
{ triple: "aarch64-unknown-linux-musl", binary: "corgea" },
{ triple: "x86_64-apple-darwin", binary: "corgea" },
{ triple: "aarch64-apple-darwin", binary: "corgea" },
{ triple: "x86_64-pc-windows-msvc", binary: "corgea.exe" },
Expand Down
Loading