CI #24
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| # What this workflow asserts about the specification. | |
| # | |
| # A specification is a claim about programs, so the claims are tested by | |
| # building and running programs rather than by reading the text. There are four: | |
| # | |
| # declarations the two forms compile, under three compiler families and on | |
| # three systems, and declare the same entities | |
| # substitution a program's source is invariant under a change of | |
| # implementation, and the check that says so fails when it should | |
| # conformance the suite in this repository runs against the implementation | |
| # for each system and every observation holds | |
| # composability an implementation that provides three interfaces is examined | |
| # for three, rather than failing to link | |
| # | |
| # The three compiler families are covered because the specification is a | |
| # contract and a contract that holds only under the compiler its author used is | |
| # a description of that compiler. | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| workflow_dispatch: | |
| env: | |
| # A version verified to build these packages, not a measured minimum. The pin | |
| # exists for reproducibility rather than because an older mcpp is known to | |
| # fail. | |
| MCPP_VERSION: 2026.8.19.4 | |
| XLINGS_VERSION: v2026.8.17.2 | |
| XLINGS_NON_INTERACTIVE: '1' | |
| jobs: | |
| # --------------------------------------------------------------------------- | |
| # The declarations compile, everywhere, in both forms. | |
| declarations: | |
| name: declarations (${{ matrix.os }}, ${{ matrix.toolchain }}) | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 45 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - { os: ubuntu-24.04, toolchain: 'gcc@16.1.0' } | |
| - { os: ubuntu-24.04, toolchain: 'llvm@22.1.8' } | |
| - { os: macos-14, toolchain: 'llvm@20.1.7' } | |
| - { os: windows-2022, toolchain: 'llvm@20.1.7' } | |
| - { os: windows-2022, toolchain: 'msvc@system' } | |
| defaults: | |
| run: | |
| shell: bash | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install xlings and mcpp (Unix) | |
| if: runner.os != 'Windows' | |
| run: | | |
| curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh \ | |
| | bash -s "$XLINGS_VERSION" | |
| echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH" | |
| - name: Install xlings and mcpp (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| irm https://d2learn.org/xlings-install.ps1.txt | iex | |
| # The installer amends the user's environment; a later step in this | |
| # job reads none of it, so the directory is named here. | |
| "$env:USERPROFILE\.xlings\subos\current\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append | |
| - name: Install mcpp | |
| run: | | |
| xlings update | |
| xlings install "mcpp@$MCPP_VERSION" -y -g | |
| mcpp --version | |
| mcpp self config --mirror GLOBAL | |
| # The compiler family and version for this row. mcpp keeps its toolchains | |
| # in a sandbox of its own, so this selects rather than installs into the | |
| # system, and `mcpp test' and `mcpp run' have no flag for it --- which is | |
| # why it is set once here rather than passed to each command. | |
| - name: Select the toolchain | |
| run: | | |
| spec='${{ matrix.toolchain }}' | |
| case "$spec" in | |
| msvc*) mcpp toolchain default msvc ;; | |
| *) mcpp toolchain install "${spec%@*}" "${spec#*@}" | |
| mcpp toolchain default "$spec" ;; | |
| esac | |
| mcpp toolchain list | |
| # The C++ form. The modules are the artefact a C++ consumer imports, and | |
| # building them is what proves the compiler accepts them. | |
| - name: The module form compiles | |
| run: mcpp build | |
| # The C form, with the environment's own headers excluded --- because the | |
| # consumer this form exists for, a C library being ported onto openkal, is | |
| # compiled that way. The tool needs a driver it can pass -nostdinc to; the | |
| # toolchain that has no such spelling compiles the same declarations in | |
| # the translation unit the conformance suite carries, which every row of | |
| # the conformance job below builds. | |
| - name: The C form compiles without the environment's headers | |
| if: runner.os != 'Windows' | |
| run: | | |
| CC=cc bash tools/check-declarations.sh | |
| command -v clang >/dev/null && CC=clang bash tools/check-declarations.sh || true | |
| # --------------------------------------------------------------------------- | |
| # The property the specification exists for. | |
| substitution: | |
| name: substitution holds (${{ matrix.toolchain }}) | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 40 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| toolchain: ['gcc@16.1.0', 'llvm@22.1.8'] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install xlings | |
| run: | | |
| curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh \ | |
| | bash -s "$XLINGS_VERSION" | |
| echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH" | |
| - name: Install mcpp | |
| run: | | |
| xlings update | |
| xlings install "mcpp@$MCPP_VERSION" -y -g | |
| mcpp self config --mirror GLOBAL | |
| # The compiler family and version for this row. mcpp keeps its toolchains | |
| # in a sandbox of its own, so this selects rather than installs into the | |
| # system, and `mcpp test' and `mcpp run' have no flag for it --- which is | |
| # why it is set once here rather than passed to each command. | |
| - name: Select the toolchain | |
| run: | | |
| spec='${{ matrix.toolchain }}' | |
| case "$spec" in | |
| msvc*) mcpp toolchain default msvc ;; | |
| *) mcpp toolchain install "${spec%@*}" "${spec#*@}" | |
| mcpp toolchain default "$spec" ;; | |
| esac | |
| mcpp toolchain list | |
| - name: Substitution holds | |
| working-directory: examples/substitution/app | |
| run: | | |
| # The property under test is that the source is invariant. The | |
| # checksum is taken before the first build and compared after the | |
| # second, so that a change made by either build would be detected. | |
| before="$(sha256sum src/main.cpp | cut -d' ' -f1)" | |
| mcpp build > /dev/null | |
| ./target/*/*/bin/app > with-fd.log 2>&1 | |
| grep -q 'the application produced this line' with-fd.log | |
| sed -i 's|openkal-fd = { path = "../impl-fd" }|openkal-discard = { path = "../impl-discard" }|' mcpp.toml | |
| rm -rf target | |
| mcpp build > /dev/null | |
| ./target/*/*/bin/app > with-discard.log 2>&1 | |
| if grep -q 'the application produced this line' with-discard.log; then | |
| echo "the discarding implementation produced output"; exit 1 | |
| fi | |
| after="$(sha256sum src/main.cpp | cut -d' ' -f1)" | |
| [ "$before" = "$after" ] || { echo "the source changed between builds"; exit 1; } | |
| - name: The surface checker detects an addition | |
| working-directory: examples/substitution/impl-fd | |
| run: | | |
| # The checker is only useful if it fails when it should. An | |
| # unspecified name is introduced, the checker is required to reject | |
| # it, and the name is then removed. | |
| mcpp build > /dev/null | |
| bash ../../../tools/check-surface.sh ../../../SURFACE.txt \ | |
| $(find target -name '*.o' | tr '\n' ' ') | |
| printf 'extern "C" void kal_vendor_extension(void) {}\n' > src/extra.cpp | |
| mcpp build > /dev/null | |
| if bash ../../../tools/check-surface.sh ../../../SURFACE.txt \ | |
| $(find target -name '*.o' | tr '\n' ' '); then | |
| echo "the checker accepted an unspecified name"; exit 1 | |
| fi | |
| rm -f src/extra.cpp | |
| # --------------------------------------------------------------------------- | |
| # The suite, against the implementation for each system. | |
| # | |
| # The implementations are checked out at the branch under test where they have | |
| # one and at their default branch otherwise, so that this job asserts what it | |
| # is for: that the specification as written here and the implementations as | |
| # written there agree today. | |
| conformance: | |
| name: conformance (${{ matrix.implementation }}, ${{ matrix.toolchain }}) | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 60 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - { os: ubuntu-24.04, toolchain: 'gcc@16.1.0', implementation: openkal-linux } | |
| - { os: ubuntu-24.04, toolchain: 'llvm@22.1.8', implementation: openkal-linux } | |
| - { os: macos-14, toolchain: 'llvm@20.1.7', implementation: openkal-macos } | |
| - { os: windows-2022, toolchain: 'llvm@20.1.7', implementation: openkal-windows } | |
| - { os: windows-2022, toolchain: 'msvc@system', implementation: openkal-windows } | |
| defaults: | |
| run: | |
| shell: bash | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: The implementation | |
| run: | | |
| git clone --quiet https://github.com/mcpplibs/${{ matrix.implementation }}.git .impl | |
| branch='${{ github.head_ref || github.ref_name }}' | |
| if git -C .impl rev-parse --verify --quiet "origin/$branch" > /dev/null; then | |
| git -C .impl checkout --quiet "origin/$branch" | |
| echo "the implementation is at $branch $(git -C .impl rev-parse --short HEAD)" | |
| else | |
| echo "the implementation has no $branch; its default branch is used" \ | |
| "($(git -C .impl rev-parse --short HEAD))" | |
| fi | |
| # Whether the two are describing the same version of the contract. | |
| # | |
| # This job asserts that the specification as written here and the | |
| # implementation as written there agree today, and that assertion is | |
| # only meaningful when the implementation is written against this | |
| # version. During a release across five repositories they are out of | |
| # step for the seconds between one merge and the next, and the | |
| # compiler's report on that is a hundred errors about names a header | |
| # no longer has --- which names the symptom and not the cause. | |
| here="$(sed -n 's/^version[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' mcpp.toml | head -1)" | |
| there="$(sed -n 's/^openkal[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' .impl/mcpp.toml | head -1)" | |
| if [ "$here" != "$there" ]; then | |
| echo "::error::this is openkal $here and ${{ matrix.implementation }} is written against openkal $there. Nothing is wrong with either; they are not in step. Re-run after the implementation's branch has the matching version." >&2 | |
| exit 1 | |
| fi | |
| echo "both describe openkal $here" | |
| - name: Install xlings and mcpp (Unix) | |
| if: runner.os != 'Windows' | |
| run: | | |
| curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh \ | |
| | bash -s "$XLINGS_VERSION" | |
| echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH" | |
| - name: Install xlings and mcpp (Windows) | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| irm https://d2learn.org/xlings-install.ps1.txt | iex | |
| "$env:USERPROFILE\.xlings\subos\current\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append | |
| - name: Install mcpp | |
| run: | | |
| xlings update | |
| xlings install "mcpp@$MCPP_VERSION" -y -g | |
| mcpp self config --mirror GLOBAL | |
| # The compiler family and version for this row. mcpp keeps its toolchains | |
| # in a sandbox of its own, so this selects rather than installs into the | |
| # system, and `mcpp test' and `mcpp run' have no flag for it --- which is | |
| # why it is set once here rather than passed to each command. | |
| - name: Select the toolchain | |
| run: | | |
| spec='${{ matrix.toolchain }}' | |
| case "$spec" in | |
| msvc*) mcpp toolchain default msvc ;; | |
| *) mcpp toolchain install "${spec%@*}" "${spec#*@}" | |
| mcpp toolchain default "$spec" ;; | |
| esac | |
| mcpp toolchain list | |
| # Every interface and every kind of examination. The exit status is the | |
| # verdict: 0 when every observation held, 1 when one did not, and 2 when | |
| # nothing was observed --- the last being the outcome a run that selected | |
| # no interface would otherwise pass silently. | |
| - name: Every interface, every kind of examination | |
| run: | | |
| bash tools/run-conformance.sh '${{ matrix.implementation }}' .impl full | |
| # The suite is composable because openkal is: an implementation provides | |
| # an interface in whole or not at all, and a suite that examined all eight | |
| # unconditionally would fail to link against a conforming implementation | |
| # of three. Selecting three is therefore asserted to produce a report | |
| # rather than a link failure. | |
| - name: A selection of three interfaces is examined, not refused | |
| run: | | |
| rm -rf conformance/target | |
| bash tools/run-conformance.sh '${{ matrix.implementation }}' .impl core,fs,task \ | |
| | tee selected.log | |
| # A report was produced, and nothing in it failed. | |
| grep -qE 'observations: [0-9]+ held, 0 did not hold' selected.log | |
| # An interface that was not selected is reported as not examined and | |
| # carries the reason, rather than being absent --- a report that | |
| # omitted it could not be distinguished from a report on an | |
| # implementation that provides it. | |
| grep -q 'openkal.process --- the interface was not selected' selected.log | |
| # And an interface that was selected was examined. | |
| grep -qE 'held +\[behaviour\].*' selected.log |