From 354fe6286e59ed6d0cf9bbf1ab75a43509e83bb1 Mon Sep 17 00:00:00 2001 From: Yuma Endo Date: Fri, 14 Aug 2026 13:54:20 +0900 Subject: [PATCH 1/4] ci: verify Clang against the older libstdc++ releases in the range The required jobs built Clang only against the libstdc++ that ships with the runner image, so 13 and 14 went unverified even though the declared range promises them. Which libstdc++ Clang uses is an axis of its own -- it does not follow from the Clang version the way it does for GCC -- so pin those two releases in a job of their own, leaving the default jobs to build without a flag or a package step. A green build is not evidence for the release a job claims, so the include search path is read back and its first standard library entry compared against the pin. Settle the contract documents accordingly: state the C++ standard, the compiler and the standard library as three axes, list the pairings that hold instead of a product of two axes, and record that GCC with libc++ is not among them because upstream has no -stdlib option to select it with. Coverage is now stated per quadrant: every libstdc++ release in range is covered in the Clang pairings, while the GCC ones cover 13 and 15 only. --- .github/workflows/linux.yml | 117 +++++++++++++++++++++++++ README.md | 39 ++++++--- docs/sphinx/source/contributing.rst | 35 +++++--- docs/sphinx/source/getting-started.rst | 50 +++++++---- 4 files changed, 202 insertions(+), 39 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 8bfd736..db2ac09 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -81,6 +81,123 @@ jobs: - name: Test run: ./build/test/dross_test --gtest_color=yes + # The jobs above cover only the libstdc++ that comes with the runner image + # (15 on ubuntu-26.04), but the declared range promises the older releases a + # consumer may still be sitting on. Which libstdc++ Clang uses is an axis of + # its own — it does not follow from the Clang version the way it does for + # GCC — so the rest of the range is pinned here instead of being left to + # best effort. The pairing is what needs the evidence: these releases predate + # both Clang versions, and -Werror is public, so a diagnostic or a header + # difference on this axis reaches consumers. + # + # Like libc++ below, these headers come from the distribution's own + # repository (universe), so no external source enters a required gate. + build-ubuntu-clang-libstdcxx: + name: Ubuntu Clang ${{ matrix.clang }} libstdc++ ${{ matrix.libstdcxx }} (${{ matrix.build_type }}) + runs-on: ubuntu-26.04 + strategy: + fail-fast: false + matrix: + build_type: [Debug, Release] + clang: [20, 22] + # 15 is the runner default, already covered by the jobs above. + libstdcxx: [13, 14] + + env: + # x86_64-linux-gnu is hardcoded: ubuntu-26.04 is an x86-64 runner today, + # so that is the only triple whose GCC installation this job has to name. + GCC_INSTALL_DIR: /usr/lib/gcc/x86_64-linux-gnu/${{ matrix.libstdcxx }} + LIBSTDCXX_HEADERS: /usr/include/c++/${{ matrix.libstdcxx }} + + steps: + - uses: actions/checkout@v7 + + - name: Install libstdc++ ${{ matrix.libstdcxx }} + run: | + sudo apt-get update + sudo apt-get install -y libstdc++-${{ matrix.libstdcxx }}-dev + + - name: Report toolchain versions + run: | + clang-${{ matrix.clang }} --version + cmake --version + + # This job's name claims a specific libstdc++, and a green build is not + # evidence for that claim: were --gcc-install-dir to resolve to another + # release than the one pinned, the build would still succeed, just not + # against what the name says. So the include search path is read back and + # its first standard library entry is compared against the pinned release. + # + # The directory check and the search path check do not subsume each other: + # the directory comes from libgcc-N-dev while the headers come from + # libstdc++-N-dev, so the directory can exist with no headers behind it — + # in which case Clang drops the standard library entries entirely and the + # comparison below reports that rather than a wrong version. + # + # What this does not cover is the flag going missing from the configure + # step below: this check would still pass, and the build would quietly fall + # back to the runner default. + - name: Verify the pinned libstdc++ is the one Clang selects + run: | + set -euo pipefail + + if [ ! -d "$GCC_INSTALL_DIR" ]; then + echo "$GCC_INSTALL_DIR does not exist: installing libstdc++-${{ matrix.libstdcxx }}-dev did not bring in the GCC installation this job pins." >&2 + exit 1 + fi + + search=$(clang++-${{ matrix.clang }} -std=c++23 \ + "--gcc-install-dir=$GCC_INSTALL_DIR" -E -x c++ -v /dev/null 2>&1) + entries=$(printf '%s\n' "$search" | awk ' + /search starts here:/ { inside = 1; next } + /End of search list/ { inside = 0 } + inside && NF { print $1 }') + if [ -z "$entries" ]; then + echo "No include search path in the driver output below; the marker Clang prints may have changed." >&2 + printf '%s\n' "$search" >&2 + exit 1 + fi + printf '%s\n' "$entries" + + # Clang prints these relative to the GCC installation, as + # .../13/../../../../include/c++/13, so they are canonicalised before + # being compared. + selected= + while read -r entry; do + resolved=$(readlink -m "$entry") + case "$resolved" in + /usr/include/c++/*) + selected=$resolved + break + ;; + esac + done <<< "$entries" + + if [ "$selected" != "$LIBSTDCXX_HEADERS" ]; then + echo "Expected $LIBSTDCXX_HEADERS to come first, found ${selected:-no standard library headers at all}." >&2 + exit 1 + fi + echo "$selected comes first in the include search path, as pinned." + + # --gcc-install-dir has to reach the link as well as the compile: it + # selects the GCC installation the driver takes its startup files and its + # libstdc++ from. Setting both variables mirrors how the libc++ jobs below + # pass -stdlib. + - name: Configure CMake + run: | + cmake -S . -B build \ + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \ + -DCMAKE_C_COMPILER=clang-${{ matrix.clang }} \ + -DCMAKE_CXX_COMPILER=clang++-${{ matrix.clang }} \ + -DCMAKE_CXX_FLAGS="--gcc-install-dir=$GCC_INSTALL_DIR" \ + -DCMAKE_EXE_LINKER_FLAGS="--gcc-install-dir=$GCC_INSTALL_DIR" + + - name: Build + run: cmake --build build --config ${{ matrix.build_type }} -j"$(nproc)" + + - name: Test + run: ./build/test/dross_test --gtest_color=yes + # The requirements say either standard library works across the whole Clang # range, so libc++ is verified at both ends rather than left to best effort. # Covering only the lower bound would leave the upper end of the declared diff --git a/README.md b/README.md index 100a1ff..d99bdca 100644 --- a/README.md +++ b/README.md @@ -23,19 +23,32 @@ ### Requirements -- **C++23** compatible compiler, verified in CI as: - - Linux: GCC 13–15, or Clang 20–22 with either libstdc++ or libc++ - - macOS: the Apple Clang shipped with macOS 15 or 26 - - Newer versions are best effort: the nightly toolchain watch tracks the - newest versioned GCC available once the toolchain PPA is in place, and - the specific Clang release next in line to enter this range - - Within the declared range, the required Linux jobs build GCC 13/15 and - Clang 20/22, with the Clang jobs pairing against the libstdc++ present - on Ubuntu 26.04 or against libc++. Clang against the older libstdc++ - releases available on Ubuntu 24.04 (13 and 14) is not yet verified; - verification for that combination is to be added, and this sentence - will be removed once it is. GCC 14 and Clang 21 are inside the declared - range but are not built by a required job. +- **A compiler configured for C++23 or later.** The public headers use C++23, + so C++17 and C++20 are outside the supported range. C++26 consumers are best + effort: no required job builds one, so neither compiling these headers as + C++26 nor the ABI and ODR compatibility of linking such a consumer against a + C++23 build of the library is verified. +- **A supported compiler and standard library pairing.** On Linux those are: + - GCC 13–15 with the libstdc++ it is paired with (13, 14 or 15) + - Clang 20–22 with libstdc++ 13, 14 or 15 + - Clang 20–22 with libc++ 20 or 22 + + GCC with libc++ is not one of them, because upstream does not support that + pairing: GCC has no `-stdlib` option to select libc++ with in the first + place. It would be worth revisiting if GCC gained an equivalent option, or + if libc++ started supporting GCC officially. On macOS the compiler is the + Apple Clang shipped with macOS 15 or 26, and the standard library is not a + separate axis there, because libc++ comes with the OS toolchain. +- **What the required jobs build.** GCC 13 and 15, each against the libstdc++ + paired with it, and Clang 20 and 22 against libstdc++ 13, 14 and 15 (15 + being the release Ubuntu 26.04 provides) as well as against libc++ 20 and + 22. Every libstdc++ release in the supported range is therefore covered in + the Clang pairings; among the GCC ones only 13 and 15 are, since the + libstdc++ version follows the compiler version there. GCC 14 and Clang 21 + are inside the declared range but are not built by a required job. Newer + versions are best effort: the nightly toolchain watch tracks the newest + versioned GCC available once the toolchain PPA is in place, and the specific + Clang release next in line to enter this range. - **CMake 3.20+** ### Installation diff --git a/docs/sphinx/source/contributing.rst b/docs/sphinx/source/contributing.rst index 2d668cd..af33921 100644 --- a/docs/sphinx/source/contributing.rst +++ b/docs/sphinx/source/contributing.rst @@ -33,16 +33,31 @@ Development Setup Build Requirements ~~~~~~~~~~~~~~~~~~ -- C++23 compatible compiler: on Linux, GCC 13-15, or Clang 20-22 with either - libstdc++ or libc++; on macOS, the Apple Clang shipped with macOS 15 or 26. - Newer versions are best effort. -- Within that range, the required Linux jobs currently build GCC 13/15 and - Clang 20/22, with Clang built against the libstdc++ present on Ubuntu - 26.04 or against libc++. Clang against the older libstdc++ releases - available on Ubuntu 24.04 (13 and 14) is not yet built by a required job; - verification for that combination is to be added, and this sentence will - be removed once it lands. GCC 14 and Clang 21 are inside the declared - range but are not built by a required job either. +- A compiler configured for C++23 or later. The public headers use C++23, so + C++17 and C++20 are outside the supported range. C++26 consumers are best + effort: no required job builds one, so neither compiling these headers as + C++26 nor the ABI and ODR compatibility of linking such a consumer against a + C++23 build of the library is verified. +- A supported compiler and standard library pairing. On Linux those are: + + - GCC 13-15 with the libstdc++ it is paired with (13, 14 or 15) + - Clang 20-22 with libstdc++ 13, 14 or 15 + - Clang 20-22 with libc++ 20 or 22 + + GCC with libc++ is not one of them, because upstream does not support that + pairing: GCC has no ``-stdlib`` option to select libc++ with in the first + place. It would be worth revisiting if GCC gained an equivalent option, or + if libc++ started supporting GCC officially. On macOS the compiler is the + Apple Clang shipped with macOS 15 or 26, and the standard library is not a + separate axis there, because libc++ comes with the OS toolchain. +- The required Linux jobs build GCC 13/15, each against the libstdc++ paired + with it, and Clang 20/22 against libstdc++ 13, 14 and 15 (15 being the + release Ubuntu 26.04 provides) as well as against libc++ 20 and 22. Every + libstdc++ release in the supported range is therefore covered in the Clang + pairings; among the GCC ones only 13 and 15 are, since the libstdc++ version + follows the compiler version there. GCC 14 and Clang 21 are inside the + declared range but are not built by a required job either. Newer versions + are best effort. - CMake 3.20 or later - Git diff --git a/docs/sphinx/source/getting-started.rst b/docs/sphinx/source/getting-started.rst index 5e3c258..3a39428 100644 --- a/docs/sphinx/source/getting-started.rst +++ b/docs/sphinx/source/getting-started.rst @@ -8,11 +8,34 @@ System Requirements To build and use dross, you need: -- **C++ Compiler**: Supporting C++23 standard - - - On Linux: GCC 13 through 15, or Clang 20 through 22 with either libstdc++ - or libc++ - - On macOS: the Apple Clang shipped with macOS 15 or 26 +- **C++ Standard**: C++23 or later in your own project + + The public headers use C++23, so C++17 and C++20 are outside the supported + range. C++26 consumers are best effort: no required job builds one, so + neither compiling these headers as C++26 nor the ABI and ODR compatibility + of linking such a consumer against a C++23 build of the library is verified. + +- **C++ Compiler and Standard Library**: on Linux, one of these pairings + + - GCC 13 through 15 with the libstdc++ it is paired with (13, 14 or 15) + - Clang 20 through 22 with libstdc++ 13, 14 or 15 + - Clang 20 through 22 with libc++ 20 or 22 + + GCC with libc++ is not one of them, because upstream does not support that + pairing: GCC has no ``-stdlib`` option to select libc++ with in the first + place. It would be worth revisiting if GCC gained an equivalent option, or + if libc++ started supporting GCC officially. On macOS the compiler is the + Apple Clang shipped with macOS 15 or 26, and the standard library is not a + separate axis there, because libc++ comes with the OS toolchain. + + The required build matrix builds GCC 13 and 15, each against the libstdc++ + paired with it, and Clang 20 and 22 (not the intermediate 21) against + libstdc++ 13, 14 and 15 — 15 being the release Ubuntu 26.04 provides — as + well as against libc++ 20 and 22. Every libstdc++ release in the supported + range is therefore covered in the Clang pairings; among the GCC ones only 13 + and 15 are, since the libstdc++ version follows the compiler version there. + GCC 14 and Clang 21 sit inside the declared range the same way, without a + required job of their own. Newer versions are best effort: GCC is exercised by the nightly toolchain watch tracking the newest versioned GCC available once the toolchain PPA @@ -20,17 +43,12 @@ To build and use dross, you need: next in line to enter this range, rather than by the required build matrix. - The Clang lower bound is higher than the GCC one because older Clang - releases cannot compile this library's C++23 ``std::expected`` usage against - the libstdc++ they are paired with on Ubuntu 24.04 — installing the - libstdc++ 14 headers alongside them does not change that either. From Clang - 20 onwards both standard libraries are supported, but the required build - matrix currently exercises only Clang 20 and 22 (not the intermediate 21), - and only against the libstdc++ present on Ubuntu 26.04 or against libc++ - — not against the older libstdc++ releases available on Ubuntu 24.04 - (13 and 14). Verification for that lower-bound combination is to be added; - this paragraph will be trimmed once it is. GCC 14 and Clang 21 sit inside - the declared range the same way, without a required job of their own. + The Clang lower bound is higher than the GCC one because Clang releases + older than 20 cannot compile this library's C++23 ``std::expected`` usage + against the libstdc++ they are paired with on Ubuntu 24.04 — and installing + the libstdc++ 14 headers alongside those older releases does not change that + either. From Clang 20 onwards both standard libraries work, which is why + every pairing listed above starts there. - **Build System**: CMake 3.20 or later - **Operating System**: Linux or macOS From 21179c4098b93c15e79e385e986c98c094454bd0 Mon Sep 17 00:00:00 2001 From: Yuma Endo Date: Fri, 14 Aug 2026 16:28:28 +0900 Subject: [PATCH 2/4] ci: keep the pin check's failures and the default's selection visible The check added with the pinned jobs dies without printing anything when the driver refuses the directory it was given: the assignment fails under set -e with the driver's output already captured, so a run keeps only its exit code. Report it instead. The awk there also took the first field of each search path entry, which turns a path containing a space into a wrong comparison rather than an error. The default jobs name no standard library at all, and Clang picks the newest GCC installation it finds rather than the distribution's alias, so nothing in a run recorded which release that arm built against even though the documents name it. Report the selection there too, without asserting on it -- those jobs exist to build against whatever the image provides, so a change of release is news rather than a failure. The documents now also say that the version in each pairing is the one whose headers are compiled against, the runtime a binary loads being versioned separately, and the configure step names the shared linker variable rather than resting on CMake carrying the flag there on its own. --- .github/workflows/linux.yml | 40 ++++++++++++++++++++------ README.md | 18 ++++++++---- docs/sphinx/source/contributing.rst | 5 ++++ docs/sphinx/source/getting-started.rst | 21 ++++++++------ 4 files changed, 62 insertions(+), 22 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index db2ac09..efb3b18 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -61,9 +61,18 @@ jobs: steps: - uses: actions/checkout@v7 + # The standard library is left to the image here, so the only record of + # which one a run used is this log. Clang selects the newest GCC + # installation it finds rather than the distribution's default alias, so + # that record cannot be derived from the image name either. Reported, not + # asserted on: these two jobs exist to build against whatever the image + # provides, so a change of release is news, not a failure. - name: Report toolchain versions run: | clang-${{ matrix.toolchain.clang }} --version + clang++-${{ matrix.toolchain.clang }} -std=c++23 -E -x c++ -v /dev/null 2>&1 \ + | grep -m1 'Selected GCC installation:' \ + || echo "(the driver reported no GCC installation selection)" cmake --version # No -stdlib override: the default standard library is what most Clang @@ -125,8 +134,10 @@ jobs: # This job's name claims a specific libstdc++, and a green build is not # evidence for that claim: were --gcc-install-dir to resolve to another # release than the one pinned, the build would still succeed, just not - # against what the name says. So the include search path is read back and - # its first standard library entry is compared against the pinned release. + # against what the name says. So the include search path is read back, and + # the first entry in it that canonicalises under /usr/include/c++ — the + # one the standard library headers are taken from — is compared against + # the pinned release. No other entry is examined. # # The directory check and the search path check do not subsume each other: # the directory comes from libgcc-N-dev while the headers come from @@ -146,12 +157,20 @@ jobs: exit 1 fi - search=$(clang++-${{ matrix.clang }} -std=c++23 \ - "--gcc-install-dir=$GCC_INSTALL_DIR" -E -x c++ -v /dev/null 2>&1) + # The driver's own output is the only diagnosis available when it + # refuses the pinned directory — a GCC installation it will not accept + # exists as far as the check above is concerned — so print it rather + # than letting the failed assignment end the step in silence. + if ! search=$(clang++-${{ matrix.clang }} -std=c++23 \ + "--gcc-install-dir=$GCC_INSTALL_DIR" -E -x c++ -v /dev/null 2>&1); then + echo "The driver failed with --gcc-install-dir=$GCC_INSTALL_DIR; its output follows." >&2 + printf '%s\n' "$search" >&2 + exit 1 + fi entries=$(printf '%s\n' "$search" | awk ' /search starts here:/ { inside = 1; next } /End of search list/ { inside = 0 } - inside && NF { print $1 }') + inside && NF { sub(/^[ \t]+/, ""); print }') if [ -z "$entries" ]; then echo "No include search path in the driver output below; the marker Clang prints may have changed." >&2 printf '%s\n' "$search" >&2 @@ -181,8 +200,12 @@ jobs: # --gcc-install-dir has to reach the link as well as the compile: it # selects the GCC installation the driver takes its startup files and its - # libstdc++ from. Setting both variables mirrors how the libc++ jobs below - # pass -stdlib. + # libstdc++ from. CMAKE_CXX_FLAGS alone already carries it to both link + # lines — the build file CMake writes puts it on the shared library's and + # on the test executable's — but that is CMake filling in language flags + # for its own reasons, not a promise about linking. Naming the linker + # variables states the requirement instead of resting on it, the same + # shape in which the libc++ jobs below pass -stdlib. - name: Configure CMake run: | cmake -S . -B build \ @@ -190,7 +213,8 @@ jobs: -DCMAKE_C_COMPILER=clang-${{ matrix.clang }} \ -DCMAKE_CXX_COMPILER=clang++-${{ matrix.clang }} \ -DCMAKE_CXX_FLAGS="--gcc-install-dir=$GCC_INSTALL_DIR" \ - -DCMAKE_EXE_LINKER_FLAGS="--gcc-install-dir=$GCC_INSTALL_DIR" + -DCMAKE_EXE_LINKER_FLAGS="--gcc-install-dir=$GCC_INSTALL_DIR" \ + -DCMAKE_SHARED_LINKER_FLAGS="--gcc-install-dir=$GCC_INSTALL_DIR" - name: Build run: cmake --build build --config ${{ matrix.build_type }} -j"$(nproc)" diff --git a/README.md b/README.md index d99bdca..ff5b365 100644 --- a/README.md +++ b/README.md @@ -33,19 +33,25 @@ - Clang 20–22 with libstdc++ 13, 14 or 15 - Clang 20–22 with libc++ 20 or 22 + The version in each pairing is the version of the standard library headers + the compiler builds against. The shared runtime a resulting binary loads + comes from the system's own runtime package, which is versioned and updated + separately. + GCC with libc++ is not one of them, because upstream does not support that pairing: GCC has no `-stdlib` option to select libc++ with in the first place. It would be worth revisiting if GCC gained an equivalent option, or if libc++ started supporting GCC officially. On macOS the compiler is the Apple Clang shipped with macOS 15 or 26, and the standard library is not a separate axis there, because libc++ comes with the OS toolchain. -- **What the required jobs build.** GCC 13 and 15, each against the libstdc++ - paired with it, and Clang 20 and 22 against libstdc++ 13, 14 and 15 (15 - being the release Ubuntu 26.04 provides) as well as against libc++ 20 and - 22. Every libstdc++ release in the supported range is therefore covered in - the Clang pairings; among the GCC ones only 13 and 15 are, since the +- **What the required Linux jobs build.** GCC 13 and 15, each against the + libstdc++ paired with it, and Clang 20 and 22 against libstdc++ 13, 14 and + 15 (15 being the release Ubuntu 26.04 provides) as well as against libc++ 20 + and 22. Every libstdc++ release in the supported range is therefore covered + in the Clang pairings; among the GCC ones only 13 and 15 are, since the libstdc++ version follows the compiler version there. GCC 14 and Clang 21 - are inside the declared range but are not built by a required job. Newer + are inside the declared range but are not built by a required job. On macOS + the required jobs build with the Apple Clang of macOS 15 and 26. Newer versions are best effort: the nightly toolchain watch tracks the newest versioned GCC available once the toolchain PPA is in place, and the specific Clang release next in line to enter this range. diff --git a/docs/sphinx/source/contributing.rst b/docs/sphinx/source/contributing.rst index af33921..edf1de2 100644 --- a/docs/sphinx/source/contributing.rst +++ b/docs/sphinx/source/contributing.rst @@ -44,6 +44,11 @@ Build Requirements - Clang 20-22 with libstdc++ 13, 14 or 15 - Clang 20-22 with libc++ 20 or 22 + The version in each pairing is the version of the standard library headers + the compiler builds against. The shared runtime a resulting binary loads + comes from the system's own runtime package, which is versioned and updated + separately. + GCC with libc++ is not one of them, because upstream does not support that pairing: GCC has no ``-stdlib`` option to select libc++ with in the first place. It would be worth revisiting if GCC gained an equivalent option, or diff --git a/docs/sphinx/source/getting-started.rst b/docs/sphinx/source/getting-started.rst index 3a39428..4449cb8 100644 --- a/docs/sphinx/source/getting-started.rst +++ b/docs/sphinx/source/getting-started.rst @@ -21,6 +21,11 @@ To build and use dross, you need: - Clang 20 through 22 with libstdc++ 13, 14 or 15 - Clang 20 through 22 with libc++ 20 or 22 + The version in each pairing is the version of the standard library headers + the compiler builds against. The shared runtime a resulting binary loads + comes from the system's own runtime package, which is versioned and updated + separately. + GCC with libc++ is not one of them, because upstream does not support that pairing: GCC has no ``-stdlib`` option to select libc++ with in the first place. It would be worth revisiting if GCC gained an equivalent option, or @@ -28,14 +33,14 @@ To build and use dross, you need: Apple Clang shipped with macOS 15 or 26, and the standard library is not a separate axis there, because libc++ comes with the OS toolchain. - The required build matrix builds GCC 13 and 15, each against the libstdc++ - paired with it, and Clang 20 and 22 (not the intermediate 21) against - libstdc++ 13, 14 and 15 — 15 being the release Ubuntu 26.04 provides — as - well as against libc++ 20 and 22. Every libstdc++ release in the supported - range is therefore covered in the Clang pairings; among the GCC ones only 13 - and 15 are, since the libstdc++ version follows the compiler version there. - GCC 14 and Clang 21 sit inside the declared range the same way, without a - required job of their own. + The required Linux build matrix builds GCC 13 and 15, each against the + libstdc++ paired with it, and Clang 20 and 22 (not the intermediate 21) + against libstdc++ 13, 14 and 15 — 15 being the release Ubuntu 26.04 + provides — as well as against libc++ 20 and 22. Every libstdc++ release in + the supported range is therefore covered in the Clang pairings; among the + GCC ones only 13 and 15 are, since the libstdc++ version follows the + compiler version there. GCC 14 and Clang 21 sit inside the declared range + the same way, without a required job of their own. Newer versions are best effort: GCC is exercised by the nightly toolchain watch tracking the newest versioned GCC available once the toolchain PPA From c0f3c81fe53db3c7ee2d0c516fbad3e35f02b8f4 Mon Sep 17 00:00:00 2001 From: Yuma Endo Date: Fri, 14 Aug 2026 17:01:58 +0900 Subject: [PATCH 3/4] ci: report what the driver said when it cannot report the selection The observation line added with the pinned jobs filtered the driver's output through grep, so a driver that failed left nothing behind: its diagnostics went into the pipe and were dropped, and the fallback text claimed the driver had reported no selection -- a statement about what the driver said, made in the one case where nothing it said survived. That is the same disappearance this branch had just removed from the pinned check, reintroduced one step away. Capture first, then read the capture. Whenever the marker cannot be reported, whether because the driver failed or because it stopped printing that line, the captured output reaches the log instead of being dropped. Reading the capture with a here-string rather than a pipe also removes the step's dependence on pipefail being unset, which is what the current behaviour rests on. Two comments elsewhere claimed more than the code does. The configure step called its linker variables the same shape the libc++ jobs use, though those name no shared-linker variable. The pin check said no other entry is examined, when what it means is that the entries are walked in order and only the first one under the standard library prefix is compared. --- .github/workflows/linux.yml | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index efb3b18..5961ca3 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -67,12 +67,25 @@ jobs: # that record cannot be derived from the image name either. Reported, not # asserted on: these two jobs exist to build against whatever the image # provides, so a change of release is news, not a failure. + # + # The driver's output is captured before it is filtered. Whenever the + # marker cannot be reported — the driver failed, or it stopped printing + # that line — the captured output goes to the log rather than being + # dropped, so the log never claims the driver said nothing when it said + # something else. Reading the capture directly also leaves no pipeline + # whose exit status would turn on whether pipefail is set, which this + # workflow does not set. No branch fails the step. - name: Report toolchain versions run: | clang-${{ matrix.toolchain.clang }} --version - clang++-${{ matrix.toolchain.clang }} -std=c++23 -E -x c++ -v /dev/null 2>&1 \ - | grep -m1 'Selected GCC installation:' \ - || echo "(the driver reported no GCC installation selection)" + if ! probe=$(clang++-${{ matrix.toolchain.clang }} -std=c++23 \ + -E -x c++ -v /dev/null 2>&1); then + printf '%s\n' "$probe" + echo "(the driver exited non-zero; no GCC installation selection recorded)" + elif ! grep -m1 'Selected GCC installation:' <<< "$probe"; then + printf '%s\n' "$probe" + echo "(the driver reported no GCC installation selection)" + fi cmake --version # No -stdlib override: the default standard library is what most Clang @@ -134,10 +147,10 @@ jobs: # This job's name claims a specific libstdc++, and a green build is not # evidence for that claim: were --gcc-install-dir to resolve to another # release than the one pinned, the build would still succeed, just not - # against what the name says. So the include search path is read back, and - # the first entry in it that canonicalises under /usr/include/c++ — the - # one the standard library headers are taken from — is compared against - # the pinned release. No other entry is examined. + # against what the name says. So the include search path is read back and + # its entries are canonicalised in order until one of them falls under + # /usr/include/c++. That one is where the standard library headers come + # from, and it is the only entry compared against the pinned release. # # The directory check and the search path check do not subsume each other: # the directory comes from libgcc-N-dev while the headers come from @@ -204,8 +217,9 @@ jobs: # lines — the build file CMake writes puts it on the shared library's and # on the test executable's — but that is CMake filling in language flags # for its own reasons, not a promise about linking. Naming the linker - # variables states the requirement instead of resting on it, the same - # shape in which the libc++ jobs below pass -stdlib. + # variables states the requirement instead of resting on it. The libc++ + # jobs below name CMAKE_EXE_LINKER_FLAGS for that same reason; they set no + # CMAKE_SHARED_LINKER_FLAGS, so the two are alike but not identical. - name: Configure CMake run: | cmake -S . -B build \ From 48a63fee6fc8d308640107c7a5854a1306415a33 Mon Sep 17 00:00:00 2001 From: Yuma Endo Date: Fri, 14 Aug 2026 17:25:55 +0900 Subject: [PATCH 4/4] ci: narrow two comments to what a reader can check One of them said this workflow does not set pipefail, while the pinned check sets it eighty lines further down. The claim holds for the step, whose default shell is the one that has no pipefail, and not for the file; shell options do not carry between steps, so narrowing the scope keeps the reasoning and drops the part a reader would find contradicted. The other explained why the libc++ jobs name their linker variable. That is an attribution those jobs do not make anywhere; their comments cover why both ends are built and where the packages come from. What can be read off them is which variables they set, so the comment now says that and stops there. --- .github/workflows/linux.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 5961ca3..614ee44 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -73,8 +73,8 @@ jobs: # that line — the captured output goes to the log rather than being # dropped, so the log never claims the driver said nothing when it said # something else. Reading the capture directly also leaves no pipeline - # whose exit status would turn on whether pipefail is set, which this - # workflow does not set. No branch fails the step. + # whose exit status would hinge on whether pipefail is set, which this + # step does not set. No branch fails the step. - name: Report toolchain versions run: | clang-${{ matrix.toolchain.clang }} --version @@ -218,8 +218,8 @@ jobs: # on the test executable's — but that is CMake filling in language flags # for its own reasons, not a promise about linking. Naming the linker # variables states the requirement instead of resting on it. The libc++ - # jobs below name CMAKE_EXE_LINKER_FLAGS for that same reason; they set no - # CMAKE_SHARED_LINKER_FLAGS, so the two are alike but not identical. + # jobs below set CMAKE_EXE_LINKER_FLAGS as well; they set no + # CMAKE_SHARED_LINKER_FLAGS. - name: Configure CMake run: | cmake -S . -B build \