Skip to content

ci: restructure the build matrix around the supported compiler range - #19

Merged
skipbit merged 8 commits into
mainfrom
ci/restructure-support-matrix
Jul 31, 2026
Merged

ci: restructure the build matrix around the supported compiler range#19
skipbit merged 8 commits into
mainfrom
ci/restructure-support-matrix

Conversation

@skipbit

@skipbit skipbit commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Why

The macOS GCC jobs have been failing since the macos-latest label moved to macOS 26. The SDK defines xnu_static_assert_struct_size only for Clang, so Homebrew GCC leaves the macro unexpanded and the build stops inside mach/message.h. Raising the GCC version does not help — the same failure is reported for 15, and the root cause sits in the SDK rather than in the compiler.

Looking into it surfaced a wider mismatch between what the documentation promises and what CI verifies:

  • MSVC 2022+ was declared without a single Windows job
  • macOS was verified with 10 jobs without being declared in the requirements
  • the three places stating the requirements disagreed: README said Clang 17+, contributing.rst said Clang 16+
  • the macOS GCC jobs used a floating OS label with pinned compilers, so an OS change landed straight on a fixed configuration. The same shape existed on Linux, where a jammy-pinned apt.llvm.org list was being added to a noble runner
  • macos-14 is scheduled for removal in November, so the Clang matrix was already a generation stale

What the verification found

Removing the -stdlib=libc++ override exposed something the declared range had been hiding:

Configuration Result
ubuntu-24.04 / GCC 13 pass
ubuntu-24.04 / Clang 17 + libstdc++ fail
ubuntu-24.04 / Clang 18 + libstdc++ fail
ubuntu-24.04 / Clang 18 + libstdc++ 14 headers fail
ubuntu-26.04 / GCC 15 pass
ubuntu-26.04 / Clang 20, 22 pass

Clang on ubuntu-24.04 cannot compile the C++23 std::expected declarations in platform/path.h against the libstdc++ it is paired with, and installing newer libstdc++ headers alongside does not change that. GCC 13 builds fine on the same image, so libstdc++ 13 does provide std::expected — the failure is in how Clang consumes it.

The old configuration only ever worked because it forced libc++, which meant the declared Clang lower bound had never been verified against the standard library most Linux users get by default. So the lower bound moves to Clang 20, the oldest release that builds with either standard library without help.

What changed

Required gates pin every runner image and take compilers from the image only:

Compiler Lower bound Newest verified
GCC ubuntu-24.04 / 13 ubuntu-26.04 / 15
Clang ubuntu-26.04 / 20 ubuntu-26.04 / 22
Apple Clang macos-15 macos-26

The bounds deliberately do not line up across compilers — that asymmetry is what the table above measured, and getting-started.rst explains it so it does not read as arbitrary.

This removes the ppa:ubuntu-toolchain-r/test and apt.llvm.org dependencies from the required path, so an upstream repository change can no longer break a merge. All apt-get install / brew install steps are gone as well — cmake and pkg-config ship with the runner images, and the installation jobs now report their versions so a missing tool surfaces immediately.

Other changes to the required gates:

  • macOS Homebrew GCC jobs are dropped. That combination was never declared as supported, and its breakage originates outside this project.
  • No standard library is imposed. The required gates build against the default, and the nightly watch covers libc++ on the same Clang 20, so neither ends up being the only one that works.
  • Static library and installation jobs are pinned to specific images instead of the floating labels.

A nightly watch (nightly.yml) covers what the required gates deliberately exclude: the newest GCC and Clang from external repositories, libc++, and the floating runner labels. Both version lookups resolve dynamically from apt, so they keep tracking upstream without edits. Breaking there is the signal we want ahead of users hitting it, so these jobs never gate a pull request and should not be added to required status checks.

The requirements are restated as a verified range plus best effort, with the README as the single source:

  • Linux: GCC 13–15 or Clang 20–22
  • macOS: the Apple Clang shipped with macOS 15 or 26
  • Newer versions: best effort, exercised by the nightly watch

An open-ended GCC 13+ cannot be verified by a finite matrix, so the old wording promised more than CI ever checked. MSVC and Windows are removed from the declared support for the same reason. The combinations are now stated per platform rather than as parallel lists, since listing compilers and operating systems on separate axes read as if macOS with GCC were supported.

Effect on check count

Build checks go from 20 to 16: the four macOS GCC jobs are gone, and the rest is unchanged in count (contract verification 12, packaging 4).

Notes

  • -Werror is currently PUBLIC in src/CMakeLists.txt, so it propagates to consumers using find_package or FetchContent. Every time the newest verified compiler moves up, a newly added warning can break both this project and downstream builds. Worth addressing separately.
  • The public headers and docs/sphinx/source/api/platform.rst still describe Windows behaviour. Those are descriptions of implementation branches rather than support declarations, so they are left as they are, but the asymmetry is worth knowing about.
  • main has no branch protection configured today. If required status checks are added later, they will need to match the new job names.

