Notcurses-Native.git | .github/workflows/ | _build-linux-glibc.yml


# Reusable workflow: build the linux--glibc prebuilt notcurses # archive in a manylinux_2_28 Docker container (RHEL 8 baseline, # glibc 2.28). Produces binaries that load on every glibc Linux # distro still maintained in 2026 — RHEL 8+, Ubuntu 18.10+, Debian # 10+. (manylinux2014 / RHEL 7 was retired by pypa in March 2025 # and its CentOS 7 yum mirrors are decaying after the June 2024 EOL, # so we're on the actively-maintained successor.) # # Why docker-run from native host instead of GHA's `container:` # directive: GHA's Node 24 JS-action runtime is built against # glibc ≥ 2.27/2.28 + libstdc++ from gcc ≥ 5. While manylinux_2_28 # *does* meet that floor, falling back to docker-run keeps both # Linux lanes (glibc + musl) on the same pattern — easier to reason # about, and gives us headroom if GHA bumps node again. # # RHEL 8's repos don't ship modern ffmpeg / libdeflate, so we # source-build both inside the container, along with libunistring — # that one for licensing rather than availability: it is LGPL, it # ships in the pack, and a dnf package version we can't pin isn't a # corresponding source we can still produce later. ncurses is still # dnf's (MIT-style X11, no source duty). cmake comes from pip # (RHEL 8 ships 3.20, notcurses needs 3.21+). name: _build-linux-glibc on: workflow_call: inputs: arch: description: '`x86_64` or `aarch64`.' type: string required: true artifact-name: description: 'Archive basename, e.g. notcurses-linux-x86_64-glibc.' type: string required: true jobs: build: name: build-linux-${{ inputs.arch }}-glibc runs-on: ${{ inputs.arch == 'aarch64' && 'ubuntu-22.04-arm' || 'ubuntu-22.04' }} steps: - uses: actions/checkout@v6 with: submodules: recursive - name: Read pinned notcurses SHA from NOTCURSES_FORK id: ncpin run: | # tr -d '\r' defends against a stray CRLF checkout (e.g. # core.autocrlf=true overriding the .gitattributes hint on # an existing clone that hasn't been renormalized). sha=$(awk -F= '/^sha=/{print $2}' NOTCURSES_FORK | tr -d '\r') echo "sha=$sha" >> "$GITHUB_OUTPUT" - name: Cache notcurses source checkout (per-SHA) uses: actions/cache@v4 with: # The checkout only — NOT the build tree cmake creates # inside it. This key is shared by all four Linux lanes # (glibc/musl x x86_64/aarch64): whichever finishes first # saves the cache and the rest restore it on the next run. # A `build/` in there would hand a manylinux-x86_64 # CMakeCache, and its object files, to an alpine-aarch64 # configure — same /work path, so cmake would not even # notice the source dir had moved. Reproduced locally by # accident while proving out this chunk, which is how it # was found; in CI it would have surfaced as an # inexplicable cross-lane build failure long after the # commit that caused it. path: | _ci-cache/notcurses-source !_ci-cache/notcurses-source/*/build # Pin-keyed so bumping NOTCURSES_FORK invalidates the cache # automatically. No fallback restore-keys — a stale half- # checkout is worse than re-fetching (~5s shallow clone). key: notcurses-source-${{ steps.ncpin.outputs.sha }} # restore/save split rather than the combined actions/cache: the # combined action saves in a post step that only runs when the # job SUCCEEDS. The r10 dispatch proved the cost on the Windows # lane — it built the entire libdeflate→dav1d→vpx→opus chain, # died at the ffmpeg download, and saved nothing, so the next # push re-paid the whole ~15-20 min build. Saving explicitly, # right after the docker run that builds the chain and BEFORE # the docker run that builds notcurses (which is what can still # fail afterwards), keeps the cache whatever that second run # goes on to do. See scripts/ci/build-linux-glibc.sh's # DEPS_ONLY branch, which is what lets this be its own docker # invocation instead of a step buried inside the full build. - name: Restore source-built codec + ffmpeg chain id: glibc-deps uses: actions/cache/restore@v4 with: # Cached path is workspace-relative so the docker bind # mount picks it up automatically. The build script reads # it via $CACHE_DIR. path: _ci-cache/manylinux_2_28-${{ inputs.arch }} # Key on: # * the manylinux_2_28_ tag (image baseline) # * hashes of every source-build script so a version # bump or config change invalidates the cache. # No fallback restore-keys — partial caches would corrupt # the install tree mid-pkgconfig. key: vendored-deps-manylinux_2_28-${{ inputs.arch }}-${{ hashFiles('scripts/ci/build-ffmpeg.sh', 'scripts/ci/build-libdeflate.sh', 'scripts/ci/build-libdav1d.sh', 'scripts/ci/build-libvpx.sh', 'scripts/ci/build-libopus.sh', 'scripts/ci/build-libunistring.sh') }} - name: Pull build image run: docker pull quay.io/pypa/manylinux_2_28_${{ inputs.arch }} - name: Build codec + ffmpeg chain (cache miss only) if: steps.glibc-deps.outputs.cache-hit != 'true' run: | # $PWD on the runner is bind-mounted to /work in the # container — so checkout's output AND the cache dir are # visible to the build script. DEPS_ONLY=1 makes the script # stop right after the codec chain is built (or, in # practice here, always built — this step only runs on a # cache miss) and chown $CACHE_DIR back to the runner user # before exiting, so the save step below can read it. docker run --rm \ -v "$PWD:/work" \ -w /work \ -e CACHE_DIR=/work/_ci-cache/manylinux_2_28-${{ inputs.arch }} \ -e DEPS_ONLY=1 \ quay.io/pypa/manylinux_2_28_${{ inputs.arch }} \ bash scripts/ci/build-linux-glibc.sh - name: Save source-built codec + ffmpeg chain if: steps.glibc-deps.outputs.cache-hit != 'true' uses: actions/cache/save@v4 with: path: _ci-cache/manylinux_2_28-${{ inputs.arch }} key: ${{ steps.glibc-deps.outputs.cache-primary-key }} - name: Build + bundle in manylinux_2_28 container run: | # $PWD on the runner is bind-mounted to /work in the # container — so checkout's output AND the cache dir are # visible to the build script. $CACHE_DIR is warm by now # either way (restored above, or just built + saved), so # this run's own internal cache-hit check skips straight to # the notcurses build. docker run --rm \ -v "$PWD:/work" \ -w /work \ -e CACHE_DIR=/work/_ci-cache/manylinux_2_28-${{ inputs.arch }} \ quay.io/pypa/manylinux_2_28_${{ inputs.arch }} \ bash scripts/ci/build-linux-glibc.sh - name: Package + upload uses: ./.github/actions/package-and-upload with: artifact-name: ${{ inputs.artifact-name }} format: tar.gz platform: linux