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
101 changes: 101 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ jobs:
runs-on: ubuntu-24.04
timeout-minutes: 40
env:
# The specification is taken from the branch of the same name where one
# exists, so a change spanning both repositories is reviewed as a whole.
OPENKAL_BRANCH: ${{ github.head_ref || github.ref_name }}
MCPP_VERSION: 2026.8.27.1
XLINGS_VERSION: v2026.8.17.2
XLINGS_NON_INTERACTIVE: '1'
Expand Down Expand Up @@ -151,6 +154,54 @@ jobs:
echo "under review: $("$built" --version) (from $MCPP_SOURCE_REF)"
fi


# ⚠️⚠️ A JOB THAT RESOLVES THE PUBLISHED SPECIFICATION CANNOT REVIEW A
# CHANGE TO ONE.
#
# Another step in this file already clones the specification and
# substitutes it, and that made the arrangement look complete. It was not:
# the steps below reached `openkal' BY VERSION, so a version under review
# --- which by definition is not published --- failed them with
#
# E_NOT_FOUND: package 'compat.openkal@0.9.0' not found in the
# synced index ... the index is current, so this name is either
# wrong or not published yet
#
# ⭐⭐ THE UNIT IS THE JOB, NOT THE REPOSITORY. Measured 2026-08-28 across
# the eight repositories of this ecosystem while one change spanned all of
# them: seven jobs in three repositories had this shape, and every one of
# those repositories ALSO had a job that substituted correctly --- which is
# what made the gap invisible to a check done a repository at a time.
#
# These jobs are green on `main` and can only be green there, because
# there the published version is the one under test. That is the property
# that makes the defect silent: it is not a check that fails, it is a
# check that cannot run at the only time it would have something to say.
- name: Point at the specification's working tree
run: |
set -euo pipefail
git clone --quiet https://github.com/mcpplibs/openkal .spec
if git -C .spec rev-parse --verify --quiet "origin/$OPENKAL_BRANCH" > /dev/null; then
git -C .spec checkout --quiet "origin/$OPENKAL_BRANCH"
echo "the specification is at $OPENKAL_BRANCH"
else
echo "the specification has no $OPENKAL_BRANCH; its default branch is used"
fi
# ⚠️ EVERY MANIFEST THAT NAMES IT, AND BY THE SAME FORM. mcpp refuses
# a graph in which one package reaches a dependency by version and
# another by path, so substituting only the root leaves the build
# refusing for a second reason instead of the first.
# ⚠️ NOT `sed -i'. This step runs on macOS and on Windows too, and
# BSD sed requires an argument to -i that GNU sed refuses. A temporary
# file is the spelling that holds on all three.
subst() { # subst <file> <relative-path-to-the-specification>
sed "s|^openkal = .*$|openkal = { path = \"$2\" }|" "$1" > "$1.next"
mv "$1.next" "$1"
grep -q "path = \"$2\"" "$1" \
|| { echo "::error::$1 was not substituted"; exit 1; }
}
subst mcpp.toml .spec
subst examples/hello/mcpp.toml ../../.spec
- name: Install OVMF and qemu
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends ovmf qemu-system-x86

Expand Down Expand Up @@ -234,6 +285,9 @@ jobs:
run:
shell: bash
env:
# The specification is taken from the branch of the same name where one
# exists, so a change spanning both repositories is reviewed as a whole.
OPENKAL_BRANCH: ${{ github.head_ref || github.ref_name }}
MCPP_VERSION: 2026.8.27.1
XLINGS_VERSION: v2026.8.17.2
XLINGS_NON_INTERACTIVE: '1'
Expand Down Expand Up @@ -376,6 +430,53 @@ jobs:
echo "under review: $("$built" --version) (from $MCPP_SOURCE_REF)"
fi


