Skip to content

Commit c3cb6ee

Browse files
committed
feat(toolchain): a spec that names a payload has answered which payload, and the iOS rows are written down
NEITHER `mcpp toolchain install emsdk` NOR `mcpp toolchain install android-ndk` HAD EVER WORKED, and openkal-emscripten's continuous integration is what found it: $ mcpp toolchain install emsdk 6.0.9 error: installed package has no known C++ frontend in '.../xim-x-emsdk/6.0.9/bin' TWO DEFECTS, ONE PER AXIS. `to_xim_package` decided the payload from the TARGET. That is right for the spelling a build uses -- `--target wasm32-emscripten` with nothing declared -- and there is no target at all in an install, so the emsdk spec resolved the generic llvm shape and looked for `clang++` in `bin/` while `em++` is in `emscripten/`. `payloadName` is the field `parse_toolchain_spec` fills from the spelling and is the more direct statement of the two: a spec that names a payload has answered this question. It decides first; the target remains the answer for a spec that names no payload, which is the escape-hatch spelling the capability gate has to be able to refuse. And the install path composed `<root>/bin` itself. That was one of the five sites the `frontendSubdir` note in mcpp.toolchain.registry records; four were repaired and this one was not. It now asks `payload_frontend`, which is the same function the build path asks -- so the two cannot disagree, and the install picks up the payload's descriptor for free. AND THE FIXTURE OF THE TEST THAT COVERS THIS AXIS HAD PICKED ITS OBJECT BY CONVENIENCE. `TheTargetChoosesThePackageAndThePackageKnowsItsLayout` built every case from `emsdk@6.0.9` and replaced only the target, which worked while the target was the only thing read. It now builds them from `llvm@22.1.8`, which names the family and no payload -- the shape whose resolution that test exists to state. D5. THE iOS ROWS ARE WRITTEN DOWN, in docs/20 and its Chinese copy: the compiler is ours and only the SDK is Apple's, the two host dependencies this platform adds are named and bounded, the deployment target travels in the effective triple alone, and the simulator is two rows because it runs the host's architecture. `examples/13-platform-targets` gains the iOS rows with measured readings -- `platform 2` for the device and `platform 7` for the simulator, which is the one pair a successful build cannot tell apart -- and records two limitations it ran into rather than working around them: `deps` is not conditional on a target, and `xim:apple-simulator-tools` exists for macosx alone, so declaring the runner's package in the manifest would make this example's Linux build depend on a macOS-only package. The program is installed by whoever runs those rows. E4c. `docs/24-openkal-cross` and its Chinese copy stop saying the Web needs an implementation and name `openkal-emscripten`, with the three-group table that makes a partial surface a conformant one. The iOS section stops being a structural argument: what the SDK's licence bounds is packaging it, not locating it, and `aarch64-macos` has been verified on exactly that split since before these rows existed. e2e 641 gains the iOS claims that a non-Apple host can make: the rows name their payload, and the SDK's absence is a refusal that names the SDK, the `xcrun` command, the Command-Line-Tools note and the compiler -- arriving before any payload is resolved. The effective-triple claim is a unit test instead, because that same gate means a host without Xcode never prints the line an end-to-end check would read. AND THE macOS LEG NOW RUNS THE ARTEFACT THROUGH THE SUPPORTED PATH. The earlier leg spawned it by hand, which proves the platform can run it and not that mcpp can; `mcpp run --target aarch64-ios-sim` goes through the `runner` the manifest declares and the program `xim:apple-simulator-tools` provides.
1 parent 564e0dd commit c3cb6ee

12 files changed

Lines changed: 778 additions & 64 deletions

.github/workflows/ci-macos-ios.yml

