Skip to content

CI

CI #49

Workflow file for this run

name: CI
# What this workflow asserts.
#
# conformance the suite in the specification package runs against this
# implementation, on both architectures, and every observation
# holds
# surface the exported names are exactly the fifty-one the
# specification lists
# independence the objects reference nothing of a C library but the two names
# no C library defines
#
# The third is the property version 0.3 exists for. A program above openkal may
# supply every name the system's own library supplies; an implementation that
# called one of them would have its calls resolve to the program's, and the
# program's would resolve back here.
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:
conformance:
name: conformance (${{ matrix.os }}, ${{ matrix.toolchain }})
runs-on: ${{ matrix.os }}
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
include:
# arm64 only, and not because the other architecture is unsupported.
# The build tool has no release for x86_64 on this system --- its
# installer answers "Unsupported release target: macosx-x86_64" --- so
# the suite cannot be built there at all. What can be done there is
# done: the `numbers' workflow measures that kernel's own table on
# both architectures, and the step below compiles every source for the
# other one with the system's compiler.
- { os: macos-14, toolchain: 'llvm@20.1.7' }
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.
- 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 )
# ⭐ `$src` is a FRESH clone each run, so `target/` holds exactly what
# this step just built and there is no earlier fingerprint directory
# to pick by mistake. `-printf` would be the safer form on a cached
# tree and is a GNU extension this must not use — one of the runners
# that reaches this line is macOS.
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
# 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: |
bash .spec/tools/run-conformance.sh openkal-macos . full
# The other architecture, as far as this system allows it to be reached.
#
# The system-call numbers agree between the two --- measured, in the
# `numbers' workflow --- and three things do not: the calling convention,
# the register the current context is reached through, and one field of a
# signal context. All three are in src/sys.h and all three are compiled
# here, with the system's own compiler rather than the build tool's,
# because the build tool has no release for that architecture.
- name: Every source compiles for the other architecture
run: |
for f in src/*.cpp; do
clang++ -std=c++2b -c -o /dev/null --target=x86_64-apple-macos13 \
-fno-exceptions -fno-rtti -DOKM_STANDALONE -I .spec/include "$f"
done
echo "every source compiles for x86_64"
- name: The exported surface is complete and contains nothing else
run: |
# Clause 9.3. The list and the checker come from the specification
# rather than from a copy kept here, so that the comparison has one
# source. --complete because this implementation claims every
# interface: a name it fails to export is a failure, not an interface
# it declines to provide.
rm -rf target && mcpp build
# This package's own objects, and not the ones the build tool emits
# beside them: one of those exists to initialise a standard library
# this package does not use, and it is not this package's to answer
# for. They are told apart by name, because the tool places both in
# one directory.
objs="$(find target -path '*/obj/*' -name '*.o' ! -name '*.m.o' ! -name 'mcpp_*' | tr '\n' ' ')"
test -n "$objs" || { echo "no objects were found" >&2; exit 1; }
bash .spec/tools/check-surface.sh --complete .spec/SURFACE.txt $objs
# The property version 0.3 exists for. The assertion is made against the
# objects rather than against the source, because a source can reach a C
# library through a macro.
- name: The objects reference nothing of a C library but the two named
run: |
rm -rf target && mcpp build --features standalone
objs="$(find target -path '*/obj/*' -name '*.o' ! -name '*.m.o' ! -name 'mcpp_*')"
test -n "$objs" || { echo "no objects were found; the check would pass vacuously" >&2; exit 1; }
# The permitted set, and why each entry is in it.
#
# memcpy, memmove, memset, memcmp a compiler emits calls to these
# from ordinary loops. They compute rather than call, so none of
# them can re-enter this implementation.
# clock_gettime_nsec_np,
# pthread_create_from_mach_thread the two names no C library
# defines, which is why they are reachable from here at all.
# __libc_start_main, main, _main the hand-over, undefined here by
# construction.
# kal_* the interface itself.
# __stack_chk_* emitted by the toolchain around
# a frame it protects; supplied by the program, not called by this
# implementation.
# section$start$… / section$end$… ⭐ THE LINKER'S, NOT A LIBRARY'S.
# `src/unwind.cpp` answers `_dyld_find_unwind_sections` out of the
# boundaries of its own image's `__TEXT,__eh_frame` and
# `__TEXT,__unwind_info`, and this object format states those as
# symbols the LINKER synthesises. Nothing defines them in any
# library, so no library can be reached through them — which is
# the property this check exists to establish. Same kind of entry
# as `dyld_stub_binder` in `port/libSystem.tbd`, and the note
# there says why: a requirement of the format rather than of this
# package.
# ___dso_handle likewise the format's: emitted
# beside the weak references above, defined by the image's own
# start files.
permitted='^_?(memcpy|memmove|memset|memcmp|bzero|clock_gettime_nsec_np|pthread_create_from_mach_thread|pthread_create|pthread_join|__libc_start_main|main|kal_[a-z_]+|__stack_chk_guard|__stack_chk_fail|GCC_except_table.*|_ZN3okm.*|__Unwind_Resume|__dso_handle|section\$(start|end)\$__TEXT\$__(eh_frame|unwind_info))$'
# Reported with the object that references it. A symbol without the
# object it came from names a fault and not a place, and the first
# time this check fired the answer was in the object rather than in
# the symbol.
bad=0
for o in $objs; do
for s in $(nm -u "$o" | sed 's/^ *//' | grep -v ':$' | sort -u); do
[ -n "$s" ] || continue
printf '%s\n' "$s" | grep -qE "$permitted" || {
echo "$o references a symbol it must not: $s" >&2
bad=1
}
done
done
test "$bad" -eq 0
echo "the implementation reaches nothing of a C library but the two names that are named"
# 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 okm_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' ! -name 'mcpp_*')"
if ! nm -u $objs | sed 's/^ *//' | 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"