diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 8bfd736..614ee44 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -61,9 +61,31 @@ 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. + # + # 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 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 + 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 @@ -81,6 +103,139 @@ 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 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 + # 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 + + # 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 { 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 + 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. 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 libc++ + # jobs below set CMAKE_EXE_LINKER_FLAGS as well; they set no + # CMAKE_SHARED_LINKER_FLAGS. + - 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" \ + -DCMAKE_SHARED_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..ff5b365 100644 --- a/README.md +++ b/README.md @@ -23,19 +23,38 @@ ### 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 + + 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 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. 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. - **CMake 3.20+** ### Installation diff --git a/docs/sphinx/source/contributing.rst b/docs/sphinx/source/contributing.rst index 2d668cd..edf1de2 100644 --- a/docs/sphinx/source/contributing.rst +++ b/docs/sphinx/source/contributing.rst @@ -33,16 +33,36 @@ 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 + + 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. +- 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..4449cb8 100644 --- a/docs/sphinx/source/getting-started.rst +++ b/docs/sphinx/source/getting-started.rst @@ -8,11 +8,39 @@ 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 + + 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. + + 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 @@ -20,17 +48,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