# ⚠️⚠️ A JOB THAT RESOLVES THE PUBLISHED SPECIFICATION CANNOT REVIEW A
# CHANGE TO ONE.
#
# Another step in this file already clones the specification and
# substitutes it, and that made the arrangement look complete. It was not:
# the steps below reached `openkal' BY VERSION, so a version under review
# --- which by definition is not published --- failed them with
#
# E_NOT_FOUND: package 'compat.openkal@0.9.0' not found in the
# synced index ... the index is current, so this name is either
# wrong or not published yet
#
# ⭐⭐ THE UNIT IS THE JOB, NOT THE REPOSITORY. Measured 2026-08-28 across
# the eight repositories of this ecosystem while one change spanned all of
# them: seven jobs in three repositories had this shape, and every one of
# those repositories ALSO had a job that substituted correctly --- which is
# what made the gap invisible to a check done a repository at a time.
#
# These jobs are green on `main` and can only be green there, because
# there the published version is the one under test. That is the property
# that makes the defect silent: it is not a check that fails, it is a
# check that cannot run at the only time it would have something to say.
- name: Point at the specification's working tree
run: |
set -euo pipefail
git clone --quiet https://github.com/mcpplibs/openkal .spec
if git -C .spec rev-parse --verify --quiet "origin/$OPENKAL_BRANCH" > /dev/null; then
git -C .spec checkout --quiet "origin/$OPENKAL_BRANCH"
echo "the specification is at $OPENKAL_BRANCH"
else
echo "the specification has no $OPENKAL_BRANCH; its default branch is used"
fi
# ⚠️ EVERY MANIFEST THAT NAMES IT, AND BY THE SAME FORM. mcpp refuses
# a graph in which one package reaches a dependency by version and
# another by path, so substituting only the root leaves the build
# refusing for a second reason instead of the first.
# ⚠️ NOT `sed -i'. This step runs on macOS and on Windows too, and
# BSD sed requires an argument to -i that GNU sed refuses. A temporary
# file is the spelling that holds on all three.
subst() { # subst <file> <relative-path-to-the-specification>
sed "s|^openkal = .*$|openkal = { path = \"$2\" }|" "$1" > "$1.next"
mv "$1.next" "$1"
grep -q "path = \"$2\"" "$1" \
|| { echo "::error::$1 was not substituted"; exit 1; }
}
subst mcpp.toml .spec
- name: The EFI application cross-builds
run: |
# ⚠️ TWICE, AND THE FIRST IS ALLOWED TO FAIL — every row of this
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@ target/
.mcpp/
compile_commands.json
mcpp.lock

# A working tree of the specification placed beside the sources. No trailing
# slash: the pattern must match a symbolic link to one as well as a directory.
.spec
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ target = "x86_64-windows-gnu"
ldflags = ["-nostdlib", "-Wl,--subsystem,10", "-Wl,-e,efi_main"]