Lines changed: 84 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,31 @@ jobs:
263263
264264
[target.x86_64-ios-sim]
265265
toolchain = "llvm@22.1.8"
266+
267+
# D4: THE RUNNER IS AN ARGV PREFIX AND THE SESSION BELONGS TO A
268+
# PACKAGE. `simctl-run` comes from `xim:apple-simulator-tools`; it
269+
# chooses a device, boots it if it is not booted, waits, spawns, and
270+
# returns the program's own exit status. The engine learns nothing
271+
# about simulators, which is the boundary it keeps for every other
272+
# emulated target.
273+
#
274+
# The device row keeps `runner` unset: an artefact cannot be run off
275+
# an iOS device without a signature the developer owns.
276+
#
277+
# THE TOOL IS DECLARED AT THE TOP LEVEL HERE AND NOT IN
278+
# examples/13, and the difference is which hosts the manifest has to
279+
# work on. `deps` is not conditional on a target -- measured:
280+
# "[target.aarch64-ios-sim.xlings] does not accept 'deps'" -- and
281+
# `xim:apple-simulator-tools` exists for macosx alone, so an
282+
# unconditional declaration breaks a Linux build. This fixture is
283+
# macOS-only, so it can say it.
284+
[xlings]
285+
deps = ["xim:apple-simulator-tools"]
286+
TOML
287+
cat >> /tmp/iostest/mcpp.toml << 'TOML'
288+
289+
[target.aarch64-ios-sim]
290+
runner = ["simctl-run"]
266291
TOML
267292
cat > /tmp/iostest/src/main.cpp << 'CPP'
268293
import std;
@@ -319,21 +344,72 @@ jobs:
319344
echo "SPAWN-FAILED exit=$?"
320345
fi
321346
347+
# D4: `mcpp run` THROUGH THE RUNNER, WHICH IS THE SUPPORTED PATH.
348+
#
349+
# The step above spawned the artefact by hand, which proves the platform
350+
# can run it and not that mcpp can. This repository has a standing note
351+
# that publishing and verifying an asset is not the same as being able to
352+
# use it through a supported path, and the difference here is a `runner`
353+
# a manifest declares and a program a package provides.
354+
- name: "D4: mcpp run --target aarch64-ios-sim prints 1-2-3"
355+
continue-on-error: true
356+
run: |
357+
set -x
358+
cd /tmp/iostest
359+
out=$("$MCPP_DEV" run --target aarch64-ios-sim 2>&1) || true
360+
set +x
361+
printf '%s\n' "$out" | tail -20
362+
if printf '%s\n' "$out" | grep -q '1-2-3'; then
363+
echo "RUN-THROUGH-RUNNER-OK"
364+
else
365+
echo "RUN-THROUGH-RUNNER-FAILED"
366+
fi
367+
368+
# x86_64-ios-sim IS THE THIRD ROW AND THIS RUNNER CANNOT RUN IT.
369+
#
370+
# The simulator runs the HOST's architecture, so an Apple-silicon machine
371+
# has no x86_64 iOS runtime to spawn into. What can be measured here is
372+
# the ARTEFACT, which is the claim the row's tier will rest on; a machine
373+
# with an Intel host is what would move it further.
374+
- name: "x86_64-ios-sim: the artefact, on a host that cannot run it"
375+
continue-on-error: true
376+
run: |
377+
set -x
378+
cd /tmp/iostest
379+
"$MCPP_DEV" build --target x86_64-ios-sim
380+
set +x
381+
ART=$(find /tmp/iostest/target/x86_64-ios-sim -name iostest -type f | head -1)
382+
file "$ART"
383+
otool -l "$ART" | grep -A5 LC_BUILD_VERSION
384+
322385
# THE HOST SURFACE IS BOUNDED, AND ITS ABSENCE NAMES THE SDK. The
323386
# recorded rule is that a host dependency must be minimal, named, and
324387
# never a fallthrough -- so the refusal is a claim like any other, and
325388
# `SDKROOT` pointing at a macOS SDK is the cheapest way to make the iOS
326389
# SDK unlocatable without breaking the rest of the machine.
327390
#
328-
# AND NO OTHER ROW CHANGES: the same environment builds for the host.
329-
- name: "D: the refusal names the SDK, and no other row is affected"
391+
# AND NOT BY POINTING `DEVELOPER_DIR` AT NOTHING, WHICH MEASURED NOTHING.
392+
#
393+
# The first version of this step set `DEVELOPER_DIR=/nonexistent` and
394+
# expected the refusal. The build SUCCEEDED: `xcrun` ignores an invalid
395+
# developer directory and falls back to the recorded one, so the
396+
# environment change did not make the SDK unlocatable. The predicate was
397+
# right and the object was wrong -- the claim was never tested.
398+
#
399+
# The claim now lives where the SDK is genuinely absent, which is every
400+
# non-Apple host: `tests/e2e/641` asserts it on Linux and Windows, with
401+
# an explicit toolchain override so the tier gate does not answer first
402+
# and `MCPP_NO_AUTO_INSTALL=1` so no payload is needed. What is left for
403+
# this runner is the other half of the same claim -- that a machine which
404+
# DOES have the SDK is not refused -- and that is what the steps above
405+
# measure by building.
406+
- name: "D: the SDK is located here, which is the other half of the claim"
330407
continue-on-error: true
331408
run: |
332409
set -x
333-
cd /tmp/iostest
334-
DEVDIR=/nonexistent-developer-dir
335-
env DEVELOPER_DIR="$DEVDIR" SDKROOT="" \
336-
"$MCPP_DEV" build --target aarch64-ios 2>&1 | tail -12
410+
xcrun --sdk iphoneos --show-sdk-path
411+
xcrun --sdk iphonesimulator --show-sdk-path
337412
set +x
338-
echo "--- and the host row still builds in the same environment ---"
339-
env DEVELOPER_DIR="$DEVDIR" "$MCPP_DEV" build 2>&1 | tail -4 || true
413+
echo "both SDKs located; the refusal for a machine without them is"
414+
echo "asserted by tests/e2e/641 on every non-Apple host"
415+
cd /tmp/iostest && "$MCPP_DEV" build --target aarch64-ios 2>&1 | tail -3

