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})."