Skip to content

0.7.0 — adopt openkal 0.9, and make the aarch64 leg work for the firs… #76

0.7.0 — adopt openkal 0.9, and make the aarch64 leg work for the firs…

0.7.0 — adopt openkal 0.9, and make the aarch64 leg work for the firs… #76

Workflow file for this run

name: CI
# What this workflow asserts.
#
# conformance the suite in the specification package runs against this
# implementation and every observation holds
# additions this package's own tests, which examine the operations
# version 0.5 added
# surface the exported names are exactly the fifty-one the
# specification lists, in both forms
# independence the objects reference no C library symbol
#
# The last is the property version 0.5 exists for, and it is asserted against
# the objects rather than against the source, because a source can reach a C
# library through a macro.
#
# Two compiler families, because an implementation is a contract's evidence 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:
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.27.1
XLINGS_VERSION: v2026.8.17.2
XLINGS_NON_INTERACTIVE: '1'
jobs:
conformance:
name: conformance, surface, and independence (${{ matrix.toolchain }})
runs-on: ubuntu-24.04
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
toolchain: ['gcc@16.1.0', 'llvm@22.1.8']
steps:
- uses: actions/checkout@v4
# The specification is checked out at the branch under test where it has
# one, so that this run asserts what it is for: that the specification as
# written there and this implementation as written here agree today.
# THE COMMITTED MANIFEST NAMES NO DIRECTORY OF ANYBODY'S MACHINE.
#
# Two scripts in the specification's repository rewrite this manifest to
# name a working tree --- run-conformance.sh and run-kit-tests.sh --- and
# both restore it through a trap. A trap does not fire when the process is
# killed, and a run by hand followed by `git add -A` then publishes a path
# that exists on one machine: a consumer resolving from the index is handed
# a manifest pointing at a directory that exists nowhere.
#
# ⚠️ THAT HAS HAPPENED IN THIS ECOSYSTEM, in openkal-musl, and the working
# tree here has carried the same rewrite more than once since. This step
# runs first, so what it examines is what the commit contains.
- name: The committed manifest names no local directory
run: |
set -euo pipefail
bad=$(grep -nE '^[a-z-]+ = \{[^}]*path = "(/|[A-Za-z]:)' mcpp.toml || true)
if [ -n "$bad" ]; then
echo "::error::the committed manifest names an absolute path"
printf '%s\n' "$bad" | sed 's/^/ /'
echo " run 'git checkout -- mcpp.toml' after using the"
echo " specification's conformance or kit scripts by hand."
exit 1
fi
echo " ok every dependency is named by version, branch or a relative path"
- name: The specification
run: |
git clone --quiet https://github.com/mcpplibs/openkal.git .spec
branch='${{ github.head_ref || github.ref_name }}'
if git -C .spec rev-parse --verify --quiet "origin/$branch" > /dev/null; then
git -C .spec checkout --quiet "origin/$branch"
echo "the specification is at $branch"
else
echo "the specification has no $branch; its default branch is used"
fi
- 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
# ⚠️ 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 )
# ⚠️ BOTH SPELLINGS, AND NO `-perm`. The matrix reaches Windows and
# macOS runners too: on Windows the artefact is `mcpp.exe`, and
# `-perm -u+x` is not a question that filesystem answers the way this
# expects. Measured: `Finished release [optimized] in 173.44s`
# followed by "mcpp did not build" — the build had succeeded and the
# search was looking for the wrong name.
#
# `$src` is a FRESH clone each run, so `target/` holds exactly what
# this step just built; `-printf` would be the safer form on a cached
# tree and is a GNU extension this must not use.
built=$(find "$src/target" -type f \
\( -name mcpp -o -name mcpp.exe \) | 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
# 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: Every interface, every kind of examination
run: |
# ⚠️⚠️ `optional` IS PART OF THE SET AND WAS NOT, WHICH MADE THE STEP'S
# OWN NAME UNTRUE.
#
# `full` expands to `standard,abi,stability,cost`, and `standard` is
# the HOSTED set — core, env, time, fs, process, task. The five
# interfaces openkal 0.8 added are in `optional`, and this
# implementation provides all of them; without naming that set the
# suite compiled their sections with the bodies removed and reported
# them as not examined. Nothing failed, and nothing was checked.
#
# This implementation is the one that may name `optional` entire: it
# is the only one providing every interface, which is why it is the
# reference. A backend that declines one names the others by hand.
bash .spec/tools/run-conformance.sh openkal-linux . full,optional
- name: Point at the specification's working tree
run: |
# run-conformance.sh has already done this; it is repeated because a
# reader of this file should not have to know that.
sed 's|^openkal = .*$|openkal = { path = ".spec" }|' mcpp.toml > mcpp.toml.next
mv mcpp.toml.next mcpp.toml
- name: This package's own tests
run: |
mcpp test 2>&1 | tee tests.log
# A suite that discovered nothing reports success, so every suite is
# asserted to have run. The list is derived from the files present
# rather than written out here: a hand-written list names the suites
# that existed when it was written, and one added afterwards escapes
# the assertion silently.
missing=0
for f in tests/*.cpp; do
name="$(basename "$f" .cpp)"
grep -q "^$name \.\.\. ok" tests.log || { echo "did not run or did not pass: $name" >&2; missing=1; }
done
test "$missing" -eq 0
- name: The exported surface is complete and contains nothing else
run: |
rm -rf target && mcpp build
bash .spec/tools/check-surface.sh --complete .spec/SURFACE.txt \
$(find target -path '*/obj/*' -name '*.o' ! -name '*.m.o' ! -name 'conformance*' | tr '\n' ' ')
- name: The C++ declarations are complete
run: |
# The specification's own tool examines the C form. The module form is
# examined here, where a build of the modules exists: the test names
# every entity SURFACE.txt lists, so a name the modules do not export
# fails to compile and the diagnostic names it.
list="$(grep -vE '^[[:space:]]*(#|$)' .spec/SURFACE.txt | sort -u)"
{
# ⚠️ THE MODULE LIST IS DERIVED, NOT WRITTEN OUT.
#
# It used to be written out, and it fell out of step the first time
# the specification gained an interface: SURFACE.txt listed four
# `kal_exec_*' names, no `import openkal.exec' was emitted, and the
# generated test failed with four undeclared identifiers --- which
# names the symptom (a name is missing) and not the cause (a list
# here was not updated).
#
# SURFACE.txt groups its names under `# openkal.<interface>'
# headings, so the list it already carries is the list this needs.
# `types' is not a group there --- it declares no kal_ name --- and
# is therefore named separately.
echo "import openkal.types;"
for m in $(grep -oE '^# openkal\.[a-z]+' .spec/SURFACE.txt | cut -d. -f2); do
echo "import openkal.$m;"
done
echo 'const void *const surface[] = {'
while read -r n; do [ -n "$n" ] && echo " (const void *)&$n,"; done <<< "$list"
echo '};'
echo 'int main() { return surface[0] == 0; }'
} > tests/conformance_declarations.cpp
mcpp test conformance_declarations 2>&1 | tee decl.log
grep -q '^conformance_declarations \.\.\. ok' decl.log
rm -f tests/conformance_declarations.cpp
# The property this version exists for. An implementation beneath a
# program that supplies its own runtime must not reach for that runtime's
# names, or its calls resolve to the program's and recur without bound.
- name: The objects reference no C library symbol
run: |
rm -rf target && mcpp build --features standalone
objs=$(find target -path '*/obj/*' -name '*.o' ! -name '*.m.o' ! -name 'conformance*')
test -n "$objs" || { echo "no objects were found; the check would pass vacuously" >&2; exit 1; }
# The permitted set. memcpy, memmove, memset and memcmp are the four a
# freestanding translation unit is permitted to require: a compiler
# emits calls to them from ordinary loops, and they compute rather
# than call, so none of them can re-enter this implementation.
# __libc_start_main and main are the hand-over, and are undefined here
# by construction.
permitted='^(memcpy|memmove|memset|memcmp|__libc_start_main|main|_GLOBAL_OFFSET_TABLE_|kal_[a-z_]+|__init_array_start|__init_array_end|__preinit_array_start|__preinit_array_end|_ZN3okl.*)$'
# ⭐⭐ ONE NAME IS PERMITTED ONLY IF IT IS WEAK, AND THE WEAKNESS IS
# THE WHOLE OF THE PERMISSION.
#
# `environ' is how src/env.cpp recovers the vectors the kernel placed
# on the stack when the C library above did not pass them --- glibc
# calls every `.init_array' entry with (argc, argv, envp) and musl
# calls them with none, so what arrived there was register residue.
#
# It is admissible where `puts' is not, and the difference is not that
# it is smaller. This check exists because a CALL into the runtime a
# program supplied resolves to the program's and can re-enter this
# implementation without bound. A pointer executes nothing. And being
# WEAK it is null in a program that has no C library, so it does not
# make one required --- which a strong reference to the same name
# would, silently, and is why the type letter is checked and not just
# the name.
weak_permitted='^environ$'
bad=0
nm --undefined-only $objs | awk '{ print $1, $2 }' | sort -u |
while read -r kind name; do
[ -n "$name" ] || continue
printf '%s\n' "$name" | grep -qE "$permitted" && continue
if printf '%s\n' "$name" | grep -qE "$weak_permitted"; then
case "$kind" in
w|v) continue ;;
*) echo "::error::$name is permitted only as a weak reference, and this one is '$kind'" >&2 ;;
esac
else
echo "the implementation references a symbol it must not: $name" >&2
fi
echo bad >> "$RUNNER_TEMP/independence.bad"
done
[ -s "$RUNNER_TEMP/independence.bad" ] && bad=1
rm -f "$RUNNER_TEMP/independence.bad"
test "$bad" -eq 0
echo "the implementation references no C library symbol"
# A checker is only useful if it fails when it should.
- name: The independence check detects a dependence
run: |
printf 'extern "C" int puts(const char*);\nextern "C" void okl_probe(void) { puts("x"); }\n' > src/probe.cpp
rm -rf target && mcpp build --features standalone
objs=$(find target -path '*/obj/*' -name '*.o' ! -name '*.m.o')
if ! nm --undefined-only $objs | awk '{print $2}' | grep -qx puts; then
echo "the probe did not produce the reference it was written to produce" >&2
rm -f src/probe.cpp; exit 1
fi
rm -f src/probe.cpp
echo "a dependence upon a C library is visible to the check"