skipbit added 8 commits July 31, 2026 09:59
The macOS GCC jobs have been failing since the macos-latest label moved to
macOS 26: the SDK defines xnu_static_assert_struct_size only for Clang, so
Homebrew GCC leaves the macro unexpanded and the build stops inside
mach/message.h. Raising the GCC version does not help, as the same failure is
reported for 15.

That failure exposed a wider mismatch between what the documentation promises
and what CI verifies. MSVC 2022+ was declared without a single Windows job,
macOS was verified without being declared, and the three places stating the
requirements disagreed with each other (README said Clang 17+, contributing.rst
said Clang 16+).

Rework the matrix so that it verifies the declared range and nothing else:

- Pin every required job to a specific runner image and take compilers from the
  image only. ubuntu-24.04 provides GCC 13 and Clang 17 (the lower bound),
  ubuntu-26.04 provides GCC 15 and Clang 22 (the newest we verify). This drops
  the ppa:ubuntu-toolchain-r/test and apt.llvm.org dependencies, including the
  jammy-pinned list that was being added to a noble runner.
- Replace macos-14/macos-15 with macos-15/macos-26. macOS 14 is scheduled for
  removal in November, and Apple Clang tracks the OS image, so varying the OS
  already varies the compiler.
- Drop the macOS Homebrew GCC jobs. That combination was never declared as
  supported, and the breakage originates in the SDK rather than in this project.
- Stop forcing -stdlib=libc++ on Linux Clang. Most Clang users on Linux build
  against the default standard library; libc++ moves to the nightly watch.
- Pin the static library and installation jobs to specific images instead of
  the floating labels.

Add a nightly watch for what the required gates deliberately exclude: the
newest GCC and Clang from external repositories, libc++, and the floating
runner labels. Breaking there is the signal we want ahead of users hitting it,
so those jobs never gate a pull request.

Restate the requirements as a verified range plus best effort, with the README
as the single source. An open-ended "GCC 13+" cannot be verified by a finite
matrix, and GCC 16 is deliberately left to the nightly watch. Remove MSVC and
Windows from the declared support so the promise matches what is exercised.
Follow-up to the matrix rework, addressing three gaps found while reviewing it.

The requirements listed compilers and operating systems on separate axes, which
read as if macOS with GCC were supported. It is not: the required gates cover
Linux with GCC or Clang, and macOS with the Apple Clang from the OS image.
State the combinations per platform instead of listing them in parallel.

The nightly jobs pinned GCC 16 and derived the Clang version by scanning
/usr/lib for llvm-*, which also matches the versions the runner image ships
and would silently pick one of those. Query apt after adding each repository
instead, so both jobs install the newest version that repository offers and
keep tracking upstream without edits. An empty result now fails the step rather
than invoking a command with no version suffix.

Matrix entries are objects, so the generated job names embedded the whole
object. Name the jobs explicitly, and report cmake and pkg-config versions in
the installation jobs now that the package installation steps are gone.
The comments claimed the resolved version comes from the repository just
added, but the query looks at every configured source. State what the step
does, and record that tracking the stable release rather than trunk is a
choice: the watch exists to catch what users will hit, not what is still in
development.
Clang 17 cannot compile the C++23 std::expected declarations in path.h
against libstdc++. It only ever worked because the previous configuration
forced libc++, so the lower bound was never verified against the standard
library most Clang users on Linux get by default.

Try 18, the next version the ubuntu-24.04 image ships, to find the oldest
Clang that builds with either standard library.
GCC 13 builds fine, so libstdc++ 13 does provide std::expected; Clang just
cannot consume that release's <expected> in C++23 mode. The 14 series fixed
the interoperability, and ubuntu-24.04 ships it, so install those headers for
the Clang jobs and let Clang pick them up.
Installing the libstdc++ 14 headers changed nothing: Clang 18 on
ubuntu-24.04 still cannot see std::expected. Whichever way the exact cause
falls, a consumer building on that image would hit the same wall and would
have to work around it, so there is no Clang lower bound worth declaring
there.

Try Clang 20, the oldest the ubuntu-26.04 image ships, to find the oldest
one that builds against the default standard library without help.
Clang 20 builds against the default standard library on ubuntu-26.04, so
that is the oldest Clang worth declaring. Record the range in the three
places that state the requirements, and explain in the getting started guide
why the Clang bound sits higher than the GCC one — otherwise the asymmetry
looks arbitrary.

Move the libc++ watch onto the same image and compiler as the new lower
bound, so the nightly run covers the standard library the required gates do
not, rather than a version the project no longer claims to support.
The comment still claimed ubuntu-24.04 covers the lower bound, which stopped
being true for Clang. Spell out the asymmetry instead — it is exactly the
place a reader would look to understand why the two compilers start on
different images.
@skipbit
skipbit merged commit 8edd379 into main Jul 31, 2026
19 checks passed
@skipbit
skipbit deleted the ci/restructure-support-matrix branch July 31, 2026 01:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant