Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions .github/actions/mtest/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: mtest
description: >
The mtest engine CI reports with: the release .github/versions.env pins
(MTEST_VERSION), an xlings package (xim:mtest) the index verifies, else the
release bundle checked against MTEST_SHA256. Exports MTEST, the launcher.

runs:
using: composite
steps:
# By the configured mirror (GLOBAL), then by CN (GitCode first), each with a
# refreshed index; else the release bundle itself: the web address, then
# the API's, checked against the pinned sha256.
- name: mtest
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
. .github/versions.env
launcher=""
for mirror in GLOBAL CN; do
xlings config --mirror "$mirror" > /dev/null 2>&1 || true
xlings update > /dev/null 2>&1 || true
if xlings install "mtest@$MTEST_VERSION" -y -g; then launcher=$(command -v mtest); break; fi
done
# The package's own launcher, not the xvm shim, which on Windows runs
# the alias through cmd.
exe=''; [ "$RUNNER_OS" != Windows ] || exe='.exe'
own="${XLINGS_HOME:-$HOME/.xlings}/data/xpkgs/xim-x-mtest/$MTEST_VERSION/bin/mtest$exe"
if [ -n "$launcher" ] && [ -f "$own" ]; then launcher="$own"; fi
xlings config --mirror GLOBAL > /dev/null 2>&1 || true
if [ -z "$launcher" ]; then
echo "::warning::xlings could not install mtest $MTEST_VERSION; taking the release itself"
archive="mtest-$MTEST_VERSION-openkal-bundle-all.tar.gz"
temp="$RUNNER_TEMP"; python=python3
if [ "$RUNNER_OS" = Windows ]; then temp=$(cygpath -u "$RUNNER_TEMP"); python=python; fi
if ! curl -fsSL --retry 5 --retry-all-errors --retry-delay 5 -o "$temp/$archive" \
"https://github.com/mcpp-community/mtest/releases/download/v$MTEST_VERSION/$archive"; then
id=$(curl -fsSL --retry 3 --retry-all-errors -H "Authorization: Bearer $GH_TOKEN" \
"https://api.github.com/repos/mcpp-community/mtest/releases/tags/v$MTEST_VERSION" \
| "$python" -c 'import json,sys; print(next(str(a["id"]) for a in json.load(sys.stdin)["assets"] if a["name"] == sys.argv[1]))' "$archive")
curl -fsSL --retry 5 --retry-all-errors --retry-delay 5 -H "Authorization: Bearer $GH_TOKEN" \
-H "Accept: application/octet-stream" -o "$temp/$archive" \
"https://api.github.com/repos/mcpp-community/mtest/releases/assets/$id"
fi
actual=$("$python" -c "import hashlib,sys; print(hashlib.sha256(open(sys.argv[1],'rb').read()).hexdigest())" "$temp/$archive")
[ "$actual" = "$MTEST_SHA256" ] || { echo "::error::$archive: sha256 $actual, versions.env pins $MTEST_SHA256"; exit 1; }
mkdir -p "$temp/mtest" && tar xzf "$temp/$archive" -C "$temp/mtest"
launcher="$temp/mtest/mtest-$MTEST_VERSION-openkal-bundle-all/bin/mtest$exe"
fi
"$launcher" --version | grep -qF "$MTEST_VERSION" \
|| { echo "::error::the mtest at $launcher is not $MTEST_VERSION"; exit 1; }
echo "MTEST=$launcher" >> "$GITHUB_ENV"
43 changes: 43 additions & 0 deletions .github/actions/test-report/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: test-report
description: >
The runs `mcpp test` kept in the mtest store's form ([test] store, docs/08;
SPEC-017 §6): taken into one store of the job, written as one Markdown report
of this workflow run's runs into the job's summary, and kept as an artifact
for the report over every job of the workflow run (ci.yml `report`).
inputs:
name:
description: the artifact's name, unique in the workflow run (mtest-unit-<host>)
required: true
stores:
description: the stores `mcpp test` kept in this job, relative to the workspace, separated by spaces
required: false
default: ld/target/mtest
runs:
using: composite
steps:
- uses: ./.github/actions/mtest
- shell: bash
env:
STORES: ${{ inputs.stores }}
run: |
set -euo pipefail
job="$(cygpath -u "$RUNNER_TEMP" 2> /dev/null || printf '%s' "$RUNNER_TEMP")/mtest-job"
mkdir -p "$job"
taken=0
for store in $STORES; do
[ -f "$store/index.ndjson" ] || continue
(cd "$job" && "$MTEST" store import --from "$GITHUB_WORKSPACE/$store")
taken=1
done
if [ "$taken" = 0 ]; then echo "mcpp test kept no run here"; exit 0; fi
(cd "$job" && "$MTEST" report --format md --group "$GITHUB_RUN_ID") >> "$GITHUB_STEP_SUMMARY" \
|| echo "::warning::no run of this workflow run in the store"
echo "MTEST_JOB_STORE=$(cygpath -w "$job/.mtest" 2> /dev/null || printf '%s' "$job/.mtest")" >> "$GITHUB_ENV"
- uses: actions/upload-artifact@v4
if: env.MTEST_JOB_STORE != ''
with:
name: ${{ inputs.name }}
path: ${{ env.MTEST_JOB_STORE }}
include-hidden-files: true
if-no-files-found: ignore
retention-days: 7
5 changes: 5 additions & 0 deletions .github/versions.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# The engines CI pins, read by the composite actions (.github/actions/mtest).
# mtest: an xlings package (xim:mtest) whose sha256 the index states; the release
# bundle's sha256 checks the fallback download.
MTEST_VERSION=0.5.2
MTEST_SHA256=be443f8c546b3f4628dcd3f1eb6f45b4d698a7f805232f966349faf8250863c6
46 changes: 45 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ on:
env:
XLINGS_VERSION: v2026.10.26.1
XLINGS_NON_INTERACTIVE: '1'
MCPP_VERSION: 2026.11.9.1
# 2026.11.10.1: openkal-run binds the interfaces its implementation provides
# (MCPP_KERNEL_ABI_INTERFACES).
MCPP_VERSION: 2026.11.10.1
LLVM: llvm@23.1.3