docs/20-toolchains.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,8 +595,105 @@ an argv prefix and the session belongs to a package rather than to the engine:
595595
```toml
596596
[target.x86_64-linux-android]
597597
runner = ["adb-run"] # a program from xim:android-platform-tools
598+
599+
[target.aarch64-ios-sim]
600+
runner = ["simctl-run"] # a program from xim:apple-simulator-tools
598601
```
599602

603+
A runner is an argv prefix and a *session* is not. Running a program on an iOS
604+
simulator means choosing a device, booting it if it is not booted, waiting for
605+
the boot, spawning, and returning the program's own exit status; a manifest
606+
line has no beginning and no end, which is why that work lives in a package.
607+
608+
## The Apple SDKs Are Located, Not Installed
609+
610+
The three iOS rows -- `aarch64-ios`, `aarch64-ios-sim` and `x86_64-ios-sim` --
611+
are the other shape a platform can take, and they are worth reading beside the
612+
two SDK toolchains above because they answer the same question differently.
613+
614+
**The compiler is ours; only the SDK is Apple's.** Any sufficiently new clang
615+
emits arm64 Mach-O for an iOS deployment target, so these rows pin
616+
`llvm@22.1.8` -- the ordinary payload, the same one `aarch64-macos` uses. What
617+
cannot be packaged is the iPhoneOS and iPhoneSimulator SDK: it ships inside
618+
Xcode and is not redistributable. So mcpp **locates** it, through
619+
`xcrun --sdk <name> --show-sdk-path`, exactly as it has always located the
620+
macOS SDK.
621+
622+
That is why these rows carry no `sysroot` entry. That column names a package,
623+
and a located directory is not one.
624+
625+
```bash
626+
mcpp build --target aarch64-ios # resolves llvm@22.1.8 + the iPhoneOS SDK
627+
mcpp build --target aarch64-ios-sim # resolves llvm@22.1.8 + the Simulator SDK
628+
```
629+
630+
### The host surface this adds, named and bounded
631+
632+
Two items, both macOS-only, both in the category a proprietary runtime that
633+
exists only on its own operating system occupies:
634+
635+
| the item | reached through | the permission |
636+
|---|---|---|
637+
| the iPhoneOS / iPhoneSimulator SDK | `xcrun` | not redistributable; there is nothing to package |
638+
| the simulator runtime | `simctl`, via `xim:apple-simulator-tools` | the same |
639+
640+
Everything else is ecosystem: the compiler, the C++ runtime, the linker and the
641+
packaging. Neither item is reached by a fallthrough -- each is asked for
642+
deliberately, and its absence is a refusal that names it:
643+
644+
```
645+
error: target aarch64-ios needs the iphoneos SDK, which this machine does not provide.
646+
It is not redistributable, so mcpp LOCATES it rather than installing it:
647+
`xcrun --sdk iphoneos --show-sdk-path` must answer, which needs Xcode on
648+
macOS (not the Command Line Tools alone -- those ship the macOS SDK only).
649+
Check `xcode-select -p`, and note that the compiler is not what is
650+
missing: these rows pin `xim:llvm`, which every other Apple row also uses.
651+
```
652+
653+
The refusal arrives **before** any payload is resolved. An Apple SDK is not
654+
something a dependency can supply, so there is nothing a later step could learn
655+
that would change the answer -- and a machine without Xcode should not download
656+
a compiler before being told the compiler is not what is missing.
657+
658+
### The deployment target
659+
660+
`[build] ios_deployment_target` sits beside `macos_deployment_target`, and the
661+
two are separate keys for one reason: `"14.0"` is a macOS version and means
662+
nothing to an iOS SDK. Only one of them can apply to any given target, so they
663+
share a single slot in the build fingerprint.
664+
665+
```toml
666+
[build]
667+
ios_deployment_target = "18.0"
668+
```
669+
670+
It is carried by the **effective triple** and nowhere else:
671+
672+
```
673+
Target aarch64-ios → arm64-apple-ios18.0
674+
Target aarch64-ios-sim → arm64-apple-ios18.0-simulator
675+
```
676+
677+
which are Apple's own spellings. No `-miphoneos-version-min` flag is emitted:
678+
the triple fully determines the platform and the minimum, and a flag would be a
679+
second place answering a question the triple already answers. Leaving the key
680+
unset is legal and means the SDK's own default, which clang supplies for an
681+
Apple target -- unlike Android, where bionic refuses an unversioned triple
682+
outright.
683+
684+
### The simulator's shape, and its boundary
685+
686+
The simulator is two rows and not a flag on the device row. A simulator build
687+
has its own SDK, produces an object naming its own platform
688+
(`LC_BUILD_VERSION` reports `IOSSIMULATOR` rather than `IOS`), and takes a
689+
different deployment-target segment. Both architectures exist because the
690+
simulator runs the **host's** architecture: an Apple-silicon machine needs
691+
`aarch64-ios-sim` and an Intel one needs `x86_64-ios-sim`.
692+
693+
The device row keeps `runner` unset. An artifact cannot be run off an iOS
694+
device without a signature the developer owns, which is not something a build
695+
tool can supply.
696+
600697
## Project-Level Version Pinning
601698

