rhalbersma / rhalbersma/cpp-ci

Declared support: one minimum version per workflow, instead of dropping rungs

Open
#26 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
No language data
Stars
1
Forks
0
Avg merge
2h 13m
Merged PRs (30d)
45

Description

Design record for a change discussed at length but not yet started. Nothing here is implemented.

The problem

A caller with a rung it cannot build on has exactly one tool today: remove the rung from tiers. sanitizers.yml already states why, in the libcxx_tiers description:

Left in the Clang ladder, one such library reddens this workflow's gate permanently, which is worse than not running the leg: a gate that is always red…

That is the right call given the alternatives, but it costs two things:

  1. You lose the day it starts working. A dropped rung is never observed again. Nobody finds out that libc++ implemented the missing adaptor, or that the runner image picked up the toolset fix, except by remembering to go and look.
  2. A dropped rung says nothing about why. tiers: qualification,development does not distinguish "this compiler generation will never have the feature" from "the fix is three weeks out."

Callers are already working around this in three different spellings:

repo stub says
tabula msvc.yml tiers: qualification,development
bit_set clang-cl.yml tiers: qualification,development
bit_set sanitizers.yml libcxx_tiers: ""

And bit_set's clang-libc++.yml passes nothing at all, runs all six legs, and is permanently red — so clang_libcxx / all cannot be a required check.

The model

One minimum version per workflow. The number is a property of the code, not of the rung: it is the highest version any feature the repository uses requires. when nobody has implemented it yet.

Everything else falls out of comparing that number against each rung's resolved version.

Major gap → do not run. Minor gap → run, do not count.

This split matters because it follows where the information is.

  • Major is what the rung declares. It is in toolchains.json, so the ladder job knows it on Ubuntu before any runner is provisioned, and can drop the leg — no Windows or macOS machine ever starts. The wait is on cpp-ci promoting a rung: a year or more.
  • Minor is whatever the package source, runner image or nightly happens to ship that day. It is knowable only on the leg, after install. The wait is on GitHub's image cadence or a nightly: days to months, which is cheap enough to keep running.

Each decision is made where its input exists. No job needs a fact it cannot reach.

The minor case is not an MSVC quirk. Every family except Apple's floats a minor under a fixed rung label:

family label what floats under it
msvc 2026 the toolset — 14.51.36231 today, whatever the image ships next
mingw 15 tag_prefix: "15." takes the newest 15.x WinLibs publishes
gcc / clang 15, 22 the minor apt currently serves
*-SVN 17-SVN, 24-SVN a nightly, by construction
apple-clang Xcode 16.4 nothing — pinned to an exact Xcode_16.4.app
The development rung always runs

It can be excused, never dropped. A feature can land on trunk any night, and trunk is the only place it can appear first — so dropping the dev rung would leave an waiver dead forever, with nothing able to clear it.

This is what makes the scheme self-maintaining.

The gate

all currently does test "$RESULT" = success against needs.<job>.result, which is one value for the whole matrix. Under this design it needs per-leg outcomes, and then:

leg fails leg passes
counted red green
excused excused red — the waiver is satisfied, delete it
dropped never runs never observed

Failing on an excused leg going green is the entire payoff: "we are waiting on the 18.7.0 fix" stops being something a human must remember to re-check.

The gate should also report which legs are currently excused on every run. It has to collect per-leg outcomes anyway, and a waiver that has been quietly satisfied — or quietly sitting for four months — should be visible rather than something you go looking for.

Lifecycle

  1. libcxx: ∞ — dev runs and is excused, stable and qualification are dropped
  2. libc++ implements the adaptor → the dev leg passes → excused-green alarm fires
  3. a human replaces with the version it landed in — the one manual step, and the number was just handed to them
  4. stable and qualification stay dropped until cpp-ci promotes a rung past it, then become required on their own

Nothing in the matrix is a prediction that goes unchecked.

Worked examples

bit_set, clang_libcxx — the row that proves the model is not over-engineered. All three rungs lack views::cartesian_product (P2374R4, unimplemented even on trunk) → . Rung 22 additionally lacks views::stride, which shipped in libc++ 23. The workflow's number is max(23, ∞) = ∞, so today: stable dropped, qualification dropped, development excused-and-running. When the adaptor lands, step 2 above fires.

bit_set, msvc — VS 2022 has no <flat_set>; it is a C1083 at compile time, and the 17.x line will never gain a C++23 library feature. Minimum is VS 18. Stable is a major gap → dropped, which is what tiers: already achieves by hand.

tabula, msvc — the basic_compass partial-specialization ambiguity, fixed in VS 18.7.0 and not in the runner image. Both failing rungs are the same major (2026 and 2026-Preview are both Visual Studio 18), so this is a minor gap: run, excuse, and clear it when the image refreshes. Today it is instead a permanently red msvc / all.

