ci: pin mcpp 2026.8.25.1 #79
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. | |
| # | |
| # The claim this package makes is that a C library ported once onto openkal runs | |
| # above every implementation of the specification, so the claim is tested by | |
| # running the same two programs on three systems and comparing what they produce | |
| # against what the system's own tools produce. | |
| # | |
| # posix a program that opens a file by an absolute name, reads a | |
| # variable, measures an interval, starts another program and waits | |
| # for it --- thirty-two observations, none of which the program | |
| # contains an implementation of | |
| # wordcount the same counts as the system's own `wc' | |
| # | |
| # Two compiler families, gcc and llvm. The third of the three mcpp offers is | |
| # absent from this package and the reason is recorded in the manifest: musl's | |
| # four remaining assembly definitions are in an object format that toolchain | |
| # does not assemble, so the question that toolchain would answer is not one this | |
| # package can ask. | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| workflow_dispatch: | |
| inputs: | |
| mcpp_ref: | |
| description: "Branch of mcpp-community/mcpp to build and test against (empty = the released pin)" | |
| required: false | |
| default: "" | |
| env: | |
| MCPP_SOURCE_REF: ${{ github.event.inputs.mcpp_ref || vars.MCPP_SOURCE_REF }} | |
| MCPP_VERSION: 2026.8.25.2 | |
| XLINGS_VERSION: v2026.8.17.2 | |
| XLINGS_NON_INTERACTIVE: '1' | |
| jobs: | |
| programs: | |
| name: ${{ matrix.name }} | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 90 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - { name: 'linux, gcc', os: ubuntu-24.04, toolchain: 'gcc@16.1.0', target: '' } | |
| - { name: 'linux, llvm', os: ubuntu-24.04, toolchain: 'llvm@22.1.8', target: '' } | |
| - { name: 'macos, llvm', os: macos-14, toolchain: 'llvm@20.1.7', target: '' } | |
| - { name: 'windows, gcc', os: windows-2022, toolchain: 'gcc@16.1.0', target: 'x86_64-windows-gnu' } | |
| defaults: | |
| run: | |
| shell: bash | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: The specification and the implementation for this system | |
| run: | | |
| bash tools/working-trees.sh '${{ github.head_ref || github.ref_name }}' \ | |
| '${{ matrix.target }}' | |
| - name: Install xlings (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 (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 | |
| # ⚠️ THE PIN MAY NAME THE RELEASE THIS RUN IS VALIDATING, which does | |
| # not exist yet — that is the whole point of MCPP_SOURCE_REF. Bootstrap | |
| # from whatever the index has; the step below replaces it with the | |
| # build under review, and the pin is what an ordinary run tests. | |
| if [ -n "${MCPP_SOURCE_REF:-}" ]; then | |
| xlings install mcpp -y -g | |
| else | |
| xlings install "mcpp@$MCPP_VERSION" -y -g | |
| fi | |
| mcpp --version | |
| mcpp self config --mirror GLOBAL | |
| # ⭐⭐ CROSS-VALIDATION: BUILD THE mcpp UNDER REVIEW AND USE THAT ONE. | |
| # | |
| # Empty in the ordinary run, so this job keeps testing the RELEASED | |
| # mcpp the pin above names. Set it — `workflow_dispatch` input, or the | |
| # repository variable — and the same job runs against that source. | |
| # | |
| # ⚠️ THIS EXISTS BECAUSE THE ORDER USED TO BE WRONG. Several mcpp | |
| # releases went out green and only then turned this ecosystem red: the | |
| # engine's own CI cannot see a defect that appears only in a real | |
| # dependency graph, and this repository could not see the engine until | |
| # it had been published. Validating before the release closes that gap. | |
| # | |
| # The released mcpp installed just above is the bootstrap that compiles | |
| # it; mcpp builds itself and there is no other compiler for it here. | |
| if [ -n "${MCPP_SOURCE_REF:-}" ]; then | |
| src="$RUNNER_TEMP/mcpp-src" | |
| [ -d "$src" ] || git clone --quiet --depth 1 \ | |
| --branch "$MCPP_SOURCE_REF" \ | |
| https://github.com/mcpp-community/mcpp.git "$src" | |
| # ⚠️ THE CLONE'S OWN WORKSPACE PIN MUST NOT DECIDE WHICH mcpp | |
| # BUILDS IT. `.xlings.json` at mcpp's root pins the mcpp that | |
| # compiles mcpp, and that pin does not move when mcpp is released — | |
| # so a build inside the checkout obeys it and tries to install a | |
| # version the index may no longer carry: | |
| # | |
| # [error] xlings: version '2026.8.17.1' not found for 'mcpp' | |
| # available: 2026.8.25.1 | |
| # | |
| # What is wanted here is the source compiled by the mcpp installed | |
| # above, which is exactly what removing the file leaves. | |
| rm -f "$src/.xlings.json" | |
| ( cd "$src" && mcpp build --release ) | |
| built=$(find "$src/target" -type f -name mcpp -perm -u+x | head -1) | |
| [ -n "$built" ] || { echo "::error::mcpp did not build from $MCPP_SOURCE_REF"; exit 1; } | |
| echo "$(cd "$(dirname "$built")" && pwd)" >> "$GITHUB_PATH" | |
| # ⚠️ Reported, because a PATH entry that does not win looks exactly | |
| # like one that does until something built with the wrong engine. | |
| echo "under review: $("$built" --version) (from $MCPP_SOURCE_REF)" | |
| fi | |
| - name: Select the toolchain | |
| run: | | |
| spec='${{ matrix.toolchain }}' | |
| mcpp toolchain install "${spec%@*}" "${spec#*@}" | |
| mcpp toolchain default "$spec" | |
| mcpp toolchain list | |
| - name: The library builds | |
| run: | | |
| extra='' | |
| [ -n '${{ matrix.target }}' ] && extra='--target ${{ matrix.target }}' | |
| mcpp build $extra | |
| # ⭐ AN OPTIONAL INTERFACE IS REFERENCED WEAKLY, WHICH IS A PROPERTY OF | |
| # THE OBJECT AND NOT OF ANY ONE LINK. | |
| # | |
| # `openkal.random` is optional, and clause 6.1 expresses an | |
| # implementation that does not provide it as the absence of a link-time | |
| # definition. This dispatcher is linked into every program, so a strong | |
| # reference to `kal_random_fill` makes an optional interface mandatory: | |
| # every program over a backend without it fails to link, whether or not | |
| # it wanted a random byte. | |
| # | |
| # ⚠️ THAT DEFECT SHIPPED, AND NOTHING HERE SAW IT. It was found one | |
| # repository downstream, by a bare-metal program in openkal-llvm-runtime | |
| # linking over openkal-opensbi: | |
| # | |
| # ld.lld: error: undefined symbol: kal_random_fill | |
| # | |
| # Reproducing it here would need a bare-metal toolchain and a backend | |
| # that declines the interface. The symbol class needs neither, and states | |
| # the invariant more directly than a link would: `w` for the optional | |
| # one, `U` for a required one beside it. | |
| - name: An optional interface is referenced weakly | |
| if: runner.os == 'Linux' | |
| run: | | |
| obj="$(find target -name okm_syscall.o | head -1)" | |
| test -n "$obj" || { echo "::error::okm_syscall.o was not built"; exit 1; } | |
| nm="$(command -v llvm-nm || command -v nm)" | |
| "$nm" "$obj" > syms.txt | |
| # The control: a required interface must still be a strong reference, | |
| # so that a backend failing to provide one is still a link error. | |
| grep -qE '^ *U kal_time_sleep$' syms.txt \ | |
| || { echo "::error::kal_time_sleep is not an undefined strong reference — the check below proves nothing" | |
| grep kal_time syms.txt; exit 1; } | |
| grep -qE '^ *w kal_random_fill$' syms.txt \ | |
| || { echo "::error::kal_random_fill is not a weak reference; an optional interface has been made mandatory" | |
| grep kal_random syms.txt; exit 1; } | |
| echo " ok kal_random_fill is weak, kal_time_sleep is strong" | |
| # A program above this package names one package. It does not name | |
| # openkal, it does not name an implementation, and it says nothing about | |
| # the platform. | |
| - name: The posix probe | |
| working-directory: examples/posix | |
| run: | | |
| extra='' | |
| [ -n '${{ matrix.target }}' ] && extra='--target ${{ matrix.target }}' | |
| mcpp build $extra | |
| binary="$(find target -type f \( -name 'posix' -o -name 'posix.exe' \) | head -1)" | |
| # A watchdog, because a program that does not return is as much a | |
| # failure as one that returns wrongly, and the job would otherwise | |
| # spend its whole timeout finding that out. Written out rather than | |
| # taken from `timeout', which two of the three systems have and one | |
| # does not. | |
| watch() { # watch <seconds> <command>... | |
| local seconds="$1"; shift | |
| "$@" & local pid=$! | |
| ( sleep "$seconds"; kill -9 "$pid" 2> /dev/null ) & local guard=$! | |
| wait "$pid"; local status=$? | |
| kill "$guard" 2> /dev/null || true | |
| return $status | |
| } | |
| if watch 120 sh -c "\"$binary\" > run.log 2>&1"; then | |
| cat run.log | |
| else | |
| status=$? | |
| echo "--- what the program printed before it stopped (status $status) ---" | |
| cat run.log | |
| # A program that stopped and a program that did not return need | |
| # different questions asked of them, and "exit code 139" and "the | |
| # job timed out" answer neither. The debugger is for the first; a | |
| # stack sample of a program that is still running is for the second, | |
| # and a debugger asked to run a program that hangs hangs with it. | |
| if [ "$status" -eq 137 ]; then | |
| echo "--- it did not return; where it was ---" | |
| "$binary" > /dev/null 2>&1 & hung=$! | |
| sleep 5 | |
| if command -v sample > /dev/null 2>&1; then | |
| sample "$hung" 3 -mayDie 2>&1 | head -80 || true | |
| elif command -v eu-stack > /dev/null 2>&1; then | |
| eu-stack -p "$hung" 2>&1 | head -60 || true | |
| fi | |
| kill -9 "$hung" 2> /dev/null || true | |
| elif command -v lldb > /dev/null 2>&1; then | |
| watch 90 lldb --batch -o run \ | |
| -k 'thread backtrace all' -k 'register read' -k quit \ | |
| -- "$binary" > crash.log 2>&1 || true | |
| cat crash.log | |
| elif command -v gdb > /dev/null 2>&1; then | |
| watch 90 gdb -batch -ex run -ex 'bt' --args "$binary" > crash.log 2>&1 || true | |
| cat crash.log | |
| fi | |
| exit 1 | |
| fi | |
| # Both directions: that the program reported, and that nothing it | |
| # observed failed to hold. Asserting only the first would pass for a | |
| # program that printed its failures. | |
| grep -qE '^-- failures: 0 --$' run.log | |
| ! grep -q '^FAIL:' run.log | |
| - name: The same program, built the ordinary way, as a control | |
| run: | | |
| # ⚠️ THIS STEP EXISTS BECAUSE IT ALREADY EARNED ITS PLACE. | |
| # | |
| # The cross-built copy failed on a machine of the other system, and the | |
| # obvious reading was a defect in this port or in the way it was | |
| # cross-built. A native build of the SAME source reproduced it here --- | |
| # which said it was neither, and the defect was in the program's own | |
| # assertion macro, which evaluated its condition twice and so closed a | |
| # stream that was already closed. | |
| # | |
| # Two failures that look alike are told apart by building the same | |
| # source two ways, and that is cheap enough to do every time. | |
| # ⚠️ The target has to be named here as it is everywhere else in this | |
| # job: one system needs it, and a step that omitted it built for that | |
| # system's other toolchain and failed at the link with names kernel32 | |
| # supplies. Measured, on the first run of this step. | |
| extra='' | |
| [ -n '${{ matrix.target }}' ] && extra='--target ${{ matrix.target }}' | |
| cd examples/cross-hello | |
| mcpp build $extra | |
| binary="$(find target -type f \( -name 'cross-hello' -o -name 'cross-hello.exe' \) | head -1)" | |
| "./$binary" 2>&1 | tee out.log | |
| grep -q -- '-- failures: 0 --' out.log | |
| - name: The counts agree with the system's own | |
| working-directory: examples/wordcount | |
| run: | | |
| printf 'alpha beta\ngamma\n' > sample.txt | |
| expected="$(wc < sample.txt | tr -s ' ' | sed 's/^ //')" | |
| extra='' | |
| [ -n '${{ matrix.target }}' ] && extra='--target ${{ matrix.target }}' | |
| mcpp build $extra | |
| binary="$(find target -type f \( -name 'wordcount' -o -name 'wordcount.exe' \) | head -1)" | |
| WORDCOUNT_VERBOSE=1 "$binary" sample.txt 2>&1 | tee run.log | |
| counts="$(sed -n 's/^lines \([0-9]*\) words \([0-9]*\) bytes \([0-9]*\)$/\1 \2 \3/p' run.log)" | |
| test -n "$counts" || { echo "the program printed no count" >&2; exit 1; } | |
| echo "wordcount: $counts" | |
| echo "wc: $expected" | |
| test "$counts" = "$expected" | |
| # The variable was consulted, and the interval was measured. | |
| grep -q 'elapsed .* nanoseconds' run.log | |
| # --------------------------------------------------------------------------- | |
| # The measurement musl/PATCHES.md records, made from a machine that is not the | |
| # system it is about. | |
| # | |
| # It is a job rather than a note because the claim it supports --- that a | |
| # program for that system can be built, and linked, without any file that | |
| # system supplies --- is one that a change to this port could silently undo. | |
| # The two numbers it asserts are zero indirect symbols and exactly two names. | |
| cross-macos: | |
| name: cross-link for the other system, from this one | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: The implementation for that system | |
| run: | | |
| branch='${{ github.head_ref || github.ref_name }}' | |
| git clone --quiet https://github.com/mcpplibs/openkal-macos.git ../openkal-macos | |
| git -C ../openkal-macos checkout --quiet "origin/$branch" 2>/dev/null || true | |
| git clone --quiet https://github.com/mcpplibs/openkal.git ../openkal | |
| git -C ../openkal checkout --quiet "origin/$branch" 2>/dev/null || true | |
| - name: Install LLVM | |
| run: | | |
| sudo apt-get update -qq | |
| sudo apt-get install -y -qq clang lld llvm | |
| # ld64.lld is the Mach-O driver of the same binary; Ubuntu's package | |
| # installs it under a version suffix and without the plain name. | |
| v=$(ls /usr/lib/llvm-*/bin/ld64.lld 2>/dev/null | sort -V | tail -1) | |
| test -n "$v" || { echo "::error::this image has no ld64.lld"; exit 1; } | |
| d=$(dirname "$v") | |
| echo "LD64=$v" >> "$GITHUB_ENV" | |
| # The tools are installed under a version suffix and the probe asks for | |
| # them by their plain names, so the directory that has both spellings | |
| # is named rather than assumed. | |
| echo "NM=$d/llvm-nm" >> "$GITHUB_ENV" | |
| echo "$d" >> "$GITHUB_PATH" | |
| "$d/llvm-nm" --version | head -1 | |
| clang --version | head -1 | |
| - name: Zero indirect symbols, and two names | |
| run: | | |
| bash tools/probe-cross-macos.sh aarch64 | |
| bash tools/probe-cross-macos.sh x86_64 | |
| # ⚠️ LINKING IS NOT RUNNING, AND THIS IS WHERE THAT IS ADMITTED. | |
| # | |
| # Everything above happens on a machine that is not the system in | |
| # question, and everything above is about the objects and the link. Whether | |
| # the result STARTS is a different question, and it cannot be asked here: | |
| # that system's 64-bit ARM variant refuses to start an image that is not | |
| # signed, and the signature can only be made on a machine of that system. | |
| # | |
| # So the artifact is carried there. The job that receives it is the only | |
| # place in this workflow where the claim becomes "a program built here | |
| # runs there" rather than "a program built here links". | |
| - name: A program for that system, built here | |
| run: | | |
| bash tools/cross-build-macos.sh aarch64 examples/cross-hello/src/main.c cross-hello-arm64 | |
| file cross-hello-arm64 | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: cross-hello-arm64 | |
| path: cross-hello-arm64 | |
| retention-days: 1 | |
| # --------------------------------------------------------------------------- | |
| # The other half of the claim, on a machine of that system. | |
| cross-macos-run: | |
| name: what was cross-built starts, on that system | |
| needs: cross-macos | |
| runs-on: macos-14 | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/download-artifact@v4 | |
| with: | |
| name: cross-hello-arm64 | |
| - name: Sign it, because this system will not start it otherwise | |
| run: | | |
| chmod +x cross-hello-arm64 | |
| # Ad hoc: the identity is `-', which asserts nothing about who made | |
| # the program and is exactly what this system requires before it will | |
| # start one on this architecture. A program that is distributed needs | |
| # more; a program that is checked needs this. | |
| codesign -s - --force cross-hello-arm64 | |
| codesign -dv cross-hello-arm64 2>&1 | head -3 | |
| - name: It starts, and the C library beneath it came up | |
| run: | | |
| ./cross-hello-arm64 | tee run.log | |
| grep -q -- '-- failures: 0 --' run.log |