[dependencies]
openkal = "0.5.1"
openkal-uefi = "0.1.0"
openkal = "0.9.0"
openkal-uefi = "0.3.0"
```

## ⚠️ The target is `x86_64-windows-gnu`, and that is not a workaround
Expand Down
2 changes: 1 addition & 1 deletion examples/hello/mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ ldflags = ["-nostdlib", "-Wl,--subsystem,10", "-Wl,-e,efi_main"]
# That is the right refusal --- two forms can name two different trees --- and
# it means an example inside a repository follows that repository's own
# declaration while a change is in flight.
openkal = "0.8.0"
openkal = "0.9.0"
openkal-uefi = { path = "../.." }
4 changes: 2 additions & 2 deletions mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
[package]
namespace = "mcpplibs"
name = "openkal-uefi"
version = "0.2.0"
version = "0.3.0"
description = "An implementation of openkal on UEFI Boot Services, for applications the firmware loads before an operating system exists"
license = "Apache-2.0"

Expand All @@ -32,7 +32,7 @@ authors = ["mcpplibs"]
repo = "https://github.com/mcpplibs/openkal-uefi"

[dependencies]
openkal = "0.8.0"
openkal = "0.9.0"

[build]
flags = [
Expand Down
28 changes: 18 additions & 10 deletions src/kal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ efi_system_table* g_st = nullptr;
// window rather than a cache.
constexpr unsigned kChunk = 128;

kal_io_result write_to(efi_simple_text_output_protocol* out,
kal_intptr write_to(efi_simple_text_output_protocol* out,
const unsigned char* p, kal_uintptr n) {
if (!out) return kal_io_result{0, kal_err_io};
if (!out) return -kal_err_io;
efi_char16 buf[kChunk * 2 + 1]; // worst case: every byte becomes CR LF
kal_uintptr done = 0;
while (done < n) {
Expand All @@ -63,9 +63,9 @@ kal_io_result write_to(efi_simple_text_output_protocol* out,
}
buf[w] = 0;
if (out->output_string(out, buf) != EFI_SUCCESS)
return kal_io_result{done, kal_err_io};
return done ? static_cast<kal_intptr>(done) : -kal_err_io;
}
return kal_io_result{done, kal_ok};
return static_cast<kal_intptr>(done);
}

} // namespace
Expand Down Expand Up @@ -112,13 +112,13 @@ kal_stream kal_stdin (void) { return kal_stream{kStdin}; }
kal_stream kal_stdout(void) { return kal_stream{kStdout}; }
kal_stream kal_stderr(void) { return kal_stream{kStderr}; }

kal_io_result kal_stream_write(kal_stream s, const void* buf, kal_uintptr n) {
if (!g_st) return kal_io_result{0, kal_err_io};
kal_intptr kal_stream_write(kal_stream s, const void* buf, kal_uintptr n) {
if (!g_st) return -kal_err_io;
efi_simple_text_output_protocol* out =
s.h == kStdout ? g_st->con_out
: s.h == kStderr ? (g_st->std_err ? g_st->std_err : g_st->con_out)
: nullptr;
if (!out) return kal_io_result{0, kal_err_invalid};
if (!out) return -kal_err_invalid;
return write_to(out, static_cast<const unsigned char*>(buf), n);
}

Expand All @@ -128,9 +128,9 @@ kal_io_result kal_stream_write(kal_stream s, const void* buf, kal_uintptr n) {
// loses every key that is not a plain character. Returning "no bytes, no error"
// is end of input, which is the truthful reading of a console this
// implementation does not attempt to interpret.
kal_io_result kal_stream_read(kal_stream s, void*, kal_uintptr) {
if (s.h != kStdin) return kal_io_result{0, kal_err_invalid};
return kal_io_result{0, kal_ok};
kal_intptr kal_stream_read(kal_stream s, void*, kal_uintptr) {
if (s.h != kStdin) return -kal_err_invalid;
return 0;
}

// The console is unbuffered by this implementation; the bytes were handed to
Expand All @@ -153,6 +153,14 @@ kal_uintptr kal_stream_props(kal_stream s) {
// a stricter alignment is satisfied by over-allocating and storing the original
// pointer immediately before the aligned address — the same technique a C
// library uses for `aligned_alloc` where the platform lacks one.
// The quantum this environment allocates and protects memory in.
//
// Firmware hands out memory in pages of four kilobytes --- that is the unit of
// `AllocatePages', which is what this allocator draws on --- and it applies no
// protection of its own. The coarser of the two is therefore the page, and a
// caller that rounds to it is never wrong.
kal_uintptr kal_memory_granularity(void) { return 4096; }

void* kal_alloc(kal_uintptr size, kal_uintptr align) {
if (!g_st) return nullptr;
if (size == 0) size = 1;
Expand Down
17 changes: 17 additions & 0 deletions src/version.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include <openkal/version.h>

// What this implementation says about itself before it is used. Both answers
// are constants; see openkal/version.h for why they belong to no interface.
extern "C" {

kal_u64 kal_version(void) { return KAL_VERSION; }

// Firmware supplies a console and a page allocator and nothing else this
// specification has an interface for. `openkal.env' is absent because a program
// started by firmware receives no arguments this implementation can report, and
// reporting none would be a claim rather than an absence.
kal_u64 kal_interfaces(void) {
return KAL_IFACE_ABORT | KAL_IFACE_STREAM | KAL_IFACE_MEMORY;
}

}
Loading