From 79ed75c448e356d9969cf4727304f610ecb0029d Mon Sep 17 00:00:00 2001 From: foodandtravel <217430421+foodandtravel@users.noreply.github.com> Date: Thu, 29 Oct 2026 21:44:57 +0100 Subject: [PATCH 1/3] Build the Linux release binaries against glibc 2.17 A plain cargo build links against the runner's glibc. v1.7.6 came off a newer ubuntu-latest, needed GLIBC_2.43 and wouldn't start on Ubuntu 24.04. The Linux rows of release-github, release-npm and release-nuget now build inside quay.io/pypa/manylinux2014_*, the image the PyPI wheels already use, and check-glibc.sh fails the job if the binary needs anything above 2.17. check-linux-glibc.yml builds x86_64 the same way on pull requests that touch packaging/linux/ and runs the binary on centos:7 and debian:12. Closes #272 Co-Authored-By: Claude Opus 5.5 --- .agents/AGENTS.md | 11 +++++ .github/workflows/check-linux-glibc.yml | 55 ++++++++++++++++++++++ .github/workflows/release-github.yml | 20 ++++++-- .github/workflows/release-npm.yml | 13 ++++++ .github/workflows/release-nuget.yml | 13 ++++++ packaging/linux/build-manylinux.sh | 62 +++++++++++++++++++++++++ packaging/linux/check-glibc.sh | 24 ++++++++++ 7 files changed, 195 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/check-linux-glibc.yml create mode 100755 packaging/linux/build-manylinux.sh create mode 100755 packaging/linux/check-glibc.sh diff --git a/.agents/AGENTS.md b/.agents/AGENTS.md index f3a6929..d130290 100644 --- a/.agents/AGENTS.md +++ b/.agents/AGENTS.md @@ -579,6 +579,17 @@ that imports `VCRUNTIME140.dll` fails to load on a machine without the VC++ Redi a Windows exe imports the runtime again. Don't set a `RUSTFLAGS` env var in those workflows: it replaces the config rustflags instead of adding to them. +The Linux binaries have a glibc 2.17 floor, the same as the PyPI wheels. A plain `cargo build` +links against the runner's glibc, and v1.7.6 built on a newer `ubuntu-latest` needed GLIBC_2.43, +so it would not start on Ubuntu 24.04 (#272). `release-github`, `release-npm` and `release-nuget` +build their Linux rows with `packaging/linux/build-manylinux.sh`, which runs `cargo build` inside +`quay.io/pypa/manylinux2014_*` (the image release-pypi's maturin already uses), then run +`packaging/linux/check-glibc.sh` to fail the job if the binary needs anything above 2.17. The +runner image no longer matters for Linux. `check-linux-glibc.yml` builds x86_64 the same way on +pull requests that touch `packaging/linux/` and runs the result on `centos:7` and `debian:12`. +Don't move the Linux builds back to a bare `cargo build` or a job-level `container:` (the +actions' Node needs glibc 2.28, which CentOS 7 lacks). + Every release asset now carries a `.sha256` sidecar, not just the macOS Homebrew tarball. Scoop, winget, and the AUR PKGBUILD each need one, and they consume the raw binaries rather than the tarball. `release-github` also builds a `.deb` and `.rpm` per Linux target with nfpm diff --git a/.github/workflows/check-linux-glibc.yml b/.github/workflows/check-linux-glibc.yml new file mode 100644 index 0000000..bf0fe4a --- /dev/null +++ b/.github/workflows/check-linux-glibc.yml @@ -0,0 +1,55 @@ +name: Linux glibc floor + +# The release workflows build the Linux binaries with packaging/linux/build-manylinux.sh so they +# run on glibc 2.17 and later. A pull request that touches that script, or this file, builds +# x86_64 the same way, checks the glibc floor, and starts the binary on the oldest and a current +# glibc, so a broken build script shows up before a release does. +on: + pull_request: + paths: + - packaging/linux/** + - .github/workflows/check-linux-glibc.yml + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +env: + CARGO_TERM_COLOR: always + TARGET: x86_64-unknown-linux-gnu + +jobs: + build: + name: Build and check (x86_64) + runs-on: ubuntu-24.04 + timeout-minutes: 60 + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Setup Rust cache + uses: Swatinem/rust-cache@v2 + with: + key: manylinux-${{ env.TARGET }} + + - name: Build Linux binary (glibc 2.17) + shell: bash + run: ./packaging/linux/build-manylinux.sh "${TARGET}" + + - name: Check Linux binary against the glibc 2.17 floor + shell: bash + run: ./packaging/linux/check-glibc.sh "target/${TARGET}/release/sigit" 2.17 + + # centos:7 is glibc 2.17, the floor itself; debian:12 is a current distro. + - name: Run the binary on old and current glibc + shell: bash + run: | + for image in centos:7 debian:12; do + echo "== ${image}" + docker run --rm -v "${PWD}/target/${TARGET}/release/sigit:/usr/local/bin/sigit:ro" \ + "${image}" sh -c 'ldd --version | head -n 1 && sigit --version' + done diff --git a/.github/workflows/release-github.yml b/.github/workflows/release-github.yml index 95dff53..ee74fb4 100644 --- a/.github/workflows/release-github.yml +++ b/.github/workflows/release-github.yml @@ -36,9 +36,10 @@ jobs: matrix: include: - name: linux-amd64 - # Pinned, not ubuntu-latest: the binary links against the build host's - # glibc, and v1.7.6 built on a newer image needed GLIBC_2.43, which - # Ubuntu 24.04 (2.39, the assurance runner image's base) doesn't have. + # Pinned since v1.7.6, which built on a newer ubuntu-latest and needed + # GLIBC_2.43. The Linux binaries are now built in manylinux2014 (glibc + # 2.17, see packaging/linux/build-manylinux.sh), so the runner's own + # glibc no longer reaches the binary. runner: ubuntu-24.04 target: x86_64-unknown-linux-gnu - name: linux-arm64 @@ -96,9 +97,22 @@ jobs: key: github-${{ matrix.target }} - name: Build binary + if: ${{ !contains(matrix.target, 'linux') }} shell: bash run: cargo build --locked --release -p sigit --target ${{ matrix.target }} + # Built in manylinux2014 so the binary runs on glibc 2.17 and later, whatever the runner + # has. See packaging/linux/build-manylinux.sh. + - name: Build Linux binary (glibc 2.17) + if: contains(matrix.target, 'linux') + shell: bash + run: ./packaging/linux/build-manylinux.sh ${{ matrix.target }} + + - name: Check Linux binary against the glibc 2.17 floor + if: contains(matrix.target, 'linux') + shell: bash + run: ./packaging/linux/check-glibc.sh target/${{ matrix.target }}/release/sigit 2.17 + - name: Check Windows binary has no VC++ runtime dependency if: contains(matrix.target, 'windows') shell: pwsh diff --git a/.github/workflows/release-npm.yml b/.github/workflows/release-npm.yml index 7f87430..987cb2e 100644 --- a/.github/workflows/release-npm.yml +++ b/.github/workflows/release-npm.yml @@ -100,9 +100,22 @@ jobs: key: npm-${{ matrix.build.TARGET }} - name: Build binary + if: ${{ !contains(matrix.build.TARGET, 'linux') }} shell: bash run: cargo build --locked --release -p sigit --target ${{ matrix.build.TARGET }} + # Built in manylinux2014 so the binary runs on glibc 2.17 and later, whatever the runner + # has. See packaging/linux/build-manylinux.sh. + - name: Build Linux binary (glibc 2.17) + if: contains(matrix.build.TARGET, 'linux') + shell: bash + run: ./packaging/linux/build-manylinux.sh ${{ matrix.build.TARGET }} + + - name: Check Linux binary against the glibc 2.17 floor + if: contains(matrix.build.TARGET, 'linux') + shell: bash + run: ./packaging/linux/check-glibc.sh target/${{ matrix.build.TARGET }}/release/sigit 2.17 + - name: Install Node uses: actions/setup-node@v6 with: diff --git a/.github/workflows/release-nuget.yml b/.github/workflows/release-nuget.yml index 55f2836..98d2f34 100644 --- a/.github/workflows/release-nuget.yml +++ b/.github/workflows/release-nuget.yml @@ -89,9 +89,22 @@ jobs: key: nuget-${{ matrix.build.TARGET }} - name: Build binary + if: ${{ !contains(matrix.build.TARGET, 'linux') }} shell: bash run: cargo build --locked --release -p sigit --target ${{ matrix.build.TARGET }} + # Built in manylinux2014 so the binary runs on glibc 2.17 and later, whatever the runner + # has. See packaging/linux/build-manylinux.sh. + - name: Build Linux binary (glibc 2.17) + if: contains(matrix.build.TARGET, 'linux') + shell: bash + run: ./packaging/linux/build-manylinux.sh ${{ matrix.build.TARGET }} + + - name: Check Linux binary against the glibc 2.17 floor + if: contains(matrix.build.TARGET, 'linux') + shell: bash + run: ./packaging/linux/check-glibc.sh target/${{ matrix.build.TARGET }}/release/sigit 2.17 + - name: Prepare artifact shell: bash run: | diff --git a/packaging/linux/build-manylinux.sh b/packaging/linux/build-manylinux.sh new file mode 100755 index 0000000..97793fe --- /dev/null +++ b/packaging/linux/build-manylinux.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# Builds the release `sigit` for a Linux target inside the manylinux2014 image, so the binary +# needs nothing newer than glibc 2.17. +# +# packaging/linux/build-manylinux.sh x86_64-unknown-linux-gnu +# +# A plain `cargo build` links against the build host's glibc. v1.7.6 was built on a newer +# ubuntu-latest and needed GLIBC_2.43, so it would not start on Ubuntu 24.04 (2.39), the base of +# the Code Assurance runner image (#272). 2.17 is the same floor as the PyPI wheels, which +# release-pypi already builds in quay.io/pypa/manylinux2014_* through maturin, so this image is +# known to handle aws-lc-sys, onig_sys and esaxx-rs. +# +# This is a script and not a job-level `container:` because actions/checkout and the other +# JavaScript actions run Node inside the job container, and that Node needs glibc 2.28, which +# CentOS 7 does not have. cargo-zigbuild (`--target x86_64-unknown-linux-gnu.2.17`) would avoid +# Docker, but it swaps the C toolchain for every native dependency, while this image is the one +# PyPI has been building with since v1.7.4. +# +# The output lands at target//release/sigit, the same place `cargo build --target` puts +# it, so the packaging steps after it don't change. The host's ~/.cargo/{registry,git} are +# mounted so Swatinem/rust-cache still restores and saves them. Check the result with +# packaging/linux/check-glibc.sh. +set -euo pipefail + +target=${1:?usage: build-manylinux.sh } +arch=$(uname -m) +case "${target}" in + "${arch}"-unknown-linux-gnu) ;; + *) echo "build-manylinux.sh: ${target} does not match this ${arch} host" >&2; exit 1 ;; +esac + +repo=$(cd "$(dirname "$0")/../.." && pwd) +cargo_home=${CARGO_HOME:-$HOME/.cargo} +toolchain=$(sed -n 's/^channel = "\(.*\)"/\1/p' "${repo}/rust-toolchain.toml" | head -n 1) +[ -n "${toolchain}" ] || { echo "build-manylinux.sh: no channel in rust-toolchain.toml" >&2; exit 1; } +image=quay.io/pypa/manylinux2014_${arch} + +mkdir -p "${cargo_home}/registry" "${cargo_home}/git" + +# The container runs as root. Files it creates under target/ and the cargo cache are handed +# back to the runner user, which the later steps and rust-cache's post step need to write to. +docker run --rm \ + -v "${repo}:/io" \ + -v "${cargo_home}/registry:/cargo/registry" \ + -v "${cargo_home}/git:/cargo/git" \ + -w /io \ + -e CARGO_HOME=/cargo \ + -e RUSTUP_HOME=/rustup \ + -e CARGO_TERM_COLOR="${CARGO_TERM_COLOR:-auto}" \ + -e TOOLCHAIN="${toolchain}" \ + -e TARGET="${target}" \ + -e OWNER="$(id -u):$(id -g)" \ + "${image}" \ + bash -euo pipefail -c ' + trap "chown -R ${OWNER} /io/target /cargo/registry /cargo/git 2>/dev/null || true" EXIT + # Same before-script-linux as release-pypi: aws-lc-sys needs a full Perl. + yum install -y -q perl-core + curl -sSf https://sh.rustup.rs | sh -s -- -y -q --profile minimal --default-toolchain "${TOOLCHAIN}" + export PATH=/cargo/bin:$PATH + ldd --version | head -n 1 + cargo build --locked --release -p sigit --target "${TARGET}" + ' diff --git a/packaging/linux/check-glibc.sh b/packaging/linux/check-glibc.sh new file mode 100755 index 0000000..9feb9b8 --- /dev/null +++ b/packaging/linux/check-glibc.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# Fails if a Linux binary needs a newer glibc than the release floor. +# +# packaging/linux/check-glibc.sh target/x86_64-unknown-linux-gnu/release/sigit [2.17] +# +# The Linux releases are built in manylinux2014 (packaging/linux/build-manylinux.sh) so they run +# on glibc 2.17 and later. If a build slips back onto the runner's own toolchain, the binary still +# builds and passes tests there, then fails to start on an older distro with "GLIBC_2.xx not +# found". This check catches it before release, the way check-static-crt.ps1 does for Windows. +set -euo pipefail + +binary=${1:?usage: check-glibc.sh [max-glibc]} +max=${2:-2.17} + +top=$(objdump -T "${binary}" | grep -o 'GLIBC_[0-9.]*' | sed 's/GLIBC_//' | sort -uV | tail -n 1) +if [ -z "${top}" ]; then + echo "::error::objdump found no GLIBC_ symbols in ${binary}" + exit 1 +fi +if [ "$(printf '%s\n%s\n' "${top}" "${max}" | sort -V | tail -n 1)" != "${max}" ]; then + echo "::error::${binary} needs GLIBC_${top}, above the ${max} floor" + exit 1 +fi +echo "${binary} needs at most GLIBC_${top} (floor ${max})." From 2a4c353fdcbaa78adc6dea0339cb525a7bdab201 Mon Sep 17 00:00:00 2001 From: foodandtravel <217430421+foodandtravel@users.noreply.github.com> Date: Thu, 29 Oct 2026 21:54:27 +0100 Subject: [PATCH 2/3] Read ldd's version without tripping pipefail head closed the pipe while ldd was still writing, and under pipefail the SIGPIPE (141) failed the build step. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/check-linux-glibc.yml | 2 +- packaging/linux/build-manylinux.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check-linux-glibc.yml b/.github/workflows/check-linux-glibc.yml index bf0fe4a..71d6b14 100644 --- a/.github/workflows/check-linux-glibc.yml +++ b/.github/workflows/check-linux-glibc.yml @@ -51,5 +51,5 @@ jobs: for image in centos:7 debian:12; do echo "== ${image}" docker run --rm -v "${PWD}/target/${TARGET}/release/sigit:/usr/local/bin/sigit:ro" \ - "${image}" sh -c 'ldd --version | head -n 1 && sigit --version' + "${image}" sh -c 'ldd --version | sed -n 1p && sigit --version' done diff --git a/packaging/linux/build-manylinux.sh b/packaging/linux/build-manylinux.sh index 97793fe..06be434 100755 --- a/packaging/linux/build-manylinux.sh +++ b/packaging/linux/build-manylinux.sh @@ -57,6 +57,6 @@ docker run --rm \ yum install -y -q perl-core curl -sSf https://sh.rustup.rs | sh -s -- -y -q --profile minimal --default-toolchain "${TOOLCHAIN}" export PATH=/cargo/bin:$PATH - ldd --version | head -n 1 + ldd --version | sed -n 1p cargo build --locked --release -p sigit --target "${TARGET}" ' From 7fd8c2f5947dce64e19aa3f31a2d42b72cd94aa4 Mon Sep 17 00:00:00 2001 From: foodandtravel <217430421+foodandtravel@users.noreply.github.com> Date: Thu, 29 Oct 2026 22:09:45 +0100 Subject: [PATCH 3/3] Name the glibc check "CI Linux glibc floor" That puts it next to CI in the Actions sidebar, matching the "Release " names on the release workflows. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/check-linux-glibc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-linux-glibc.yml b/.github/workflows/check-linux-glibc.yml index 71d6b14..ef135cf 100644 --- a/.github/workflows/check-linux-glibc.yml +++ b/.github/workflows/check-linux-glibc.yml @@ -1,4 +1,4 @@ -name: Linux glibc floor +name: CI Linux glibc floor # The release workflows build the Linux binaries with packaging/linux/build-manylinux.sh so they # run on glibc 2.17 and later. A pull request that touches that script, or this file, builds