Skip to content

ci: pin mcpp 2026.8.25.1 #79

ci: pin mcpp 2026.8.25.1

ci: pin mcpp 2026.8.25.1 #79

Workflow file for this run

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