|
cpp-toolchain documentation
Up-to-date C++ toolchain images for the complete development cycle - GNU and LLVM side by side
|
Thanks for helping improve cpp-toolchain.
This document covers the contribution workflow: how changes get in, what the CI gate expects, and how images get published.
There is a hard split between building (gate, runs on every PR) and publishing (pushes tags, only ever from main):
| Workflow | Trigger | What it does | Pushes? |
|---|---|---|---|
| docker-build | every PR to main, every push to main | builds the normal variant in full and the cross-arch build, then runs the images validation gate; a push to main also builds the cross-arch static-analysis / documentation / dev | ❌ |
| docker-publish | GitHub release (major), merged candidate PR (minor), twice-monthly rc schedule, manual dispatch | builds rcs/majors, promotes minors, pushes tags to Docker Hub + GHCR | ✅ |
| release-candidate-check | every PR to main (no-op unless a promotion record is touched) | validates the promotion record and smoke-tests the candidate image by digest | ❌ |
| documentation | every push to main, manual dispatch | renders the repository's markdown and publishes it to the gh-pages branch (docs/details) | ❌ |
| ubuntu-snapshot | monthly schedule (25th), manual dispatch | opens a PR moving the Ubuntu archive snapshot forward | ❌ |
What those workflows share lives in .github/actions/ as composite actions: buildx setup, registry login, promotion-record identification, and the sticky issue the two report steps share.
The gate is deliberately not path-filtered - it is a required status check, so it runs on every PR (a path-filtered workflow that never runs would leave the PR waiting forever on a check that never reports).
When the Dockerfile and scripts are untouched the GitHub Actions cache makes it a near-no-op.
Only a push to main writes that cache, so a PR replays the layers the last merge exported, and the first PR after a snapshot bump pays for a cold build.
Nothing is pushed, no registry credentials are needed, and the gate therefore also works for PRs coming from forks (which have no access to secrets).
docker-build builds, in dependency order on a single buildx builder, both image variants:
runtime carries no toolchain, so it has no cross variant. A break in either variant fails the gate.
BINUTILS_TARGETS is consumed at the tail of build, so changing it re-parents every stage above it: the cross-arch static-analysis / documentation / dev can hit no cache and reinstall their packages in full, for package sets the normal variant has already built. A PR therefore gates the cross toolchain itself, through build and validate-build. Build the three locally, as below, when you change what they install.
It then runs the images validation gate - the validate-build and validate-runtime stages, which assert that every toolchain package still comes from the repository that owns it, and that a binary compiled in build still runs on runtime.
Both are throwaway stages built on layers the job already has, so they cost a cache hit plus their own RUN. See docs/IMAGES_VALIDATION.md.
Reproduce it locally before pushing (context is the repo root). This is the driver the gate runs, with the cache flags off:
build-stages.sh calls docker buildx build. Without buildx, drive docker build over the same stage lists:
The heavy build layer is produced once and reused by static-analysis / documentation / dev, so a full local run is cheaper than five independent builds.
.devcontainer/ is for working on cpp-toolchain, not for consuming it. Its docker-compose.yaml names no registry: it builds the dev target from the repo-root Dockerfile and mounts the checkout at /workspace, so Reopen in Container gives you a from-source environment with your working tree in it, at the cost of a full local build.
Consuming the published images needs none of that - one devcontainer.json with an image key, in Using the images.
The local build above builds every stage; Build your own image builds a single customised one. Different jobs, so neither replaces the other.
Publishing is docker-publish - a separate workflow that contributors never trigger from a PR:
The full release procedure (promotion, urgent fixes, rollback, failure modes) is in docs/RELEASE_PROCESS.md - the cadence is stated there and nowhere else.
Release notes are composed by scripts/details/render-manifest.py from three things: the versions a release pins, read from the Dockerfile's ARGs; what the images installed where a pin cannot say, read from the promotion record; and the pull requests merged since the previous release. That last part is a plain list of PR titles, so the title you give a PR is what a release note shows. What a pin does and does not fix is in Tags & versioning.
Guards protecting the registries:
See Tags & versioning for the full tag scheme.