602699
If a project needs to pin a specific version rather than rely on the global default, declare it in the project's `mcpp.toml`:

docs/24-openkal-cross.md

Lines changed: 73 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -225,32 +225,83 @@ openkal: 1-2-3 exit 0
225225
is the weaker claim of the two and is worth stating separately: the first says
226226
the implementation builds, the second says a program over it runs.
227227

228-
### iOS would share the macOS implementation, and that cannot be claimed yet
229-
230-
The same argument applies on Apple's side — iOS and macOS share the Darwin
231-
kernel, and `openkal-macos` is arch-dispatched the same way — but the argument
232-
is not evidence. The iPhoneOS and iPhoneSimulator SDKs ship inside Xcode and
233-
are not redistributable, so the `aarch64-ios` and `*-ios-sim` rows are
234-
`planned`: there is nothing to build against and therefore nothing to run.
235-
Declaring support on a structural argument alone is the shape this ecosystem has
236-
paid for before — a package present in an index is not a package that builds a
237-
real project — so these rows claim nothing until an SDK is reachable.
238-
239-
### Web needs a new implementation, and a different one
228+
### iOS shares the macOS implementation, and the SDK is located rather than packaged
229+
230+
The same argument applies on Apple's side: iOS and macOS share the Darwin
231+
kernel, the same call numbers and the same calling convention, and
232+
`openkal-macos` is arch-dispatched the same way. What differs between them is
233+
the SDK and the deployment-target flag, and both belong to the build tool
234+
rather than to the implementation — so iOS is one `cfg` line in a manifest and
235+
no new package.
236+
237+
**What was blocked was the SDK, and what unblocked it was asking a smaller
238+
question.** The iPhoneOS and iPhoneSimulator SDKs ship inside Xcode and are not
239+
redistributable, which bounds *packaging* them. It does not bound *locating*
240+
them: `aarch64-macos` has been `verified` on exactly that split since long
241+
before these rows existed — `xim:llvm` compiles and the machine's macOS SDK is
242+
found through `xcrun`. The iOS rows take the same split with a second SDK, so
243+
they pin `llvm@22.1.8` and carry no `sysroot` entry, because that column names a
244+
package and a located directory is not one.
245+
246+
The consequence for this document is that the rows are no longer a structural
247+
argument. `openkal-macos` compiles for them, and what a reader needs to know is
248+
that the SDK is a named host dependency — one of exactly two this platform adds,
249+
the other being `simctl` — and that its absence is a refusal naming the SDK
250+
rather than a build that quietly produces a macOS artefact.
251+
252+
### Web needed a new implementation, and `openkal-emscripten` is it
240253

