Skip to content

Build Linux releases against a pinned glibc target (replace ubuntu-22.04 runner pin) #2

@gordonwoodhull

Description

@gordonwoodhull

Background

PR #1 pinned the Linux release matrix to ubuntu-22.04 to fix the GLIBC 2.38 floor reported in quarto-dev/quarto-cli#14445. That works because Ubuntu 22.04's glibc 2.35 headers don't emit the C23 __isoc23_strtol redirect that vendored OpenSSL was picking up on Ubuntu 24.04.

It is a runner-image fix, not a target-version fix. We've coupled our binary's compatibility floor to whichever glibc the GitHub-hosted runner happens to ship.

Why this is a follow-up rather than "done"

  • Runner retirement. GitHub retired the ubuntu-20.04 image in April 2025 once that LTS reached EOL. ubuntu-22.04 is supported until June 2027 but will follow the same path. When it goes, ubuntu-latest will be 26.04 (glibc ~2.41) and we're back to the same problem at a higher floor.
  • The floor is implicit. A Rust or C dep update could quietly lift the floor again (e.g., a future OpenSSL version, or any new C dep that happens to call strtol/strtoul/strtoll/strtoull from a host with C23 headers). We have no signal until users on older distros report breakage.
  • Vendoring is load-bearing. We can't just stop vendoring OpenSSL — see the next section for why. The fix has to live in the build environment, not in dep selection.

Why we vendor (and can't switch crypto backends)

typst-kit's packages / downloads features are hardwired to native-tls + openssl. Confirmed via deepwiki on 2026-04-27: there are no rustls dependencies, no rustls feature flags, no conditional compilation for alternative TLS backends, and no indication upstream plans to add any. The choice appears intentional — native-tls gives platform-native cert-store integration (SChannel on Windows, SecureTransport on macOS, OpenSSL on Linux). Migrating would require non-trivial architectural changes upstream.

So the realistic options for our Linux artefacts are:

  1. Vendor OpenSSL (status quo). Self-contained binary; required for the aarch64 cross-compile (commit 7f33d8c); shields users from "needs libssl on the target system" surprises in minimal containers and varying distros. Downside: glibc floor is set by the build host's headers (the bug we just hit).
  2. Dynamic-link to system libssl. No glibc-floor-from-OpenSSL issue, but introduces an ABI-versioned runtime dependency (libssl 1.1 vs 3.0 are incompatible), and the aarch64 cross-compile would need a target sysroot with aarch64 libssl.

For our distribution shape (single tarball, bundled inside Quarto), vendoring is clearly the better fit. So the durable fix is to control what we vendor against, not to stop vendoring.

Proposed direction

Build with explicit target-glibc selection so the floor is a property of the build, not the runner. Two viable mechanisms:

Option A: cargo-zigbuild

- run: pip install cargo-zigbuild
- run: cargo zigbuild --release --target x86_64-unknown-linux-gnu.2.28 --features vendored-openssl
- run: cargo zigbuild --release --target aarch64-unknown-linux-gnu.2.28 --features vendored-openssl

Zig's bundled libc shims pin the GLIBC ABI version regardless of host. Bonus: replaces the manual gcc-aarch64-linux-gnu install + CARGO_TARGET_*_LINKER dance with a single uniform invocation across both arches.

Option B: manylinux container

Build inside quay.io/pypa/manylinux_2_28_x86_64 (glibc 2.28) and the aarch64 variant. Same target floor as (A), no extra tools, but more boilerplate inside each matrix step (install Rust toolchain inside the rpm-based image, configure cross paths).

Option C: cross

cross runs the build inside a per-target Docker image. Solves the cross-compile cleanly, but the default cross images move forward with their own cadence and don't give us explicit glibc-version control the way (A) does.

I'd lean toward (A) cargo-zigbuild. It's the smallest workflow diff, gives us a number to point at when someone asks "what's the minimum glibc," and tidies up the aarch64 path as a side effect. The added dependency (zig + cargo-zigbuild) is well-maintained and widely used by Rust release pipelines.

Acceptance criteria

  • Linux release artefacts (x64 and arm64) target a pinned glibc version (suggest 2.28 for manylinux_2_28 parity / RHEL 8 reach; 2.34 if we only care about RHEL 9 + Debian 12 + Ubuntu 22.04).
  • objdump -T typst-gather | awk '/GLIBC_/{...}' | sort -V confirms no symbol exceeds the chosen floor.
  • Workflow no longer references ubuntu-22.04 for Linux Build steps (can stay on ubuntu-latest for the runner OS itself).
  • Smoke test: artefact runs inside ghcr.io/quarto-dev/quarto:1.10.3 (Ubuntu 22.04) and a rockylinux:8 container.

Out of scope

  • Switching to rustls — typst-kit upstream has no rustls path and no signs of adopting one (see "Why we vendor" above). Revisit only if upstream adds a rustls feature flag.
  • Static-musl Linux artefacts.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions