@sigitcode-old / sigit / commits / 79ed75c
Fork 1

I'm siGit, running on siGit Code & Deploy platform, browsing siGit Code coding agent repo.

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 <noreply@anthropic.com>

foodandtravel committed Oct 29, 2026 at 21:44 UTC 79ed75c448e356d9969cf4727304f610ecb0029d
7 files changed +195 -3
.agents/AGENTS.md
+11
@@ -579,6 +579,17 @@ that imports `VCRUNTIME140.dll` fails to load on a machine without the VC++ Redi
579 a Windows exe imports the runtime again. Don't set a `RUSTFLAGS` env var in those workflows: it
580 replaces the config rustflags instead of adding to them.
581
582 +The Linux binaries have a glibc 2.17 floor, the same as the PyPI wheels. A plain `cargo build`
583 +links against the runner's glibc, and v1.7.6 built on a newer `ubuntu-latest` needed GLIBC_2.43,
584 +so it would not start on Ubuntu 24.04 (#272). `release-github`, `release-npm` and `release-nuget`
585 +build their Linux rows with `packaging/linux/build-manylinux.sh`, which runs `cargo build` inside
586 +`quay.io/pypa/manylinux2014_*` (the image release-pypi's maturin already uses), then run
587 +`packaging/linux/check-glibc.sh` to fail the job if the binary needs anything above 2.17. The
588 +runner image no longer matters for Linux. `check-linux-glibc.yml` builds x86_64 the same way on
589 +pull requests that touch `packaging/linux/` and runs the result on `centos:7` and `debian:12`.
590 +Don't move the Linux builds back to a bare `cargo build` or a job-level `container:` (the
591 +actions' Node needs glibc 2.28, which CentOS 7 lacks).
592 +
593 Every release asset now carries a `.sha256` sidecar, not just the macOS Homebrew tarball. Scoop,
594 winget, and the AUR PKGBUILD each need one, and they consume the raw binaries rather than the
595 tarball. `release-github` also builds a `.deb` and `.rpm` per Linux target with nfpm
.github/workflows/check-linux-glibc.yml new
+55
@@ -0,0 +1,55 @@
1 +name: Linux glibc floor
2 +
3 +# The release workflows build the Linux binaries with packaging/linux/build-manylinux.sh so they
4 +# run on glibc 2.17 and later. A pull request that touches that script, or this file, builds
5 +# x86_64 the same way, checks the glibc floor, and starts the binary on the oldest and a current
6 +# glibc, so a broken build script shows up before a release does.
7 +on:
8 + pull_request:
9 + paths:
10 + - packaging/linux/**
11 + - .github/workflows/check-linux-glibc.yml
12 +
13 +concurrency:
14 + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
15 + cancel-in-progress: true
16 +
17 +permissions:
18 + contents: read
19 +
20 +env:
21 + CARGO_TERM_COLOR: always
22 + TARGET: x86_64-unknown-linux-gnu
23 +
24 +jobs:
25 + build:
26 + name: Build and check (x86_64)
27 + runs-on: ubuntu-24.04
28 + timeout-minutes: 60
29 +
30 + steps:
31 + - name: Checkout
32 + uses: actions/checkout@v6
33 +
34 + - name: Setup Rust cache
35 + uses: Swatinem/rust-cache@v2
36 + with:
37 + key: manylinux-${{ env.TARGET }}
38 +
39 + - name: Build Linux binary (glibc 2.17)
40 + shell: bash
41 + run: ./packaging/linux/build-manylinux.sh "${TARGET}"
42 +
43 + - name: Check Linux binary against the glibc 2.17 floor
44 + shell: bash
45 + run: ./packaging/linux/check-glibc.sh "target/${TARGET}/release/sigit" 2.17
46 +
47 + # centos:7 is glibc 2.17, the floor itself; debian:12 is a current distro.
48 + - name: Run the binary on old and current glibc
49 + shell: bash
50 + run: |
51 + for image in centos:7 debian:12; do
52 + echo "== ${image}"
53 + docker run --rm -v "${PWD}/target/${TARGET}/release/sigit:/usr/local/bin/sigit:ro" \
54 + "${image}" sh -c 'ldd --version | head -n 1 && sigit --version'
55 + done
.github/workflows/release-github.yml
+17 -3
@@ -36,9 +36,10 @@ jobs:
36 matrix:
37 include:
38 - name: linux-amd64
39 - # Pinned, not ubuntu-latest: the binary links against the build host's
40 - # glibc, and v1.7.6 built on a newer image needed GLIBC_2.43, which
41 - # Ubuntu 24.04 (2.39, the assurance runner image's base) doesn't have.
39 + # Pinned since v1.7.6, which built on a newer ubuntu-latest and needed
40 + # GLIBC_2.43. The Linux binaries are now built in manylinux2014 (glibc
41 + # 2.17, see packaging/linux/build-manylinux.sh), so the runner's own
42 + # glibc no longer reaches the binary.
43 runner: ubuntu-24.04
44 target: x86_64-unknown-linux-gnu
45 - name: linux-arm64
@@ -96,9 +97,22 @@ jobs:
97 key: github-${{ matrix.target }}
98
99 - name: Build binary
100 + if: ${{ !contains(matrix.target, 'linux') }}
101 shell: bash
102 run: cargo build --locked --release -p sigit --target ${{ matrix.target }}
103
104 + # Built in manylinux2014 so the binary runs on glibc 2.17 and later, whatever the runner
105 + # has. See packaging/linux/build-manylinux.sh.
106 + - name: Build Linux binary (glibc 2.17)
107 + if: contains(matrix.target, 'linux')
108 + shell: bash
109 + run: ./packaging/linux/build-manylinux.sh ${{ matrix.target }}
110 +
111 + - name: Check Linux binary against the glibc 2.17 floor
112 + if: contains(matrix.target, 'linux')
113 + shell: bash
114 + run: ./packaging/linux/check-glibc.sh target/${{ matrix.target }}/release/sigit 2.17
115 +
116 - name: Check Windows binary has no VC++ runtime dependency
117 if: contains(matrix.target, 'windows')
118 shell: pwsh
.github/workflows/release-npm.yml
+13
@@ -100,9 +100,22 @@ jobs:
100 key: npm-${{ matrix.build.TARGET }}
101
102 - name: Build binary
103 + if: ${{ !contains(matrix.build.TARGET, 'linux') }}
104 shell: bash
105 run: cargo build --locked --release -p sigit --target ${{ matrix.build.TARGET }}
106
107 + # Built in manylinux2014 so the binary runs on glibc 2.17 and later, whatever the runner
108 + # has. See packaging/linux/build-manylinux.sh.
109 + - name: Build Linux binary (glibc 2.17)
110 + if: contains(matrix.build.TARGET, 'linux')
111 + shell: bash
112 + run: ./packaging/linux/build-manylinux.sh ${{ matrix.build.TARGET }}
113 +
114 + - name: Check Linux binary against the glibc 2.17 floor
115 + if: contains(matrix.build.TARGET, 'linux')
116 + shell: bash
117 + run: ./packaging/linux/check-glibc.sh target/${{ matrix.build.TARGET }}/release/sigit 2.17
118 +
119 - name: Install Node
120 uses: actions/setup-node@v6
121 with:
.github/workflows/release-nuget.yml
+13
@@ -89,9 +89,22 @@ jobs:
89 key: nuget-${{ matrix.build.TARGET }}
90
91 - name: Build binary
92 + if: ${{ !contains(matrix.build.TARGET, 'linux') }}
93 shell: bash
94 run: cargo build --locked --release -p sigit --target ${{ matrix.build.TARGET }}
95
96 + # Built in manylinux2014 so the binary runs on glibc 2.17 and later, whatever the runner
97 + # has. See packaging/linux/build-manylinux.sh.
98 + - name: Build Linux binary (glibc 2.17)
99 + if: contains(matrix.build.TARGET, 'linux')
100 + shell: bash
101 + run: ./packaging/linux/build-manylinux.sh ${{ matrix.build.TARGET }}
102 +
103 + - name: Check Linux binary against the glibc 2.17 floor
104 + if: contains(matrix.build.TARGET, 'linux')
105 + shell: bash
106 + run: ./packaging/linux/check-glibc.sh target/${{ matrix.build.TARGET }}/release/sigit 2.17
107 +
108 - name: Prepare artifact
109 shell: bash
110 run: |
packaging/linux/build-manylinux.sh new
+62
@@ -0,0 +1,62 @@
1 +#!/usr/bin/env bash
2 +# Builds the release `sigit` for a Linux target inside the manylinux2014 image, so the binary
3 +# needs nothing newer than glibc 2.17.
4 +#
5 +# packaging/linux/build-manylinux.sh x86_64-unknown-linux-gnu
6 +#
7 +# A plain `cargo build` links against the build host's glibc. v1.7.6 was built on a newer
8 +# ubuntu-latest and needed GLIBC_2.43, so it would not start on Ubuntu 24.04 (2.39), the base of
9 +# the Code Assurance runner image (#272). 2.17 is the same floor as the PyPI wheels, which
10 +# release-pypi already builds in quay.io/pypa/manylinux2014_* through maturin, so this image is
11 +# known to handle aws-lc-sys, onig_sys and esaxx-rs.
12 +#
13 +# This is a script and not a job-level `container:` because actions/checkout and the other
14 +# JavaScript actions run Node inside the job container, and that Node needs glibc 2.28, which
15 +# CentOS 7 does not have. cargo-zigbuild (`--target x86_64-unknown-linux-gnu.2.17`) would avoid
16 +# Docker, but it swaps the C toolchain for every native dependency, while this image is the one
17 +# PyPI has been building with since v1.7.4.
18 +#
19 +# The output lands at target/<target>/release/sigit, the same place `cargo build --target` puts
20 +# it, so the packaging steps after it don't change. The host's ~/.cargo/{registry,git} are
21 +# mounted so Swatinem/rust-cache still restores and saves them. Check the result with
22 +# packaging/linux/check-glibc.sh.
23 +set -euo pipefail
24 +
25 +target=${1:?usage: build-manylinux.sh <rust-target>}
26 +arch=$(uname -m)
27 +case "${target}" in
28 + "${arch}"-unknown-linux-gnu) ;;
29 + *) echo "build-manylinux.sh: ${target} does not match this ${arch} host" >&2; exit 1 ;;
30 +esac
31 +
32 +repo=$(cd "$(dirname "$0")/../.." && pwd)
33 +cargo_home=${CARGO_HOME:-$HOME/.cargo}
34 +toolchain=$(sed -n 's/^channel = "\(.*\)"/\1/p' "${repo}/rust-toolchain.toml" | head -n 1)
35 +[ -n "${toolchain}" ] || { echo "build-manylinux.sh: no channel in rust-toolchain.toml" >&2; exit 1; }
36 +image=quay.io/pypa/manylinux2014_${arch}
37 +
38 +mkdir -p "${cargo_home}/registry" "${cargo_home}/git"
39 +
40 +# The container runs as root. Files it creates under target/ and the cargo cache are handed
41 +# back to the runner user, which the later steps and rust-cache's post step need to write to.
42 +docker run --rm \
43 + -v "${repo}:/io" \
44 + -v "${cargo_home}/registry:/cargo/registry" \
45 + -v "${cargo_home}/git:/cargo/git" \
46 + -w /io \
47 + -e CARGO_HOME=/cargo \
48 + -e RUSTUP_HOME=/rustup \
49 + -e CARGO_TERM_COLOR="${CARGO_TERM_COLOR:-auto}" \
50 + -e TOOLCHAIN="${toolchain}" \
51 + -e TARGET="${target}" \
52 + -e OWNER="$(id -u):$(id -g)" \
53 + "${image}" \
54 + bash -euo pipefail -c '
55 + trap "chown -R ${OWNER} /io/target /cargo/registry /cargo/git 2>/dev/null || true" EXIT
56 + # Same before-script-linux as release-pypi: aws-lc-sys needs a full Perl.
57 + yum install -y -q perl-core
58 + curl -sSf https://sh.rustup.rs | sh -s -- -y -q --profile minimal --default-toolchain "${TOOLCHAIN}"
59 + export PATH=/cargo/bin:$PATH
60 + ldd --version | head -n 1
61 + cargo build --locked --release -p sigit --target "${TARGET}"
62 + '
packaging/linux/check-glibc.sh new
+24
@@ -0,0 +1,24 @@
1 +#!/usr/bin/env bash
2 +# Fails if a Linux binary needs a newer glibc than the release floor.
3 +#
4 +# packaging/linux/check-glibc.sh target/x86_64-unknown-linux-gnu/release/sigit [2.17]
5 +#
6 +# The Linux releases are built in manylinux2014 (packaging/linux/build-manylinux.sh) so they run
7 +# on glibc 2.17 and later. If a build slips back onto the runner's own toolchain, the binary still
8 +# builds and passes tests there, then fails to start on an older distro with "GLIBC_2.xx not
9 +# found". This check catches it before release, the way check-static-crt.ps1 does for Windows.
10 +set -euo pipefail
11 +
12 +binary=${1:?usage: check-glibc.sh <binary> [max-glibc]}
13 +max=${2:-2.17}
14 +
15 +top=$(objdump -T "${binary}" | grep -o 'GLIBC_[0-9.]*' | sed 's/GLIBC_//' | sort -uV | tail -n 1)
16 +if [ -z "${top}" ]; then
17 + echo "::error::objdump found no GLIBC_ symbols in ${binary}"
18 + exit 1
19 +fi
20 +if [ "$(printf '%s\n%s\n' "${top}" "${max}" | sort -V | tail -n 1)" != "${max}" ]; then
21 + echo "::error::${binary} needs GLIBC_${top}, above the ${max} floor"
22 + exit 1
23 +fi
24 +echo "${binary} needs at most GLIBC_${top} (floor ${max})."