241254
Emscripten is the one of the three that changes the model rather than extending
242255
it. There is no kernel and there are no system calls to issue: Emscripten
243256
supplies its own C library over a JavaScript host. An openkal implementation for
244257
it therefore cannot be written the way `openkal-linux` is — beneath a C library
245-
— and would have to sit **above** one. The specification permits exactly that
246-
("an implementation may be built upon a C library, beneath one, or without
247-
one"), so this is new software rather than a sharing decision, and it is the one
248-
of the three that is neither done nor blocked.
258+
— and has to sit **above** one. The specification permits exactly that ("an
259+
implementation may be built upon a C library, beneath one, or without one"), so
260+
this was new software rather than a sharing decision.
261+
262+
`openkal-emscripten` is the first implementation in this ecosystem written in
263+
that direction. That makes the code thin and not easy: a forward and an error
264+
translation is most of each function, and what it has to get right is the
265+
places where the C library's vocabulary and openkal's do **not** correspond —
266+
the granularity that is an alignment and not a page, a monotonic clock whose
267+
resolution a browser deliberately coarsens, a terminal that exists under node
268+
and not in a page.
269+
270+
**A partial surface is a conforming one, and the specification says how.**
271+
Clause 6.2 gives three times, each the earliest at which the information
272+
exists, and the implementation's three groups get three different treatments:
273+
274+
| group | treatment | the reason |
275+
|---|---|---|
276+
| `stream`, `fs`, `time`, `env`, `memory`, `random`, `abort`, `terminal` | provided, forwarding to Emscripten's libc | MEMFS and the JavaScript host serve all of these |
277+
| `net`, `datagram`, `timeout` | provided, with the capability word reporting what is exercisable | the calls are real and the transport is a WebSocket proxy, so `kal_net_props` claims neither IPv6 nor half-close |
278+
| `process`, `exec`, `space` | NOT PROVIDED | there is no fork, no exec and no second address space |
279+
280+
The third row is the decision worth stating plainly: **an absent symbol is the
281+
report.** Measured:
282+
283+
```
284+
wasm-ld: error: obj/main.o: undefined symbol: kal_process_spawn
285+
```
249286

250-
Until it exists, `wasm32-emscripten` is served the ordinary way: by a payload.
251-
`xim:emsdk` ships the compiler, the sysroot and a libc++ module surface, so a
252-
program that uses `import std` builds and runs for the Web today without openkal
253-
being involved at all — which is what the row's `verified` tier records.
287+
which is clause 6.2's second time. Providing `kal_process_spawn` so that it
288+
returned an error would be the shape the specification forbids — present and
289+
always failing, which the caller cannot tell from a condition — and it would
290+
move a fact known at link time to run time.
291+
292+
`openkal.task` is carried by a feature for a reason specific to this platform:
293+
threads need `-pthread`, which selects a different C library build, a different
294+
memory model and a different loader contract. Without the feature the
295+
translation unit is empty and the eight symbols do not exist, which is the same
296+
treatment the three absent interfaces get. With it they do, and
297+
`kal_interfaces()` follows the link rather than a name the package invented.
298+
299+
None of this replaces the payload route. `wasm32-emscripten` is still served
300+
the ordinary way — `xim:emsdk` ships the compiler, the sysroot and a libc++
301+
module surface, so a program that uses `import std` builds and runs for the Web
302+
with openkal not involved at all, which is what the row's `verified` tier
303+
records. openkal is what a program uses when it wants one source above several
304+
platform interfaces.
254305

255306
### The table
256307

@@ -259,9 +310,9 @@ being involved at all — which is what the row's `verified` tier records.
259310
| Linux (glibc, musl) | `openkal-linux` | the reference implementation |
260311
| Android (both ABIs) | `openkal-linux`, unchanged | builds; a program over it ran on an emulator |
261312
| macOS | `openkal-macos` | on the macOS system-call surface |
262-
| iOS, iOS simulator | `openkal-macos` would serve it | blocked: the SDK is not redistributable |
313+
| iOS, iOS simulator | `openkal-macos`, unchanged | Darwin is Darwin; the SDK is located, not packaged |
263314
| Windows | `openkal-windows` | on Win32 and the object manager |
264-
| Web (Emscripten) | none | needs an implementation written ABOVE a C library |
315+
| Web (Emscripten) | `openkal-emscripten` | written ABOVE a C library; twelve of fifteen interfaces |
265316

266317
## Bare Metal
267318

0 commit comments

Comments
 (0)