jobs:
Expand All @@ -54,6 +56,12 @@ jobs:
mcpp toolchain default "$LLVM"
- name: libokld against the published vectors
run: (cd ld && mcpp test)
# The run `mcpp test` kept (ld/target/mtest): this job's report in its
# summary, and the store for the workflow run's report (`report`).
- uses: ./.github/actions/test-report
if: always()
with:
name: mtest-images
- name: Debugger object bounds and failed-admission ownership
run: python3 .github/scripts/check-debugger-contract.py --cxx "$HOME/.mcpp/registry/data/xpkgs/xim-x-llvm/23.1.3/bin/clang++"
- name: okld
Expand Down Expand Up @@ -260,6 +268,10 @@ jobs:
okprof=$(find "$PWD/target" -path '*bin*' \( -name okprof -o -name okprof.exe \) -type f | head -1)
[ -n "$okprof" ] || { echo "::error::okprof did not build"; exit 1; }
echo "OKPROF=$okprof" >> "$GITHUB_ENV"
- uses: ./.github/actions/test-report
if: always()
with:
name: mtest-run-${{ matrix.host }}
- name: okprof forwards environment values and the child's status
run: |
python=python3; [ "$RUNNER_OS" = Windows ] && python=python
Expand Down Expand Up @@ -486,3 +498,35 @@ jobs:
grep -q "$want" bind.txt || { echo "::error::the binding: $want"; exit 1; }
done
bash examples/bind/check-refusals.sh