What has to change

  1. Per-leg outcomes reaching the gate. GitHub gives a downstream job one result for an entire matrix. Getting per-leg verdicts means each leg writing one, most likely as a small artifact uploaded if: always() and reduced by the gate. This is the prerequisite for everything else and the only part with a real unknown in it.
  2. version in toolchains.json for three families. gcc and clang carry it; apple-clang, msvc and mingw have labels only. Apple's needs version-aware comparison rather than integer (16.4 < 26.6).
  3. The toolchain action marks instead of drops. Matrix mode currently drops an unrequested rung with a ::notice::; it would emit the leg carrying its state.
  4. The declaration's home. A single file in the caller reads best — it is the support matrix nobody can currently state anywhere — but no ladder job checks out the caller, so that means adding actions/checkout plus a parser to every ladder job. A stub input needs neither and lives next to the tiers input it modifies.
  5. self-check.yml queries the packagers. cpp-ci owns the table, so cpp-ci should notice when it is stale. It already runs on cron: '30 5 * * 1'. All five sources answer without a build: ppa:ubuntu-toolchain-r/test (Launchpad or apt-cache madison), apt.llvm.org (does the next suite exist), brechtsanders/winlibs (releases API), and actions/runner-images releases for VS and Xcode. install-msvc-preview already does exactly this shape — a HEAD on aka.ms/vs/18/pre/channel, version out, nothing installed.

Open questions

  • What does job-level continue-on-error report into needs.<job>.result for a matrix? It looks like a cheap alternative to artifacts, but it cannot express excused-green, which is the signal the whole design exists for. Worth a throwaway probe before designing around either answer. This repository settled always() vs !cancelled() empirically; same treatment.
  • Version arithmetic against a trunk rung is fuzzy. 24-SVN reports version 24, but it is really pre-24, and a feature landing after the release branch forks ships in 25 while trunk shows it. Comparing the dev rung against a number may be the wrong instrument. A likely simplification: the dev rung is exempt from the comparison entirely — always runs, always advisory-with-alarm while the workflow carries any waiver.
  • MSVC minor granularity. The toolset version is only knowable on the leg, so an MSVC minimum is checked there rather than in the gate. That is consistent with the major/minor split, but it means the comparison has two sites rather than one.
  • An excused leg is excused wholly, not per-diagnostic. While clang_libcxx/development waits on an adaptor, a genuine trunk regression on that leg is absorbed silently — and catching trunk regressions is one of the reasons the dev rung exists. Diagnostic-scoped waivers would fix it and are not worth it: matching compiler error text is brittle across three vendors. Mitigation is keeping waivers rare and reporting the excused set every run.
  • Debug-only failures are not expressible on a (workflow, version) key, by design — support hinges on language and library features, not build type. bit_set's test.set.sieve Debug failures and the MinGW Debug segfaults (#48 there) stay ordinary bugs, so msvc, clang_cl and mingw do not become requirable on day one.

Landing order

The gate plumbing gates everything else, so it goes first and on one workflow only.

  1. Probe continue-on-error semantics; spike per-leg outcomes on gcc.yml
  2. version fields for apple-clang, msvc, mingw
  3. Declaration format; toolchain marks instead of drops
  4. Gate: AND the counted legs, alarm on excused-green, report the excused set
  5. Roll across the remaining ladder workflows
  6. self-check.yml ladder-freshness query

Two pieces are independent and useful whether or not the rest ships:

  • bit_set has no canary. xstd and tabula both run canary.yml weekly; bit_set's only scheduled workflows are scorecard, coverage and codeql. It also carries by far the most waivers, so it is the repository where a satisfied waiver would go unnoticed longest — nothing runs there unless somebody pushes.
  • The ladder-freshness query (5) is worth having on its own. Dependabot covers github-actions and pip; nothing watches for a GCC, LLVM or Visual Studio release, so toolchains.json currently advances only when a human notices.

Knock-on

Repins stop being no-ops. Today a repin is what tabula rhalbersma/tabula#49 and bit_set rhalbersma/bit_set#50 said it was: the pin moves, nothing else. Under this design a repin can promote several excused legs to required, because the ladder advanced past the minimum. That is the right moment for it — a reviewable PR where the newly-required result is visible — but the gate should name which legs changed status under the new pin, rather than leaving the reviewer to infer it from a red check.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the proposed continue-on-error probe and per-leg outcome spike on gcc.yml. Then inspect toolchains.json, the toolchain action, and self-check.yml against the landing order and open questions. Done means the gate can distinguish counted, excused, and dropped legs, report excused-green results, and the version/declaration changes are applied across the ladder workflows.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp, github-actions
Domain
ci-cd
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.