|
cpp-toolchain documentation
Up-to-date C++ toolchain images for the complete development cycle - GNU and LLVM side by side
|
Standalone scripts to install CMake, GCC, LLVM/Clang, cross-compilation binutils (+ cross-libc), and Doxygen, reusable on any Debian/Ubuntu-based system.
All take no dependency on each other and describe themselves with --help. Installing needs root. The exceptions run as any user: the --list-installed and --list-targets query modes, answered from dpkg, and doxygen.sh --prefix=<directory>, which installs under a directory of the caller's choosing.
Registers the Kitware apt repository (via its kitware-archive.sh bootstrap), then installs a single cmake version. Unlike gcc.sh/llvm.sh, CMake has no side-by-side multi-version story (no update-alternatives) - the Kitware repo only ever exposes whichever versions are currently published.
| Option | Type | Default | Description |
|---|---|---|---|
| -v, --versions | string | latest | latest | an upstream version (e.g. '4.4.0') | an exact apt version string as reported by --list-available (e.g. '3.29.6-0kitware1ubuntu24.04.1') |
| -l, --list-available | boolean | 0 | Only list the versions available via apt-cache madison cmake, without installing anything |
| -s, --silent | boolean | 1 | Suppress log output |
| -a, --alias | boolean | 0 | Append the resulting cmake_version variable to /etc/bash.bashrc and /etc/zsh/zshrc |
| -r, --rc | boolean | 0 | Also register the Kitware release-candidate apt repository |
| -h, --help | - | - | Display usage |
Boolean values accept y|yes|1|true / n|no|0|false (case-insensitive).
Example: list available versions, then install a specific one:
An upstream version (4.4.0) is resolved to the Kitware apt version that carries it (4.4.0-0kitware1ubuntu24.04.1), newest first if several qualify. That indirection is what makes CMake pinnable by automation: the apt version string is distro-specific and published by no upstream datasource, whereas the plain upstream version is exactly what release trackers do publish - so a caller can pin 4.4.0 and let a bot follow Kitware/CMake releases.
Installs one or more GCC versions from the ubuntu-toolchain-r/test PPA (added automatically if missing), sets up update-alternatives for gcc/g++/gcov/gcov-tool, and (by default) installs the matching -multilib packages.
| Option | Type | Default | Description |
|---|---|---|---|
| -v, --versions | string | latest-stable | all | latest | latest-stable | >=<number> | space-separated version numbers (e.g. '13 14') |
| -l, --list-available | boolean | 0 | Only list the versions that --versions resolves to, without installing anything |
| --list-installed | boolean | 0 | Only list the major versions already installed, filtered by --versions when that is given explicitly. |
| -s, --silent | boolean | 1 | Suppress log output |
| -a, --alias | boolean | 0 | Append the resulting gcc_versions variable to /etc/bash.bashrc and /etc/zsh/zshrc |
| --multilib | boolean | 1 | Install gcc-<N>-multilib / g++-<N>-multilib (secondary ABIs: -m32, -mx32) |
| --mode | string | full | How much of the toolchain to install: runtime | minimalistic | full - see below |
| -m, --minimalistic | boolean | 0 | Alias for --mode=minimalistic, kept for compatibility |
| -h, --help | - | - | Display usage |
| --mode | Installs |
|---|---|
| runtime | libstdc++6 libgcc-s1 - the shared libraries, no compiler |
| minimalistic | gcc-<N> g++-<N>; disables --multilib unless it was set explicitly |
| full | the above plus gcc-<N>-multilib / g++-<N>-multilib |
runtime takes no --versions: libstdc++6 and libgcc-s1 carry no major in their name, one install serves every compiler, and there is nothing for a version selector to choose between. It is what the Dockerfile's runtime stage installs, and it names the two packages the validation gate expects from this PPA - so that expectation is written down once rather than in both places.
Boolean values accept y|yes|1|true / n|no|0|false (case-insensitive).
Multilib is best-effort by default: the packages lag for brand-new GCC versions and do not exist on non-amd64 hosts, so an unavailable one is skipped with a warning.
An explicit --multilib=yes is honored strictly and fails hard instead - the default resolution is resilient, an explicit request is not silently ignored.
Example: install the two latest available versions:
Wraps the upstream apt.llvm.org/llvm.sh installer:
| Option | Type | Default | Description |
|---|---|---|---|
| -v, --versions | string | latest-stable | all | latest | latest-stable | >=<number> | space-separated version numbers (e.g. '17 18') |
| -l, --list-available | boolean | 0 | Only list the versions that --versions resolves to, without installing anything |
| --list-installed | boolean | 0 | Only list the major versions already installed, filtered by --versions when that is given explicitly |
| -s, --silent | boolean | 1 | Suppress log output |
| -a, --alias | boolean | 0 | Append the resulting llvm_versions variable to /etc/bash.bashrc and /etc/zsh/zshrc |
| --mode | string | full | How much of the toolchain to install: runtime | minimalistic | coverage | full - see below |
| -c, --cleanup | boolean | 0 | Purge any pre-existing llvm-*/lldb-*/clang-*/python3-lldb-* packages before installing |
| -h, --help | - | - | Display usage |
The three modes are tiered, and each one selects both the packages installed and the update-alternatives registered:
| --mode | Installs | Registers unversioned |
|---|---|---|
| runtime | libc++1 libc++abi1 - the shared libraries, no compiler and no headers | nothing |
| minimalistic | clang lld lldb clangd + the compiler runtimes | clang, clang++ |
| coverage | the above + llvm-<N> | + llvm-cov, llvm-profdata |
| full | the upstream all set - analysis tools, libclang development headers, ... | + clang-tidy, clang-format, clangd, lldb, scan-build, ... |
runtime is the only mode that does not run the upstream installer at all - its smallest package set still starts with clang-<N>.
It registers the same apt.llvm.org repository upstream would have, then installs those two packages and stops.
--versions still selects which suite, because that is what decides the libc++ release,
but nothing is registered under an unversioned name: there is no binary to alternate between.
It needs LLVM 20 or later, and says so rather than guessing: apt.llvm.org carried the major in these package names until then, and spelled it inconsistently - libc++1-18 and libc++1-19, but libc++1-17t64 across the time_t transition.
libunwind is deliberately absent, since apt.llvm.org builds libc++abi against libgcc_s, which every Debian-derived image already has.
The compiler runtimes are the libc++ stack (libc++-<N>-dev, libc++abi-<N>-dev, libunwind-<N>-dev), OpenMP (libomp-<N>-dev) and, from LLVM 15, the sanitizer and Polly runtimes (libclang-rt-<N>-dev, libpolly-<N>-dev). Every mode that installs a compiler installs them: they are compiler capabilities - -stdlib=libc++, -fopenmp, -fsanitize=... - rather than tools, so a minimalistic install still compiles everything a full one does.
Boolean values accept y|yes|1|true / n|no|0|false (case-insensitive).
update-alternatives priority is the version number, so the highest installed version wins the unversioned clang/clang++ - the same rule gcc.sh uses.
The modes are meant to be layered: a first --mode=minimalistic run installs and registers only the compilers, and a later --mode=full run over the same environment adds clang-tidy/clang-format/clangd/lldb/scan-build without touching them. Useful when a compile-only environment and a full analysis one are built from a common base - which is exactly how the Dockerfile's build and static-analysis stages relate.
runtime layers the same way, from underneath: it puts libc++1 in place, and a later --mode=minimalistic run over it adds the compiler and headers that match.
That is how build sits on runtime - one library, installed once, at the bottom of the stack where the image that only has to run things can stop.
Example: install the two latest available versions:
Installs a complete cross toolchain for each target.
An empty target list (--targets='') installs nothing and exits successfully, so a caller can make cross support conditional without branching around the call.
By default (--with-gcc=1) installs g++-<triplet>, which transitively pulls the whole set - cross binutils (as, ld, objdump, readelf, strip),
cross glibc, cross libgcc and cross libstdc++ - laid out under /usr/lib/gcc-cross/<triplet>/.
That is enough to compile and link C and C++ for the target, and Clang's driver auto-detects the cross-GCC install, so clang --target=<triplet> works (with libstdc++) too.
With --with-gcc=0, or for targets that have no cross-g++, it falls back to bare binutils-<triplet> + libc6-dev-<debarch>-cross:
This fallback is compiler-agnostic - the bare binutils serve any toolchain emitting that arch, which is why cross tooling lives here rather than in gcc.sh (gcc.sh owns --multilib, a secondary ABI of the host arch - a different thing).
| Option | Type | Default | Description |
|---|---|---|---|
| -t, --targets | string | 'common' | Space-separated GNU target triplets, or common, or all |
| --list-targets | boolean | 0 | Only print the triplets --targets resolves to, without consulting apt. A pure query, and how anything downstream resolves common |
| --with-gcc | boolean | 1 | Install g++-<triplet> (full toolchain, links C/C++); 0 = bare binutils + libc |
| -l, --list-available | boolean | 0 | Only list the cross target triplets available on this host, restricted to --targets. Use --targets=all for every triplet the host offers |
| --list-installed | boolean | 0 | Only list the cross target triplets already installed, restricted to --targets when that is given explicitly. |
| -s, --silent | boolean | 1 | Suppress log output |
| -h, --help | - | - | Display usage |
Boolean values accept y|yes|1|true / n|no|0|false (case-insensitive).
Each target is installed best-effort - availability is host/arch dependent, so an unavailable package is skipped with a warning rather than failing the run.
25 of 32 targets have a cross-g++; the 7 without one (ia64, hppa64, loongarch64, and the four mips-n32 variants) automatically use the binutils + libc fallback.
CPU, FPU and ABI variants are encoded in the triplet - there is no separate switch:
| Axis | Example triplets |
|---|---|
| FPU | arm-linux-gnueabi (soft-float) vs arm-linux-gnueabihf (hard-float VFP) |
| ABI | mips64-linux-gnuabi64 (n64) vs mips64-linux-gnuabin32 (n32) |
| ABI | x86-64-linux-gnu (LP64) vs x86-64-linux-gnux32 (x32) |
| CPU / ISA | mipsisa32r6-linux-gnu, mipsisa64r6el-linux-gnuabi64 (MIPS release 6) |
| Endianness | powerpc64 vs powerpc64le, mips vs mipsel |
In the fallback path, cross-libc packages key off the Debian architecture alias, not the GNU triplet (aarch64-linux-gnu → arm64, mipsisa64r6el-linux-gnuabin32 → mipsn32r6el), so the script carries an internal triplet_to_deb_arch lookup table (29 triplets; alpha, hppa64, ia64 have no cross-libc and get binutils only).
common is the set these images ship - aarch64-linux-gnu, arm-linux-gnueabihf, riscv64-linux-gnu, x86-64-linux-gnu - and binutils.sh is where that list lives. Nothing else in the repository repeats it: the workflows pass common through as BINUTILS_TARGETS, and the validation gate expands it with --list-targets.
Example: discover the available targets, then install a couple:
--list-available without --targets answers "which of the defaults exist here", the same way gcc.sh --list-available answers for its default --versions. --targets=all is what lists every triplet the host offers.
With a cross-g++ (the default), C and C++ both compile and link for the target, with GNU cross tools or with Clang:
On amd64, installs the official pre-built binary from the GitHub release, because Ubuntu's doxygen package lags upstream by years: mermaid fences render as diagrams only from 1.17.0 on (doxygen PR #12069). Only the doxygen binary is taken out of the tarball; dot still comes from the graphviz package.
On every other architecture, upstream publishes no pre-built binary, so the distro package is installed instead - older, but the only portable option short of a source build.
The tag carries upstream's underscored spelling, and the dotted form the download asset uses (doxygen-1.17.0.linux.bin.tar.gz) is derived from it.
| Option | Type | Default | Description |
|---|---|---|---|
| <release-tag> | string | - | Required, positional. A published doxygen release, Release_1_17_0 |
| --prefix=<directory> | string | /usr/local | Install to <directory>/bin/doxygen |
| -h, --help | - | - | Display usage |
--prefix needs no root and writes nothing outside the directory given, which is how docs/details/generate.sh renders the documentation site. Under a prefix the apt fallback is refused rather than taken: apt writes /usr/bin whatever was asked for, and an older doxygen installed somewhere other than where the caller pointed is worse than a failure.
Example: the images' system-wide install, and a private one: