Provide net, datagram, timeout and exec; decline space in terms #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. | |
| # | |
| # conformance the suite in the specification package runs against this | |
| # implementation and every observation holds, under all three | |
| # of this environment's toolchains | |
| # surface the exported names are exactly the fifty-one the | |
| # specification lists | |
| # independence the objects reference no C runtime symbol | |
| # | |
| # This environment has two application binary interfaces and three toolchains | |
| # that reach them, and an implementation that built under one of the three would | |
| # not be an implementation for this environment. So all three are built: the two | |
| # that produce the environment's own ABI, and the one that produces the other. | |
| 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: | |
| # --------------------------------------------------------------------------- | |
| # On the system itself. | |
| conformance: | |
| name: conformance (${{ matrix.name }}) | |
| runs-on: windows-2022 | |
| timeout-minutes: 60 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - { name: 'llvm, msvc ABI', toolchain: 'llvm@20.1.7', target: '' } | |
| - { name: 'msvc', toolchain: 'msvc@system', target: '' } | |
| - { name: 'gcc, gnu CRT', toolchain: 'gcc@16.1.0', target: 'x86_64-windows-gnu' } | |
| defaults: | |
| run: | |
| shell: bash | |
| 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 | |
| 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 | |
| # ⚠️⚠️ TRANSITION: GIVE THE BOOTSTRAP THE glibc ITS BINDING NAMES. | |
| # | |
| # `xim:glibc`'s `latest` moved from `2.44` to `2.44.2`. A payload | |
| # directory is named after the version a request RESOLVED to, while a | |
| # RuntimeBinding carries the version that was DECLARED — and the xlings | |
| # a released mcpp vendors into its own sandbox still declares `2.44`. | |
| # So a clean machine installs `2.44.2`, the toolchain fixup asks for | |
| # `2.44`, and the build stops before anything is compiled: | |
| # | |
| # error: selected RuntimeBinding glibc@2.44 requires payload | |
| # '…/xpkgs/xim-x-glibc/2.44', but it is not installed | |
| # | |
| # ⚠️ On every NEW machine and on none that already existed, which is why | |
| # it is invisible from a developer's own. Measured on `main` as readily | |
| # as on any branch — the index records the same failure verbatim in | |
| # `pkgs/g/glibc.lua` and states the rule it broke: "The index is DATA | |
| # and the client is a PROGRAM: the consumer ships first." | |
| # | |
| # ⭐ REMOVE THIS once a released mcpp resolves it. `mcpp 2026.8.27.1` | |
| # accepts an installed payload whose version REFINES the requested one | |
| # (`payload_dir_for_version`), so a bootstrap from it needs nothing | |
| # here. Until then the missing payload is simply installed. | |
| if [ -x "$HOME/.mcpp/registry/bin/xlings" ]; then | |
| XLINGS_HOME="$HOME/.mcpp/registry" XLINGS_NON_INTERACTIVE=1 \ | |
| "$HOME/.mcpp/registry/bin/xlings" install glibc@2.44 -y -g \ | |
| >/dev/null 2>&1 || true | |
| echo "glibc payloads present: $(ls "$HOME/.mcpp/registry/data/xpkgs/xim-x-glibc" 2>/dev/null | tr '\n' ' ')" | |
| 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 | |
| # EVERY NAME THE HEADER DECLARES IS EXPORTED BY ONE OF THE .def FILES. | |
| # | |
| # `port/*.def` is an explicit list, and an import library generated from it | |
| # contains exactly those names. So a declaration added to `src/win32.h` | |
| # without a matching line in a `.def` compiles, and fails at the link of a | |
| # CONSUMER --- not of this package, which does not link. | |
| # | |
| # ⚠️ MEASURED. Three declarations were added for openkal 0.8 and the names | |
| # were not, and this package's own CI stayed green: the failure appeared in | |
| # openkal-llvm-runtime's cross-build, one repository away, as | |
| # | |
| # ld.lld: error: undefined symbol: __declspec(dllimport) CreatePipe | |
| # | |
| # which reads as a defect in the consumer. The check belongs here, where | |
| # the two lists are. | |
| - name: Every declared name is exported by a .def | |
| run: | | |
| python3 - <<'PY' | |
| import glob, os, re, sys | |
| # ⚠️ THE ENCODING IS NAMED. Python opens a file with the platform's | |
| # default, which on this runner is cp1252, and these sources are UTF-8: | |
| # | |
| # UnicodeDecodeError: 'charmap' codec can't decode byte 0x90 | |
| # | |
| # A check that reads source files must say what they are encoded in, or | |
| # it reports on the runner's locale. | |
| declared = set(re.findall(r'OKW_IMPORT\s+\w+\s+OKW_API\s+(\w+)\s*\(', | |
| open("src/win32.h", encoding="utf-8").read())) | |
| exported = set() | |
| for f in glob.glob("port/*.def"): | |
| body = open(f, encoding="utf-8").read().split("EXPORTS", 1) | |
| if len(body) < 2: continue | |
| exported |= {l.strip() for l in body[1].split("\n") | |
| if l.strip() and not l.lstrip().startswith(';')} | |
| # A denominator on both sides: with either list empty the difference is | |
| # vacuously empty too. | |
| if not declared or not exported: | |
| print(f"::error::declared={len(declared)} exported={len(exported)}; nothing was compared") | |
| sys.exit(1) | |
| missing = sorted(declared - exported) | |
| print(f" {len(declared)} declared, {len(exported)} exported across " | |
| f"{len(glob.glob('port/*.def'))} .def files") | |
| if missing: | |
| print("::error::declared in src/win32.h and exported by no .def:") | |
| for m in missing: print(f" {m}") | |
| sys.exit(1) | |
| print(" ok every declared name is exported") | |
| PY | |
| - 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: | | |
| extra='' | |
| [ -n '${{ matrix.target }}' ] && extra='--target ${{ matrix.target }}' | |
| # ⚠️ THE SET IS ENUMERATED AND `optional` IS NOT NAMED, AND THE | |
| # DIFFERENCE IS ONE INTERFACE. | |
| # | |
| # `optional` includes `space`, which this system does not provide: | |
| # there is no primitive here that copies an address space and starts a | |
| # context in the copy. A set demanding it would not report an | |
| # observation that did not hold — it would fail to LINK, naming | |
| # `kal_space_start`, which is clause 6.1's report arriving where a | |
| # report was not wanted. | |
| # | |
| # So the six this implementation does provide are named, and the | |
| # seventh is absent from the list for the same reason it is absent | |
| # from the objects. | |
| bash .spec/tools/run-conformance.sh openkal-windows . \ | |
| full,exec,random,terminal,net,datagram,timeout $extra | |
| # --------------------------------------------------------------------------- | |
| # From a system that is not this one. | |
| # | |
| # The same package, produced by a cross build and run under an implementation | |
| # of this environment's interfaces rather than on the environment. It is here | |
| # for two reasons: it is where the exported names can be examined with tools | |
| # that read this environment's object format and are not on it, and a package | |
| # that only builds on the system it targets is a package a contributor on any | |
| # other system cannot work on. | |
| cross: | |
| name: cross build, run under wine, and examine the objects | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 60 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - 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" | |
| fi | |
| - name: Install wine | |
| run: | | |
| sudo dpkg --add-architecture i386 | |
| sudo apt-get update -qq | |
| # Which package carries it and what the program is called differ | |
| # between distributions and between releases of one, so both are | |
| # tried and the name that exists is the one used. A script that names | |
| # one of them fails with "command not found" after a successful | |
| # install, which reads as a missing package rather than a renamed | |
| # program. | |
| sudo apt-get install -y --no-install-recommends wine wine64 > /dev/null 2>&1 \ | |
| || sudo apt-get install -y --no-install-recommends wine > /dev/null | |
| runner="$(command -v wine || command -v wine64)" | |
| test -n "$runner" || { echo "no wine was installed" >&2; exit 1; } | |
| "$runner" --version | |
| echo "OPENKAL_CONFORMANCE_RUNNER=$runner" >> "$GITHUB_ENV" | |
| - 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 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 | |
| - name: Every interface, every kind of examination | |
| env: | |
| WINEDEBUG: '-all' | |
| run: | | |
| bash .spec/tools/run-conformance.sh openkal-windows . \ | |
| full,exec,random,terminal,net,datagram,timeout \ | |
| --target x86_64-windows-gnu | |
| - 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 --target x86_64-windows-gnu | |
| objs="$(find target -path '*/obj/*' -name '*.o' ! -name '*.m.o' | 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 this implementation exists to have. A program above openkal | |
| # may supply a C runtime of its own; an implementation that reached for | |
| # this environment's would have its calls resolve to the program's, and | |
| # the program's would resolve back here. | |
| - name: The objects reference no C runtime symbol | |
| run: | | |
| rm -rf target && mcpp build --features standalone --target x86_64-windows-gnu | |
| objs="$(find target -path '*/obj/*' -name '*.o' ! -name '*.m.o')" | |
| 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. | |
| # | |
| # __imp_* this environment's own interfaces, reached through | |
| # the import table. They are the environment, not a C runtime. | |
| # Nt*, Rtl* the object manager, likewise. | |
| # WaitOnAddress, WakeByAddress* the suspension primitive, which | |
| # this environment publishes without an import stub. | |
| # 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. | |
| # __libc_start_main, main the hand-over, undefined here by | |
| # construction. | |
| # kal_*, _ZN3okw* the interface and this package. | |
| # __chkstk_ms, __udivti3, ... the compiler's own support | |
| # routines, which are in the compiler's library and not in a C | |
| # runtime. | |
| permitted='^(__imp_[A-Za-z0-9_]+|Nt[A-Za-z]+|Rtl[A-Za-z]+|WaitOnAddress|WakeByAddress(All|Single)|memcpy|memmove|memset|memcmp|__libc_start_main|main|kal_[a-z_]+|_ZN3okw.*|_*chkstk[a-z_]*|__udiv[a-z0-9]*|__umod[a-z0-9]*|__divti3|__modti3|_GLOBAL_OFFSET_TABLE_)$' | |
| bad=0 | |
| for s in $(nm --undefined-only $objs | awk '{print $2}' | sort -u); do | |
| [ -n "$s" ] || continue | |
| printf '%s\n' "$s" | grep -qE "$permitted" || { | |
| echo "the implementation references a symbol it must not: $s" >&2 | |
| bad=1 | |
| } | |
| done | |
| test "$bad" -eq 0 | |
| echo "the implementation references no C runtime 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 okw_probe(void) { puts("x"); }\n' > src/probe.cpp | |
| rm -rf target && mcpp build --features standalone --target x86_64-windows-gnu | |
| 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 runtime is visible to the check" |