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
75 changes: 75 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ name: CI
# (checks/hosted; mcpp docs/24)
# platform a platform module used outside `if constexpr' for its platform
# fails to LINK, and the same use in a discarded branch links (R5)
# emscripten the tests that start no program, built for wasm32-emscripten and
# run under node (checks/emscripten); a program that starts one
# fails at LINK naming the openkal symbol (checks/absent)
#
# The library is built with the released mcpp, 2026.10.27.1 or later: the
# first that selects a platform (SPEC-013), which the ok images and
Expand Down Expand Up @@ -325,3 +328,75 @@ jobs:
echo "::endgroup::"
done
exit $failed

# ── EMSCRIPTEN (`wasm32-emscripten`) ────────────────────────────────────────
#
# A wasm module over Emscripten's C library and openkal-emscripten, run under
# node. The toolchain is the target's (emsdk 6.0.9) and the runner is node
# from the payload's descriptor. openkal-emscripten has no openkal.process, so
# the two suites that start programs are not built; that a program which
# starts one fails at link, naming the symbol, is asserted instead.
emscripten:
name: emscripten (node) ${{ matrix.profile }}
runs-on: ubuntu-24.04
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
profile: [dev, release]
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v4
- name: Install xlings and mcpp
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"
export PATH="$HOME/.xlings/subos/current/bin:$PATH"
for attempt in 1 2 3 4 5 6; do
xlings update > /dev/null 2>&1 || true
if xlings install "mcpp@$MCPP_VERSION" -y -g; then break; fi
[ "$attempt" = 6 ] && { echo "::error::mcpp could not be installed"; exit 1; }
sleep 60
done
mcpp --version
mcpp self config --mirror GLOBAL
if [ -n "${MCPP_SOURCE_REF:-}" ]; then
src="$RUNNER_TEMP/mcpp-src"
git clone --quiet --depth 1 --branch "$MCPP_SOURCE_REF" https://github.com/mcpp-community/mcpp.git "$src"
rm -f "$src/.xlings.json"
( cd "$src" && mcpp build --release )
built=$(find "$src/target" -type f -name mcpp | head -1)
[ -n "$built" ] || { echo "::error::mcpp did not build"; exit 1; }
echo "$(cd "$(dirname "$built")" && pwd)" >> "$GITHUB_PATH"
fi
- name: Every test of the package that starts no program has its counterpart here
run: |
for t in tests/*_test.cpp; do
n=$(basename "$t")
case "$n" in process_test.cpp|ipc_test.cpp) continue ;; esac
[ -f "checks/emscripten/tests/$n" ] || { echo "::error::checks/emscripten/tests has no $n"; exit 1; }
done
- name: Tests, under node
working-directory: checks/emscripten
run: |
set -euo pipefail
mcpp test --target wasm32-emscripten --profile '${{ matrix.profile }}' 2>&1 | tee tests.log
for name in platform_test fs_test dirs_terminal_test linux_test library_test; do
grep -q "^$name \.\.\. ok" tests.log || { echo "::error::$name did not run or did not pass"; exit 1; }
done
- name: The compile-time constants of Emscripten (R3)
run: ( cd checks/constants && mcpp build --target wasm32-emscripten ) || { echo "::error::a constant does not hold"; exit 1; }
- name: A program that names itself fails at link, naming the openkal symbol
run: |
if ( cd checks/absent && mcpp build --target wasm32-emscripten ) > absent.log 2>&1; then
cat absent.log
echo "::error::a program using openkal.process linked for Emscripten"
exit 1
fi
grep -E "^(error|wasm-ld)" absent.log | head -5
grep -q "undefined symbol: kal_process_" absent.log \
|| { cat absent.log; echo "::error::the build did not fail naming an openkal.process symbol"; exit 1; }
echo " ok the link names the symbol this platform does not have"
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,38 @@
# Changelog

## 0.2.2

- **Emscripten (`wasm32-emscripten`).** The package builds for a wasm module
over Emscripten's C library and openkal-emscripten 0.6.0, under node or in a
page. It adds `platform::emscripten`, `architecture::wasm32` and
`is_emscripten`, and selects `src/targets/emscripten.cppm`,
`src/arch/wasm32.cppm`, `src/emscripten/` and the C library's part of
`src/posix/fs.cpp`. No platform module is defined. A program that starts
another fails at link, naming the openkal symbol (`checks/absent`).
- `interrupt` reports that no request to end is delivered. Its
implementation over openkal's stop word moves to `src/kal/interrupt.cpp`,
which every other target selects.
- `machine` names `wasm32`, reports the processors Emscripten counts and
does not know the memory.
- A volume's kind is the file system Emscripten mounted there (`memfs`,
`nodefs`, …). Standard output is diverted with the C library's
descriptors, which openkal-emscripten's streams are.
- CI builds the tests that do not start programs for the target and runs them
under node (`checks/emscripten`, debug and release), along with the
compile-time constants and the R5 checks.
- **`fs::read` reads into the string, not through a 64 KiB array on the
stack.** That array was all of a wasm module's default stack (65536 bytes)
and overflowed it.
- **An enquiry asks for the kind it reads absence from.** `stamp_of` and
`identity_of` asked openkal for the size, the time or the identity, and then
read the kind. An implementation fills only the fields it is asked for
(openkal-emscripten does), so the kind stayed zero, which is
`kal_node_absent`, and an existing file was reported not found.
- **The tests ask the volume before taking a lock or renaming without
replacing,** and on a volume that does neither they check that the request is
refused and changes nothing. A test that names its scratch directory after
the program uses random bytes where no program is named.

## 0.2.1

- **The ok-image targets take their C library, C++ run-time and loader from
Expand Down
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ every system in CI.

| Module | Contents |
|---|---|
| `mcpplibs.os.platform` | `PLATFORM`, `ARCH`, `is_linux` / `is_macos` / `is_windows` / `is_posix`, `exe_suffix`, `path_list_separator`; `host()`, a constant expression on a native target |
| `mcpplibs.os.platform` | `PLATFORM`, `ARCH`, `is_linux` / `is_macos` / `is_windows` / `is_posix` / `is_emscripten`, `exe_suffix`, `path_list_separator`; `host()`, a constant expression on a native target |
| `mcpplibs.os.error` | `errc` (openkal's closed set of conditions), `error`, `result<T>` |
| `mcpplibs.os.env` | variables (names without case on Windows), `NAME=value` entries as overrides (`assignments`), arguments, the program's own image, lists of directories in the host's separator (`split_list`, `join_list`), PATH search with PATHEXT |
| `mcpplibs.os.fs` | `write_atomic`, `publish` (no replace), `append`, `sync`, `caps`, `lock`, links, `set_executable`, `stamp_of`, `identity_of`, `owner_of`, `writable`, `locate`, `absolute`, `names_a_place`, `canonical`, a bounded `read`; what a volume does, measured (`measure`, `volume_type`), and the operations that follow it (`place`, `exchange`); names in the host's rules without the file system: `generic`, `native`, `extended`, `is_absolute`, `same`, `within` |
Expand Down Expand Up @@ -125,6 +125,24 @@ for `<arch>-openkal`; this package declares none of them); the tests are built
as images in CI and run through okrun on four systems, and
`checks/constants` holds the compile-time facts of a native target.

## On Emscripten

Built for `wasm32-emscripten`, a program over this library is a wasm module
that runs under node or in a page, over Emscripten's C library and
openkal-emscripten. `os::PLATFORM` is `platform::emscripten`, `os::ARCH` is
`architecture::wasm32`, and `os::host()` is a constant expression, as on a
native target. Files are the module's own file system (MEMFS, one supplied
directory at `/`); `os::fs::caps()` says what it does: no locks, and no rename
that refuses to replace, so `publish` takes the name exclusively and
`rename(…, false)` is refused rather than tested and then done. A program is
not started and a library is not loaded: openkal-emscripten provides no
`openkal.process`, so a program that starts one, or names itself
(`process::self`), fails at link naming the symbol (`checks/absent`), and
`library::supported()` is false. No request to end is delivered
(`interrupt::available()` is false). The machine is named `wasm32` and its
memory is not known. The tests that do not start programs are built for the
target in CI and run under node (`checks/emscripten`).

## License

Apache-2.0
11 changes: 11 additions & 0 deletions checks/absent/mcpp.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Built for Emscripten only, and expected NOT to link: openkal-emscripten
# provides no `openkal.process', so a program that names itself or starts
# another fails at link naming the openkal symbol (openkal SPEC clause 6.2's
# second time), rather than at run time with an error a caller cannot tell
# from a condition. CI asserts the failure and the name.
[package]
name = "os-check-absent"
version = "0.1.0"

[dependencies]
os = { path = "../.." }
5 changes: 5 additions & 0 deletions checks/absent/src/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// A program that names itself: openkal.process, which Emscripten does not have.
import std;
import mcpplibs.os;

int main() { return mcpplibs::os::process::self().has_value() ? 0 : 1; }
9 changes: 5 additions & 4 deletions checks/constants/mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# The compile-time constants of a native target (R3; part 2 E29), checked when
# the program is compiled. An ok image has neither `exe_suffix' nor a constant
# `host()', so these facts are a build of their own on the native rows rather
# than part of tests/platform_test.cpp, which is also built as an image.
# The compile-time constants of a native target and of Emscripten (R3; part 2
# E29), checked when the program is compiled. An ok image has neither
# `exe_suffix' nor a constant `host()', so these facts are a build of their
# own on the native rows rather than part of tests/platform_test.cpp, which is
# also built as an image.
[package]
name = "os-check-constants"
version = "0.1.0"
Expand Down
5 changes: 3 additions & 2 deletions checks/constants/src/main.cpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// Built on every native row: each fact is a constant expression there.
// Built on every native row and for Emscripten: each fact is a constant
// expression there.
import std;
import mcpplibs.os;

namespace os = mcpplibs::os;

static_assert(os::host() == os::PLATFORM);
static_assert(os::is_linux + os::is_macos + os::is_windows == 1);
static_assert(os::is_linux + os::is_macos + os::is_windows + os::is_emscripten == 1);
static_assert(!os::is_image);
static_assert(os::exe_suffix == (os::is_windows ? ".exe" : ""));
static_assert(os::path_list_separator == (os::is_windows ? ';' : ':'));
Expand Down
13 changes: 13 additions & 0 deletions checks/emscripten/mcpp.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# The package's tests built for Emscripten (`wasm32-emscripten') and run under
# node: a wasm module over Emscripten's C library and openkal-emscripten.
#
# Every test of the package has its counterpart here except the two that start
# programs (process_test, ipc_test): openkal-emscripten provides no
# `openkal.process', so a program that starts one fails at link naming the
# symbol, which CI asserts instead (absent/).
[package]
name = "os-check-emscripten"
version = "0.1.0"

[dependencies]
os = { path = "../.." }
6 changes: 6 additions & 0 deletions checks/emscripten/tests/dirs_terminal_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// dirs_terminal_test.cpp of mcpplibs.os, compiled for Emscripten. The imports are restated
// because the dependency scan reads this file and not the one it includes.
#include "../../../tests/check.hpp"
import std;
import mcpplibs.os;
#include "../../../tests/dirs_terminal_test.cpp"
6 changes: 6 additions & 0 deletions checks/emscripten/tests/fs_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// fs_test.cpp of mcpplibs.os, compiled for Emscripten. The imports are restated
// because the dependency scan reads this file and not the one it includes.
#include "../../../tests/check.hpp"
import std;
import mcpplibs.os;
#include "../../../tests/fs_test.cpp"
6 changes: 6 additions & 0 deletions checks/emscripten/tests/library_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// library_test.cpp of mcpplibs.os, compiled for Emscripten. The imports are restated
// because the dependency scan reads this file and not the one it includes.
#include "../../../tests/check.hpp"
import std;
import mcpplibs.os;
#include "../../../tests/library_test.cpp"
6 changes: 6 additions & 0 deletions checks/emscripten/tests/linux_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// linux_test.cpp of mcpplibs.os, compiled for Emscripten. The imports are restated
// because the dependency scan reads this file and not the one it includes.
#include "../../../tests/check.hpp"
import std;
import mcpplibs.os;
#include "../../../tests/linux_test.cpp"
6 changes: 6 additions & 0 deletions checks/emscripten/tests/platform_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// platform_test.cpp of mcpplibs.os, compiled for Emscripten. The imports are restated
// because the dependency scan reads this file and not the one it includes.
#include "../../../tests/check.hpp"
import std;
import mcpplibs.os;
#include "../../../tests/platform_test.cpp"
25 changes: 24 additions & 1 deletion mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
namespace = "mcpplibs"
name = "os"
version = "0.2.1"
version = "0.2.2"
description = "mcpplibs.os: a C++ operating-system library over openkal --- processes, files, environment, terminals, directories and local IPC, with compile-time platform constants and a few platform modules."
license = "Apache-2.0"
authors = ["mcpplibs"]
Expand All @@ -23,6 +23,13 @@ openkal-macos = "0.15.1"
[target.windows.dependencies]
openkal-windows = "0.13.3"

# Emscripten (`wasm32-emscripten`): openkal written above the C library, with
# twelve of openkal's fifteen interfaces. `openkal.process', `openkal.exec' and
# `openkal.space' are absent, so a program that starts one fails at link
# naming the symbol; `openkal.task' is present in a `-pthread' link only.
[target.'cfg(os = "emscripten")'.dependencies]
openkal-emscripten = "0.6.0"

[targets.os]
kind = "lib"

Expand All @@ -34,6 +41,12 @@ kind = "lib"
[build]
sources = ["src/*.cppm", "src/*.cpp"]

# A request to end, over openkal's stop word and its wait: on every target that
# has `openkal.process' and `openkal.task'. Emscripten has neither, and its
# interrupt is src/emscripten/interrupt.cpp.
[target.'cfg(not(os = "emscripten"))'.build]
sources = ["src/kal/interrupt.cpp"]

# The architecture of the build, as a constant beside the platform's.
[target.'cfg(arch = "x86_64")'.build]
sources = ["src/arch/x86_64.cppm"]
Expand All @@ -44,6 +57,9 @@ sources = ["src/arch/aarch64.cppm"]
[target.'cfg(arch = "riscv64")'.build]
sources = ["src/arch/riscv64.cppm"]

[target.'cfg(arch = "wasm32")'.build]
sources = ["src/arch/wasm32.cppm"]

[target.linux.build]
sources = ["src/targets/linux.cppm", "src/linux/*.cpp", "src/posix/*.cpp"]

Expand Down Expand Up @@ -84,6 +100,13 @@ sources = ["src/windows/crt/stdio.cpp"]
[target.'cfg(all(windows, c-abi = "musl"))'.build]
sources = ["src/windows/posix/stdio.cpp"]

# EMSCRIPTEN: a wasm module over Emscripten's C library, under node or in a
# page. No platform module is defined; the file system's durability and
# ownership are the C library's (src/posix/fs.cpp), and the rest is
# src/emscripten/.
[target.'cfg(os = "emscripten")'.build]
sources = ["src/targets/emscripten.cppm", "src/emscripten/*.cpp", "src/posix/fs.cpp"]

# AN OK IMAGE (`<arch>-openkal', openkal SPEC clause 12): no implementation
# beneath --- the loader binds the image's `kal_*' --- and no platform module
# defined. `host()' and the conventions are answered when the image is loaded.
Expand Down
2 changes: 1 addition & 1 deletion src/arch/aarch64.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export module mcpplibs.os.platform:arch;

export namespace mcpplibs::os {

enum class architecture { x86_64, aarch64, riscv64 };
enum class architecture { x86_64, aarch64, riscv64, wasm32 };

// What the program is built for. The machine it runs upon may differ (a
// translated program); `machine::arch()' answers that.
Expand Down
2 changes: 1 addition & 1 deletion src/arch/riscv64.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export module mcpplibs.os.platform:arch;

export namespace mcpplibs::os {

enum class architecture { x86_64, aarch64, riscv64 };
enum class architecture { x86_64, aarch64, riscv64, wasm32 };

// What the program is built for. The machine it runs upon may differ (a
// translated program); `machine::arch()' answers that.
Expand Down
13 changes: 13 additions & 0 deletions src/arch/wasm32.cppm
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// The architecture of this build: wasm32. Selected by the manifest's
// [target.'cfg(arch = "wasm32")'.build].
export module mcpplibs.os.platform:arch;

export namespace mcpplibs::os {

enum class architecture { x86_64, aarch64, riscv64, wasm32 };

// What the program is built for. A wasm module runs upon whatever machine its
// host runs upon, and is told nothing of it; `machine::arch()' answers wasm32.
inline constexpr architecture ARCH = architecture::wasm32;

} // namespace mcpplibs::os
2 changes: 1 addition & 1 deletion src/arch/x86_64.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export module mcpplibs.os.platform:arch;

export namespace mcpplibs::os {

enum class architecture { x86_64, aarch64, riscv64 };
enum class architecture { x86_64, aarch64, riscv64, wasm32 };

// What the program is built for. The machine it runs upon may differ (a
// translated program); `machine::arch()' answers that.
Expand Down
14 changes: 14 additions & 0 deletions src/emscripten/exec.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Emscripten starts no program and replaces no program's image.
module mcpplibs.os.process;

import std;
import mcpplibs.os.error;

namespace mcpplibs::os::process::sys {

result<void> replace_image(const std::filesystem::path&, const std::vector<std::string>&,
const std::vector<std::string>&, const std::filesystem::path&) {
return std::unexpected(error{ errc::not_supported, "this system replaces no program's image" });
}

} // namespace mcpplibs::os::process::sys
Loading
Loading