From 78b6e3a1ba9cc1eeba15541b4b8b6a0abddf6283 Mon Sep 17 00:00:00 2001 From: speak-agent Date: Thu, 27 Aug 2026 10:13:57 +0800 Subject: [PATCH 1/6] =?UTF-8?q?ci:=20=E6=8A=8A=E5=BC=95=E5=AF=BC=E9=98=B6?= =?UTF-8?q?=E6=AE=B5=E7=BB=91=E5=AE=9A=E6=8C=87=E5=90=8D=E7=9A=84=20glibc?= =?UTF-8?q?=20=E8=BD=BD=E8=8D=B7=E8=A3=85=E7=BB=99=E5=AE=83(=E8=BF=87?= =?UTF-8?q?=E6=B8=A1=E6=AD=A5=E9=AA=A4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `xim:glibc` 的 `latest` 从 `2.44` 移到了 `2.44.2`。载荷目录按请求**解析成**的版本 命名,而 RuntimeBinding 带的是**声明的**版本 —— 而一个已发布的 mcpp vendor 进自己 沙箱的那份 xlings 仍然声明 `2.44`。于是干净机器装出 `2.44.2`,工具链 fixup 要 `2.44`,构建在编译任何东西之前就停住: error: selected RuntimeBinding glibc@2.44 requires payload '.../xpkgs/xim-x-glibc/2.44', but it is not installed ⚠️ 在每一台**新**机器上出现,在任何已存在的机器上都不出现 —— 所以开发机看不见。 索引的 `pkgs/g/glibc.lua` 把这次失败逐字记着,并给出规则: 「The index is DATA and the client is a PROGRAM: the consumer ships first。」 ## ⚠️ 我先试过 bump xlings,撤回了 把 `XLINGS_VERSION` 提到 2026.8.27.1 会让**沙箱里那份** xlings 被换掉 (mcpp 的 `acquire_xlings_binary` 在 pin 前移时更新它),绑定于是**前移**到 `glibc@2.44.2`,而已经装好的是 `2.44` —— 请求比已装的**更新**。那是更糟的一侧: 接受一个更旧的载荷去回答更新的请求,等于静默用上索引明说 「IT IS NOT THE ONE TO INSTALL」的那份(带 mcpp#484 的 preload 缺陷)。 ⇒ 保持 xlings 不动,把缺的那个载荷直接装上。 ⭐ 待 mcpp 2026.8.27.1(mcpp-community/mcpp#515)发布后移除:它接受 「版本精化了请求」的已装载荷,从它引导就不需要这一步。 --- .github/workflows/ci.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fc21c11..cea0142 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,6 +82,34 @@ jobs: 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. From 58b9e17b1098695dc61d8931c305852a4457de6d Mon Sep 17 00:00:00 2001 From: speak-agent Date: Thu, 27 Aug 2026 13:28:30 +0800 Subject: [PATCH 2/6] Implement openkal.terminal, and follow openkal 0.8 The console operations this environment offers map onto the interface directly: ENABLE_LINE_INPUT is what openkal.terminal calls line editing and ENABLE_ECHO_INPUT is what it calls echo, which is the same division the interface draws. GetConsoleMode is already what kal_stream_props asks, so a stream that is not a console is reported as unsupported by the same test. The mode is read, modified and written rather than written alone. The console mode carries processed input, mouse input, window input and virtual terminal processing, none of which the interface names; a word composed from the two positions alone would turn all of them off, and a program that asked only to stop echoing would find its console changed in ways it did not ask for. kal_terminal_size reports the WINDOW and not the buffer. This environment's screen buffer may be taller than the window that shows it, and a program drawing a full screen against the buffer's height would scroll its own output away. The other four interfaces version 0.8 adds are not provided, which clause 6.1 makes an absence at the link rather than a deviation. --- mcpp.toml | 4 +- src/terminal.cpp | 107 +++++++++++++++++++++++++++++++++++++++++++++++ src/win32.h | 16 +++++++ 3 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 src/terminal.cpp diff --git a/mcpp.toml b/mcpp.toml index 2a6812b..b0acfda 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -1,7 +1,7 @@ [package] namespace = "mcpplibs" name = "openkal-windows" -version = "0.1.5" +version = "0.2.0" description = "An implementation of openkal for Windows, written on the Win32 interfaces and the object manager beneath them, using no C runtime symbol." license = "Apache-2.0" @@ -18,7 +18,7 @@ authors = ["mcpplibs"] repo = "https://github.com/mcpplibs/openkal-windows" [dependencies] -openkal = "0.7.0" +openkal = { git = "https://github.com/mcpplibs/openkal", branch = "feat/openkal-0.8" } # The package contributes definitions and no modules. The interface it # implements is declared by the specification package, which this package diff --git a/src/terminal.cpp b/src/terminal.cpp new file mode 100644 index 0000000..2fb9e5c --- /dev/null +++ b/src/terminal.cpp @@ -0,0 +1,107 @@ +#include "win32.h" +#include + +// openkal.terminal upon this environment's console operations. +// +// THE TWO POSITIONS THIS INTERFACE DEFINES ARE TWO CONSOLE FLAGS, AND THEY ARE +// NOT THE SAME TWO A UNIX TERMINAL HAS. This environment assembles lines under +// ENABLE_LINE_INPUT and shows what is typed under ENABLE_ECHO_INPUT, which is +// the same division openkal.terminal draws; what differs is that the flags live +// on the INPUT handle and this interface takes a stream. A caller asking about +// the output handle is answered for the output handle, which reports neither +// position, and that is correct rather than a limitation: the mode of an output +// console is not what the interface asks about. + +namespace { + +void* handle_of(kal_stream s) { return reinterpret_cast(s.h); } +bool valid(void* h) { return h != nullptr && h != INVALID_HANDLE_VALUE; } + +// This environment's console input flags. +constexpr DWORD enable_line_input = 0x0002; +constexpr DWORD enable_echo_input = 0x0004; + +} // namespace + +extern "C" { + +int kal_terminal_get_mode(kal_stream s, kal_uintptr* mode) { + if (mode == nullptr) return kal_err_invalid; + void* h = handle_of(s); + if (!valid(h)) return kal_err_invalid; + + DWORD m = 0; + // Reading a console's mode succeeds for a console and fails otherwise, which + // is the same enquiry kal_stream_props performs. A stream that is not a + // console is therefore reported as unsupported rather than as an error of + // this environment. + if (!GetConsoleMode(h, &m)) return kal_err_not_supported; + + kal_uintptr out = 0; + if ((m & enable_line_input) != 0) out |= KAL_TERM_LINE_EDIT; + if ((m & enable_echo_input) != 0) out |= KAL_TERM_ECHO; + *mode = out; + return kal_ok; +} + +int kal_terminal_set_mode(kal_stream s, kal_uintptr mode) { + void* h = handle_of(s); + if (!valid(h)) return kal_err_invalid; + + // READ, MODIFY, WRITE. The console mode carries processed input, mouse + // input, window input and virtual terminal processing, none of which this + // interface names. Writing a word composed from the two positions alone + // would turn all of them off, and a program that asked only to stop echoing + // would find its console changed in ways it did not ask for. + DWORD m = 0; + if (!GetConsoleMode(h, &m)) return kal_err_not_supported; + + if ((mode & KAL_TERM_LINE_EDIT) != 0) m |= enable_line_input; + else m &= ~enable_line_input; + if ((mode & KAL_TERM_ECHO) != 0) m |= enable_echo_input; + else m &= ~enable_echo_input; + + // A position this implementation does not distinguish is ignored rather than + // refused, which clause 6.2 requires of a word. + if (!SetConsoleMode(h, m)) return kal_err_not_supported; + return kal_ok; +} + +int kal_terminal_size(kal_stream s, kal_uintptr* cols, kal_uintptr* rows) { + if (cols == nullptr || rows == nullptr) return kal_err_invalid; + void* h = handle_of(s); + if (!valid(h)) return kal_err_invalid; + + CONSOLE_SCREEN_BUFFER_INFO_ info{}; + // Both outputs are left untouched on failure, which the interface requires. + if (!GetConsoleScreenBufferInfo(h, &info)) return kal_err_not_supported; + + // THE WINDOW AND NOT THE BUFFER. This environment's screen buffer may be + // taller than the window that shows it, and a program drawing a full screen + // against the buffer's height would scroll its own output away. The window + // is what a caller asking for the size of the display means. + *cols = static_cast(info.srWindow.Right - info.srWindow.Left + 1); + *rows = static_cast(info.srWindow.Bottom - info.srWindow.Top + 1); + return kal_ok; +} + +kal_uintptr kal_terminal_props(kal_stream s) { + void* h = handle_of(s); + if (!valid(h)) return 0; + + kal_uintptr p = 0; + + DWORD m = 0; + if (GetConsoleMode(h, &m)) p |= KAL_TERM_PROP_MODE; + + // Asked for rather than derived from the first: an input console answers the + // mode and not the size, and an output console answers the size and not the + // mode. Deriving either from the other would make the word claim a facility + // the next call refuses. + CONSOLE_SCREEN_BUFFER_INFO_ info{}; + if (GetConsoleScreenBufferInfo(h, &info)) p |= KAL_TERM_PROP_SIZE; + + return p; +} + +} // extern "C" diff --git a/src/win32.h b/src/win32.h index cf71357..3ad2a22 100644 --- a/src/win32.h +++ b/src/win32.h @@ -245,6 +245,22 @@ OKW_IMPORT DWORD OKW_API GetLastError(void); OKW_IMPORT DWORD OKW_API GetFileType(HANDLE); OKW_IMPORT BOOL OKW_API SetHandleInformation(HANDLE, DWORD, DWORD); OKW_IMPORT BOOL OKW_API GetConsoleMode(HANDLE, DWORD*); +OKW_IMPORT BOOL OKW_API SetConsoleMode(HANDLE, DWORD); + +// The console's dimensions, for openkal.terminal. The structure is this +// environment's and is declared here for the reason every other structure in +// this file is: it belongs to the environment rather than to a C library, and +// this implementation has none to take it from. +struct COORD_ { short X; short Y; }; +struct SMALL_RECT_ { short Left; short Top; short Right; short Bottom; }; +struct CONSOLE_SCREEN_BUFFER_INFO_ { + COORD_ dwSize; + COORD_ dwCursorPosition; + unsigned short wAttributes; + SMALL_RECT_ srWindow; + COORD_ dwMaximumWindowSize; +}; +OKW_IMPORT BOOL OKW_API GetConsoleScreenBufferInfo(HANDLE, CONSOLE_SCREEN_BUFFER_INFO_*); OKW_IMPORT BOOL OKW_API ReadFile(HANDLE, LPVOID, DWORD, DWORD*, OVERLAPPED*); OKW_IMPORT BOOL OKW_API WriteFile(HANDLE, LPCVOID, DWORD, DWORD*, OVERLAPPED*); From 2db18a6a8d79f77338e20d33f4535ca98820c95e Mon Sep 17 00:00:00 2001 From: speak-agent Date: Thu, 27 Aug 2026 14:00:58 +0800 Subject: [PATCH 3/6] Provide the three operations openkal 0.8 adds to openkal.process ADDING TO AN EXISTING INTERFACE OBLIGES EVERY IMPLEMENTATION OF IT, and adding a new interface obliges none. Clause 6.1 makes an interface a backend does not provide absent at the link and not a deviation; it makes one provided IN PART a deviation. The five interfaces version 0.8 adds are therefore free to decline, and the three names added to openkal.process are not. The specification's own surface checker said so before anything else noticed: openkal.process is provided in part: 3 of 8 names are not exported -- kal_process_channel kal_process_channel_close kal_process_spawn_with kal_process_channel is CreatePipe. THIS ENVIRONMENT DECIDES INHERITANCE PER HANDLE AND NOT PER EXEC, which is the opposite of the other two: there every handle is inherited unless marked otherwise, so those implementations mark both ends close-on-exec and let the spawn place the far one. Here the default is not to inherit, so the far end is created inheritable and the near end is withdrawn afterwards --- otherwise the started program would hold both ends and the writer would never observe the end of input. kal_process_spawn_with refuses a non-empty set of grants, and the refusal is the honest answer rather than a gap. A preopened directory is a handle a started program reads back by NUMBER, and this environment has no numbering: a handle crosses a spawn by being inheritable, and the started program learns of it through a mechanism the parent arranges. There is no correspondence to descriptor three. Clause 6.2 is what makes that conforming: the operation exists, reports kal_err_not_supported, and the property word does not claim KAL_PROCESS_PROP_GRANT_DIR --- so a caller learns from the word what it would otherwise learn from a failed call. A count of zero is still answered, by the ordinary spawn, because a program with no preopens is what this environment starts anyway. --- src/process.cpp | 81 ++++++++++++++++++++++++++++++++++++++++++++++++- src/win32.h | 3 ++ 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/src/process.cpp b/src/process.cpp index 7049e72..8af7600 100644 --- a/src/process.cpp +++ b/src/process.cpp @@ -177,6 +177,81 @@ int kal_process_spawn(kal_dir base, return kal_ok; } +// A channel: a pair of streams of which one end is meant to cross a spawn. +// +// THIS ENVIRONMENT DECIDES INHERITANCE PER HANDLE AND NOT PER EXEC, which is the +// opposite of the other two and is why the far end is created inheritable while +// the near end is not. On a descriptor system every handle is inherited unless +// marked otherwise, so those implementations mark the ends close-on-exec and let +// the spawn place the far one deliberately. Here the default is not to inherit, +// so the far end must be marked to be inheritable and the near end must be left +// alone --- otherwise the started program would hold both ends and the writer +// would never observe the end of input. +int kal_process_channel(kal_stream* mine, kal_stream* theirs) { + if (mine == nullptr || theirs == nullptr) return kal_err_invalid; + + SECURITY_ATTRIBUTES sa{}; + sa.nLength = sizeof sa; + sa.bInheritHandle = TRUE; + + HANDLE reading = nullptr, writing = nullptr; + if (!CreatePipe(&reading, &writing, &sa, 0)) + return okw::translate_win32(GetLastError()); + + // The near end is withdrawn from inheritance after the fact, because + // CreatePipe applies one set of attributes to both. + SetHandleInformation(reading, HANDLE_FLAG_INHERIT, 0); + + // Bare handles rather than packed ones, because openkal.stream's transfer + // operations take what this environment takes. kal_fs_stream reports a + // file's stream the same way and for the same reason. + *mine = kal_stream{ reinterpret_cast(reading) }; + *theirs = kal_stream{ reinterpret_cast(writing) }; + return kal_ok; +} + +void kal_process_channel_close(kal_stream s) { + void* h = reinterpret_cast(s.h); + if (h == nullptr || h == INVALID_HANDLE_VALUE) return; + // The standard streams are borrowed. Closing one through this operation + // would take a stream away from the whole program. + if (h == GetStdHandle(STD_INPUT_HANDLE) || + h == GetStdHandle(STD_OUTPUT_HANDLE) || + h == GetStdHandle(STD_ERROR_HANDLE)) return; + CloseHandle(h); +} + +// Starting a program that receives exactly the directories named. +// +// ⚠️ NOT PROVIDED, AND THE REFUSAL IS THE HONEST ANSWER RATHER THAN A GAP. A +// preopened directory is a handle a started program reads back through +// kal_fs_preopen by NUMBER, and this environment has no numbering: a handle +// crosses a spawn by being inheritable, and the started program learns of it +// through a mechanism the parent has to arrange itself. There is no +// correspondence here to descriptor three. +// +// Clause 6.2 is what makes the refusal conforming rather than a deviation: the +// operation exists, reports kal_err_not_supported, and the property word does +// not claim KAL_PROCESS_PROP_GRANT_DIR. A caller therefore learns from the word +// what it would otherwise learn from a failed call. +int kal_process_spawn_with(kal_dir base, + const char* path, kal_uintptr path_len, + const char** argv, const kal_uintptr* argv_lens, kal_uintptr argc, + const char** envp, const kal_uintptr* envp_lens, kal_uintptr envc, + const kal_spawn_streams* streams, + const kal_preopen* grants, kal_uintptr grant_count, + kal_process* out) { + // A count of zero asks for a program with no preopens, which this + // environment gives a started program anyway --- it has none to pass. That + // request is therefore answerable, and is answered by the ordinary spawn. + if (grant_count == 0) + return kal_process_spawn(base, path, path_len, + argv, argv_lens, argc, + envp, envp_lens, envc, streams, out); + (void)grants; + return kal_err_not_supported; +} + int kal_process_wait(kal_process p, int* status, int* terminated) { void* h = okw::unpack(p.h); if (!h) return kal_err_invalid; @@ -212,8 +287,12 @@ void kal_process_close(kal_process p) { if (h) { okw::retire(p.h); CloseHandle(h); } } +// KAL_PROCESS_PROP_GRANT_DIR is deliberately absent: kal_process_spawn_with +// refuses a non-empty set of grants here, and a word claiming a facility the +// next call refuses is the disagreement clause 6.2 exists to prevent. const kal_uintptr kal_process_props = KAL_PROCESS_PROP_TERMINATE | KAL_PROCESS_PROP_STREAM_PASSING - | KAL_PROCESS_PROP_EXIT_STATUS; + | KAL_PROCESS_PROP_EXIT_STATUS + | KAL_PROCESS_PROP_CHANNEL; } diff --git a/src/win32.h b/src/win32.h index 3ad2a22..7e0199f 100644 --- a/src/win32.h +++ b/src/win32.h @@ -244,6 +244,9 @@ OKW_IMPORT BOOL OKW_API CloseHandle(HANDLE); OKW_IMPORT DWORD OKW_API GetLastError(void); OKW_IMPORT DWORD OKW_API GetFileType(HANDLE); OKW_IMPORT BOOL OKW_API SetHandleInformation(HANDLE, DWORD, DWORD); +// For kal_process_channel. The security attributes decide whether the ends are +// inheritable, which is what makes one of them able to cross a spawn. +OKW_IMPORT BOOL OKW_API CreatePipe(HANDLE*, HANDLE*, SECURITY_ATTRIBUTES*, DWORD); OKW_IMPORT BOOL OKW_API GetConsoleMode(HANDLE, DWORD*); OKW_IMPORT BOOL OKW_API SetConsoleMode(HANDLE, DWORD); From 6fda245c02aeec3c41c4b1b5e5e6f3def23d3724 Mon Sep 17 00:00:00 2001 From: speak-agent Date: Thu, 27 Aug 2026 15:34:57 +0800 Subject: [PATCH 4/6] Export the three names the new declarations call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `port/*.def` is an explicit list, and the import library generated from it contains exactly those names. I added three declarations to `src/win32.h` for openkal 0.8 and did not add the names, so the import library this package generates for its consumers was missing them. ⚠️ AND THIS PACKAGE'S OWN CI STAYED GREEN, because this package does not link. The failure appeared one repository away, in openkal-llvm-runtime's cross-build: ld.lld: error: undefined symbol: __declspec(dllimport) CreatePipe ld.lld: error: undefined symbol: __declspec(dllimport) GetConsoleScreenBufferInfo ld.lld: error: undefined symbol: __declspec(dllimport) SetConsoleMode which reads as a defect in the consumer and is nothing of the kind. ⭐ IT WAS FOUND BY COMPARING IMPORT LIBRARIES, NOT BY READING THE ERROR. Every copy of a vendor `libkernel32.a` on the machine had all three; the one generated here had `GetConsoleMode` and not `SetConsoleMode`, which is not a shape a vendor's library takes and pointed straight at the generated list. The check added here is the one that would have caught it: every name `src/win32.h` declares must be exported by some `.def`. It belongs in this repository, where the two lists are, rather than in the consumer that trips over the difference. Measured both ways: removing `CreatePipe` from the list makes it red and names the symbol; the lists as committed are 42 declared, 49 exported, none missing. A denominator is asserted on both sides, since two empty lists have an empty difference too. --- .github/workflows/ci.yml | 42 ++++++++++++++++++++++++++++++++++++++++ port/kernel32.def | 3 +++ 2 files changed, 45 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cea0142..ba56c89 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -163,6 +163,48 @@ jobs: 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 + declared = set(re.findall(r'OKW_IMPORT\s+\w+\s+OKW_API\s+(\w+)\s*\(', + open("src/win32.h").read())) + exported = set() + for f in glob.glob("port/*.def"): + body = open(f).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 }}' diff --git a/port/kernel32.def b/port/kernel32.def index 92175b1..2a5e1ee 100644 --- a/port/kernel32.def +++ b/port/kernel32.def @@ -6,12 +6,14 @@ LIBRARY KERNEL32.dll EXPORTS CloseHandle CreateFileW +CreatePipe CreateProcessW CreateThread FlushFileBuffers FreeEnvironmentStringsW GetCommandLineW GetConsoleMode +GetConsoleScreenBufferInfo GetCurrentDirectoryW GetCurrentProcess GetCurrentThreadId @@ -31,6 +33,7 @@ MultiByteToWideChar QueryPerformanceCounter QueryPerformanceFrequency ReadFile +SetConsoleMode SetFilePointerEx SetHandleInformation Sleep From f36959489635c22777c19ca4e8688389ecc16bc4 Mon Sep 17 00:00:00 2001 From: speak-agent Date: Thu, 27 Aug 2026 15:42:09 +0800 Subject: [PATCH 5/6] ci: name the encoding the check reads its sources in Python opens a file with the platform's default encoding, which on the Windows runner is cp1252, and these sources are UTF-8: UnicodeDecodeError: 'charmap' codec can't decode byte 0x90 in position 90 A check that reads source files must say what they are encoded in, or it reports on the runner's locale rather than on the sources. The byte it stopped at is in a comment marker, so the check would have passed on a repository whose comments happened to be ASCII and failed on this one --- which is the least useful place for a difference to appear. Both reads name utf-8. Rehearsed locally under cp1252: 42 declared, 49 exported, none missing. --- .github/workflows/ci.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ba56c89..e7abb44 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -182,11 +182,18 @@ jobs: 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").read())) + open("src/win32.h", encoding="utf-8").read())) exported = set() for f in glob.glob("port/*.def"): - body = open(f).read().split("EXPORTS", 1) + 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(';')} From 1af1258a36587970bddcf1e2a4c0068647080466 Mon Sep 17 00:00:00 2001 From: speak-agent Date: Thu, 27 Aug 2026 17:36:00 +0800 Subject: [PATCH 6/6] Name the released specification and the released engine openkal 0.8.0 is published, so the manifest names it by version rather than by branch. A branch is how a change spanning these repositories is developed and is not a form a published package may carry: a consumer resolving from the index would be handed a reference that moves. The engine pin moves to mcpp 2026.8.27.1 and the repository variable MCPP_SOURCE_REF is cleared, so what this repository tests is the engine a user installs. --- .github/workflows/ci.yml | 2 +- mcpp.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e7abb44..ef57f13 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ on: default: "" env: MCPP_SOURCE_REF: ${{ github.event.inputs.mcpp_ref || vars.MCPP_SOURCE_REF }} - MCPP_VERSION: 2026.8.26.2 + MCPP_VERSION: 2026.8.27.1 XLINGS_VERSION: v2026.8.17.2 XLINGS_NON_INTERACTIVE: '1' diff --git a/mcpp.toml b/mcpp.toml index b0acfda..ffc8aa0 100644 --- a/mcpp.toml +++ b/mcpp.toml @@ -18,7 +18,7 @@ authors = ["mcpplibs"] repo = "https://github.com/mcpplibs/openkal-windows" [dependencies] -openkal = { git = "https://github.com/mcpplibs/openkal", branch = "feat/openkal-0.8" } +openkal = "0.8.0" # The package contributes definitions and no modules. The interface it # implements is declared by the specification package, which this package