# EVERY JOB'S TEST RUNS AS ONE REPORT (`mtest ci report`, the workflow run's
# group): libokld's vectors on the image machine and on each system.
report:
name: tests (report)
needs: [images, run]
if: ${{ always() && !cancelled() }}
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- 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"
- uses: ./.github/actions/mtest
- uses: actions/download-artifact@v4
with:
pattern: mtest-*
path: stores
- name: Every job's runs, reported together
run: |
set -euo pipefail
found=0
for store in stores/*/; do
[ -f "$store/index.ndjson" ] || continue
"$MTEST" store import --from "$store"
found=1
done
[ "$found" = 1 ] || { echo "no job kept a run" >> "$GITHUB_STEP_SUMMARY"; exit 0; }
"$MTEST" ci report --group "$GITHUB_RUN_ID" --out tests.md
cat tests.md >> "$GITHUB_STEP_SUMMARY"
4 changes: 2 additions & 2 deletions .github/workflows/release-binaries.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ env:
RELEASE_TAG: ${{ github.event.inputs.tag }}
ASSET_REVISION: ${{ github.event.inputs.asset_revision || '' }}
MCPP_SOURCE_REF: ${{ github.event.inputs.mcpp_ref || vars.MCPP_SOURCE_REF || '' }}
# 2026.11.9.1: openkal-run states its implementation as a capability with defaults.
# 2026.11.10.1: openkal-run binds the interfaces its implementation provides.
# mcpp_ref supplies the candidate until this release is installable.
MCPP_VERSION: 2026.11.9.1
MCPP_VERSION: 2026.11.10.1
# The platform whose hosted arrangement the programs are built over, named by
# the dispatch beside the tag: a tag's tools pin exact implementations, and the
# platform is the one whose version group holds them (0.1.4: 0.18.0; 0.1.5:
Expand Down
21 changes: 16 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,22 @@ implementation the graph already has --- the consumer's own, or the one its
platform adds, such as openkal-posix on another system --- is the one bound,
without a patch of this package.

Version 0.5.5 is built against the openkal 0.25 family: openkal 0.25.0 and, as
the defaults per system, openkal-linux 0.26.0, openkal-macos 0.23.0 and
openkal-windows 0.21.0. The names the runner binds are generated from the
specification's SURFACE.txt, so the names 0.25 adds (`kal_fs_hard_link`,
among others) are bound without a change of the runner's sources.
Version 0.6.0 binds the interfaces the implementation provides, which it reads
when it is built (mcpp 2026.11.10.1, `MCPP_KERNEL_ABI_INTERFACES`): the
implementation's `provides-interfaces`. A name of any other interface is listed
without an address, the runner refers to nothing of that interface, and an image
that requires it is refused at load, naming it (SPEC 12.4). The word of
interfaces an image is told is the implementation's answer intersected with the
interfaces bound, together with the loader's own. An implementation that states
no list is taken to provide every interface. The interfaces the runner calls
itself are stated as `[kernel-abi] requires-interfaces`, so an implementation
without one of them is refused when the graph is resolved. No source file is
chosen per system any more: an implementation that provides part of the surface,
on any system, needs no function it does not have. The names the runner binds are
generated from the specification's SURFACE.txt, so the names 0.25 adds
(`kal_fs_hard_link`, among others) are bound without a change of the runner's
sources. The package follows openkal 0.25 (openkal-linux 0.26, openkal-macos
0.23, openkal-windows 0.21).

## An image

Expand Down
2 changes: 1 addition & 1 deletion bind/mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
namespace = "mcpplibs"
name = "openkal-bind"
version = "0.5.5"
version = "0.6.0"
# `mcpp::xpkg_request', `mcpp::decision' (protocol 15).
mcpp = ">=2026.10.1.3"
description = "A build plugin: a library published as an ok image (.okso) and a C header is called from a program of any system, through a binding generated from the header and libokrun."
Expand Down
6 changes: 5 additions & 1 deletion ld/mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
namespace = "mcpplibs"
name = "openkal-ld"
version = "0.5.5"
version = "0.6.0"
description = "libokld: the ok format (openkal image format) --- its definition, a reader of bytes, a checker, a signer, and a loader. Freestanding: no C library, no exceptions."
license = "Apache-2.0"
authors = ["mcpplibs"]
Expand All @@ -10,6 +10,10 @@ repo = "https://github.com/mcpplibs/openkal-image"
[targets.okld]
kind = "lib"

# The library's tests (tests/), on the mtest library.
[dev-dependencies]
mcpplibs.mtest = "0.5.2"

[build]
sources = ["src/*.cppm", "src/*.cpp"]
include_dirs = ["include"]
Expand Down
12 changes: 7 additions & 5 deletions ld/tests/crypto_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,21 @@
#include <cstring>
#include <okld.h>

import mcpplibs.mtest;

using namespace mcpplibs::mtest;

extern "C" void okld_sha512_for_test(const void*, okld_size, unsigned char[64]);
extern "C" void okld_ed25519_sign_for_test(const unsigned char[32], const void*, okld_size, unsigned char[64]);

namespace {
int failures = 0;
void bytes(const char* hex, unsigned char* out) {
for (std::size_t i = 0; hex[2 * i]; ++i) { unsigned v; std::sscanf(hex + 2 * i, "%2x", &v); out[i] = static_cast<unsigned char>(v); }
}
void check(bool held, const char* what) { std::printf(" %s %s\n", held ? "held" : "FAIL", what); if (!held) ++failures; }
void check(bool held, const char* what) { expect(held) << what; }
}

int main() {
UTest crypto_test{"crypto", [] {
unsigned char h[64], want[64];
okld_sha512_for_test("abc", 3, h);
bytes("ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f", want);
Expand All @@ -42,5 +45,4 @@ int main() {
okld_ed25519_sign_for_test(seed, msg, std::strlen(t.msg) / 2, sig);
check(std::memcmp(sig, want_sig, 64) == 0, "an Ed25519 signature is RFC 8032's");
}
return failures;
}
}};
12 changes: 7 additions & 5 deletions ld/tests/okb_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@
#include <vector>
#include <okld.h>

import mcpplibs.mtest;

using namespace mcpplibs::mtest;

namespace {
int failures = 0;
void check(bool held, const char* what) { std::printf(" %s %s\n", held ? "held" : "FAIL", what); if (!held) ++failures; }
void check(bool held, const char* what) { expect(held) << what; }
std::string hex(const unsigned char* p, int n) {
std::string s; char b[3];
for (int i = 0; i < n; ++i) { std::snprintf(b, sizeof b, "%02x", p[i]); s += b; }
Expand All @@ -30,7 +33,7 @@ std::vector<unsigned char> write(const std::vector<okld_zip_input>& in) {
}
} // namespace

int main() {
UTest okb_test{"okb", [] {
unsigned char d[32];
okld_sha256("abc", 3, d);
check(hex(d, 32) == "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", "SHA-256 of \"abc\" (FIPS 180-4)");
Expand Down Expand Up @@ -305,5 +308,4 @@ int main() {
check(admit(pack(without, big), without, nullptr, &why) == 0, "without chunks, the whole digest is verified");
check(admit(pack(without, changed), without, nullptr, &why) < 0, "and refuses a changed byte");
}
return failures;
}
}};
Loading
Loading