You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Target rows become executable, and a machine-scoped scan stops being paid per target (2026.9.11.3)
WASM32-EMSCRIPTEN IS `verified`. `mcpp run --target wasm32-emscripten` prints
`1-2-3` on a source that imports std, from the row alone -- and the project
side needed NO new vocabulary. That is the first real measurement of what this
design costs: seven engine changes, and a project that builds for Linux builds
for the web by changing one flag.
Each of the seven was found by the previous one's failure, and each was a
predicate that was CORRECT about the rows its author had in mind:
1. the payload chosen by the TARGET in `to_xim_package`, or `xim:llvm`
answers for a wasm target
2. `XimToolchainPackage::frontendSubdir`, or `em++` is looked for in `bin/`
3. `Triple::has_own_sysroot()` in the SHARED PRODUCER `host_compile_tokens`.
The first attempt put it at one of its three callers, so ordinary compiles
stopped taking this host's headers and the std module precompile went on
taking them -- which is where it had been failing. One decision, one site,
three readers.
4. the same predicate in `resolve_link_model` -- the MODEL, not its two
channels; the comment at the second channel already recorded that a reader
who fixed only the first "saw the identical error"
5. the same finding twice: `discover_link_runtime_dirs` put the COMPILER's
own runtime directories on the ARTEFACT's link line. For every row that
predates these two, those are the same directory.
6. `host_can_serve` -- the first version returned true unconditionally, the
same over-broad shape as the branches above it. The TARGET MATRIX caught
it: `xim:emsdk` publishes linux only.
7. `Format::Wasm`, which the module PREDICTED AND DEFERRED in its own
comment. While it was missing, every wasm build warned about a `libc++.so`
that cannot exist for the target.
Wasm also joins the capability pins: nothing else emits WebAssembly, so a
declared `gcc@16.1.0` is a request that cannot be honoured, and saying so beats
resolving gcc and failing inside it.
A MACHINE-SCOPED SCAN WAS PAID PER TARGET: 17948ms -> 4ms.
mcpp build loader-tags stage 170ms one 21 MB binary
mcpp test loader-tags stage 17948ms 108 binaries, 2.4 GB
and flat at ~17.9s across every target measured, so the cost is the whole
artefact set rather than the one being built. `mcpp test` drives the backend
once per target, so a 110-target suite paid it 110 times -- 3 minutes became a
projected 33.
`check_dlopen_surface` ran a full `inspect_elf_runtime` over EVERY linked
artefact to collect SONAMEs, before it discovered the surface was empty. Its
record said `members=0, walked=0` every time, and the artefacts are
executables, which cannot carry `DT_SONAME` at all. Both halves fixed, and the
shape is worth naming: the expensive work ran BEFORE the cheap test that makes
it unnecessary. The record is still published -- a field that disappears is
worse than one that says why it is empty.
`min_api_level`, REUSING THE MECHANISM `macos_deployment_target` ALREADY HAS.
Measured: `clang -target aarch64-linux-android21 -print-effective-triple`
answers `aarch64-unknown-linux-android21`, so the level belongs on the effective
triple -- and NOT on the canonical one, because mcpp keeps its own target
vocabulary and maps it. macOS is already this shape. So `[target.<triple>]
min_api_level = 24`, fused by the parameter `llvm_triple()` already takes, and
in the FINGERPRINT because the level selects which bionic symbols are visible.
The key's slot is renamed `minPlatformVersion`: a target is either Apple or
Android, so one slot cannot hold both, and both answer one question.
The name comes from Android's own words -- the NDK's CMake toolchain documents
`ANDROID_PLATFORM` as "the minimum API level supported by the application or
library". `ndk_api_version` was rejected twice over: "version" is not Android's
word, and `ndk_` names the TOOLCHAIN while one NDK serves a RANGE of levels.
THE FOUR-FIELD SPELLING IS ACCEPTED. `em++ -v` passes
`-target wasm32-unknown-emscripten` and rustc lists that spelling; refusing what
every other toolchain prints is a cost with no benefit. `str()` still returns
the three-field form, which is what keeps the output directory, `cfg()` and the
ABI tag single-valued.
AND THE EOL DISTRO LEG IS SWAPPED. debian-11 fails with `E: Release file ... is
expired` -- bullseye is end-of-life and its security metadata has expired,
which is a property of the distribution. Measured while replacing it: debian 11
and ubuntu 20.04 both carry glibc 2.31, so the "older glibc" coverage was
ALREADY duplicated by the ubuntu-2004 leg. Bookworm's 2.36 sits between that
and debian-testing, so the leg now covers a point the matrix did not have.
Copy file name to clipboardExpand all lines: .agents/docs/2026-09-11-platform-targets-design-review.md
+242-2Lines changed: 242 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -302,9 +302,9 @@ package -- no new mechanism, again.
302
302
| # | question | recommendation | why |
303
303
|---|---|---|---|
304
304
| R1 | the simulator's spelling |`env = "sim"`, giving `aarch64-ios-sim` and `x86_64-ios-sim` as their own rows | matches Rust's pair modulo a vendor elision mcpp already does; satisfies the row comment's own objection, which was to NOT having a separate row |
305
-
| R2 | Android's API level |a `[target.<triple>] api = <n>` manifest key, and **it must enter the build fingerprint**| every system surveyed keeps it out of the triple (Rust: outside; CMake: `ANDROID_PLATFORM`; Gradle: `minSdk`), because it is a per-PROJECT minimum. The fingerprint is non-negotiable: it selects which bionic symbols exist, so two levels are two ABIs|
305
+
| R2 | Android's API level |**`min_api_level` under `[target.<triple>]`, reusing the `macos_deployment_target` design** -- see 12.1 | mcpp maintains its own vocabulary and MAPS to a compiler target, so where LLVM carries the level says nothing about where mcpp stores it. macOS already does this exactly: a manifest key, a clean canonical triple, the level appended by `llvm_triple(param)` -- which already takes a version -- and the value in the fingerprint. One NDK serves a range of levels, so it is a project decision and not a toolchain property. No new rows|
306
306
| R3 | the payload identity shown for `emsdk`| a display identity on the resolved toolchain, not a fourth `Family`|`em++` is clang and a fourth family would be a false claim about the compiler; what is missing is only that nothing prints which archive answered |
307
-
| R4 |`@system` for a non-MSVC family |generalise it, with the row deciding whether it is permitted | the refusal argues from MSVC's uniqueness, and iOS is the second instance of exactly that situation. `xim:iphoneos-sdk` already names "locate what the machine has" as its third tier, and the engine has no spelling for it|
307
+
| R4 |`@system` for a non-MSVC family |**withdrawn** -- see 12.1a | the second instance dissolved: `xim:iphoneos-sdk` serves iOS as a package, so no host locator is required. Generalising would admit `gcc@system`, which the existing refusal names by name and which costs hermeticity. A refusal should not be relaxed without a case|
308
308
| R5 | device and simulator sessions | a `xim:` package shipping a runner program, named by `runner`| cargo states this boundary explicitly; no engine change, no new member family, and it puts platform knowledge in the ecosystem |
309
309
| R6 | the tier each row can reach |`verified` for wasm (reached); `preview` for both Android rows and for iOS | Rust rates all three Tier 2. `verified` for Android is reachable and needs a CI lane, not a design |
310
310
| R7 | signing a Mach-O or a `.app`| package `rcodesign` as `xim:rcodesign` and have `dist-apple` prefer it | MPL-2.0 with prebuilt static binaries for linux-musl (both arches), macOS universal and Windows. It removes the last host dependency from the iOS BUILD path, leaving only a device, the Simulator runtime and a notarization credential -- none of which is a program |
@@ -794,3 +794,243 @@ the second.
794
794
The two Android rows are the nearest, and the thing blocking them is not
795
795
payload work — both payloads are published and both execution routes are
796
796
measured. It is one identity decision.
797
+
798
+
## 12. Self-review of this proposal, before implementing any of it
799
+
800
+
### 12.1 R2, three times, and the design mcpp already has
801
+
802
+
This recommendation was written one way, reversed on a measurement, and then
803
+
reversed back when the measurement turned out to answer a different question.
804
+
The sequence is recorded because the mistake in the middle is instructive.
805
+
806
+
**First answer: a manifest key.** Because the API level is a per-project
807
+
minimum -- what Gradle calls `minSdk` -- and because every system surveyed
808
+
keeps it out of the triple.
809
+
810
+
**The reversal, and why it was wrong.** A real clang was asked:
| effective target |`arm64-apple-macos14.0`, composed by `llvm_triple(param)`|`aarch64-unknown-linux-android24`, same call |
838
+
| fingerprint |`put(s, "macos", b.macosDeploymentTarget)`| the same, one line |
839
+
840
+
`llvm_triple()`**already takes a version parameter** -- it is called as
841
+
`want->llvm_triple(macos::deployment_target(...))` -- so the objection that a
842
+
manifest key would give it a second input was already false when I raised it.
843
+
The function is not pure of versions today; it is pure of the *manifest*, which
844
+
is the property that matters, and the caller supplies the value.
845
+
846
+
**And one NDK serves a range of levels**, so the level is not a property of the
847
+
toolchain either: naming `android-ndk@30.0.16248370` does not pin API 24. It is
848
+
a project decision, which is what a manifest key is for.
849
+
850
+
So R2 is: **`api` under `[target.<triple>]`, appended to the effective triple
851
+
by the existing parameter, and entered into the fingerprint the way
852
+
`macos_deployment_target` already is.** No new rows, no new mechanism, and the
853
+
table does not multiply as levels are added.
854
+
855
+
#### The field name, chosen against Android's own vocabulary
856
+
857
+
`api = 24` was the first spelling and it is too vague: it says nothing about
858
+
WHICH property of the API is meant, and mcpp has no other `api` key to anchor
859
+
the reading. The naming convention to follow is `macos_deployment_target`'s --
860
+
**named in the platform's own words** -- so the question is what Android calls
861
+
this.
862
+
863
+
Read from the NDK's own documentation rather than recalled:
864
+
865
+
| source | spelling | what the docs say |
866
+
|---|---|---|
867
+
| NDK CMake toolchain |`ANDROID_PLATFORM`| "specifies the **minimum API level** supported by the application or library" |
868
+
| the same, alias |`ANDROID_NATIVE_API_LEVEL`| "Alias for `ANDROID_PLATFORM`" |
869
+
| Android.mk |`TARGET_PLATFORM`| "The Android **API level** number the build system is targeting" |
870
+
| Gradle |`minSdk`| the NDK docs state `ANDROID_PLATFORM` "corresponds to the application's `minSdkVersion`" |
871
+
872
+
So Android's concept name is **"API level"** -- the term its documentation uses
873
+
most -- and the specific quantity here is the **minimum**.
874
+
875
+
Judged against that:
876
+
877
+
| candidate | verdict |
878
+
|---|---|
879
+
|`api`| rejected. Says nothing about which property, and anchors to nothing |
880
+
|`ndk_api_version`| rejected on two counts. "version" is not Android's word, which is "level"; and `ndk_` names the TOOLCHAIN, while one NDK serves a RANGE of levels -- so naming it after the NDK reintroduces exactly the confusion 12.1 resolved |
881
+
|`platform`| rejected. It is the NDK's own variable name, and `platform` is badly overloaded in mcpp -- a module, and the `xpm` platform tables |
882
+
|`min_sdk_version`| rejected. Gradle's `minSdk` is an application-manifest concept for the Java side; for native code the NDK's word is API level, and mcpp is not building an app |
883
+
|**`min_api_level`**|**chosen.** "API level" is Android's own term; "min" states the semantics the NDK docs state themselves; no platform prefix, because `[target.aarch64-linux-android]` already supplies it |
884
+
885
+
The kinship with `macos_deployment_target` is worth stating: both answer "the
886
+
oldest OS release this artifact must run on", and both are named in their
887
+
platform's vocabulary rather than in a shared abstraction. A single
888
+
`min_os_version` for both would be more uniform and would cost the existing
889
+
key a rename and both platforms their own words -- which is the trade this
890
+
codebase has consistently declined.
891
+
892
+
#### The usage model
893
+
894
+
# mcpp.toml
895
+
[package]
896
+
name = "app"
897
+
version = "0.1.0"
898
+
899
+
# The minimum Android API level this project supports -- the same decision
900
+
# Gradle spells `minSdk`. One NDK serves a range, so this is the project's
0 commit comments