Notcurses-Native.git | .github/workflows/ | _build-linux-glibc.yml edit
# Reusable workflow: build the linux-<arch>-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_<arch> 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