rhalbersma / rhalbersma/cpp-ci
Declared support: one minimum version per workflow, instead of dropping rungs
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:
- 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.
- A dropped rung says nothing about why.
tiers: qualification,developmentdoes 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 theladderjob 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
libcxx: ∞— dev runs and is excused, stable and qualification are dropped- libc++ implements the adaptor → the dev leg passes → excused-green alarm fires
- a human replaces
∞with the version it landed in — the one manual step, and the number was just handed to them - 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
- Per-leg outcomes reaching the gate. GitHub gives a downstream job one
resultfor an entire matrix. Getting per-leg verdicts means each leg writing one, most likely as a small artifact uploadedif: always()and reduced by the gate. This is the prerequisite for everything else and the only part with a real unknown in it. versionintoolchains.jsonfor three families.gccandclangcarry it;apple-clang,msvcandmingwhave labels only. Apple's needs version-aware comparison rather than integer (16.4<26.6).- The
toolchainaction marks instead of drops. Matrix mode currently drops an unrequested rung with a::notice::; it would emit the leg carrying its state. - The declaration's home. A single file in the caller reads best — it is the support matrix nobody can currently state anywhere — but no
ladderjob checks out the caller, so that means addingactions/checkoutplus a parser to every ladder job. A stub input needs neither and lives next to thetiersinput it modifies. self-check.ymlqueries the packagers. cpp-ci owns the table, so cpp-ci should notice when it is stale. It already runs oncron: '30 5 * * 1'. All five sources answer without a build:ppa:ubuntu-toolchain-r/test(Launchpad orapt-cache madison),apt.llvm.org(does the next suite exist),brechtsanders/winlibs(releases API), andactions/runner-imagesreleases for VS and Xcode.install-msvc-previewalready does exactly this shape — aHEADonaka.ms/vs/18/pre/channel, version out, nothing installed.
Open questions
- What does job-level
continue-on-errorreport intoneeds.<job>.resultfor 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 settledalways()vs!cancelled()empirically; same treatment. - Version arithmetic against a trunk rung is fuzzy.
24-SVNreports version24, 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/developmentwaits 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.sieveDebug failures and the MinGW Debug segfaults (#48 there) stay ordinary bugs, somsvc,clang_clandmingwdo not become requirable on day one.
Landing order
The gate plumbing gates everything else, so it goes first and on one workflow only.
- Probe
continue-on-errorsemantics; spike per-leg outcomes ongcc.yml versionfields forapple-clang,msvc,mingw- Declaration format;
toolchainmarks instead of drops - Gate: AND the counted legs, alarm on excused-green, report the excused set
- Roll across the remaining ladder workflows
self-check.ymlladder-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.ymlweekly; 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-actionsandpip; nothing watches for a GCC, LLVM or Visual Studio release, sotoolchains.jsoncurrently 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
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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