Android, iOS and Web for a UI framework: the engine half of #622 (2026.9.12.3) - #623
Merged
Conversation
…ink output (#622 A5) - artifact_naming grows an `emscripten` row (triple.cppm): exeSuffix ".js", lib prefix/ext as ELF's, sharedLibExt empty with the shared-unsupported marker set. Independent of host naming (Linux/Windows both answer ".js"), and a "none" OS still falls back to the host answer unchanged. - LinkIntentFlavor gains Wasm, rendered exactly as Elf (-L, -l); the switch in flags.cppm has no default arm. - ninja_backend.cppm declares bin/<name>.wasm as an implicit output of the same link edge that writes bin/<name>.js, for every Binary/TestBinary link unit on a Wasm-object-format triple. - prepare.cppm refuses `kind = "shared"` on a Wasm triple by name, before any toolchain is resolved (same site/reasoning as the Apple SDK check it sits beside), naming -sSIDE_MODULE as the unrendered mechanism. - stage_tree.cppm adds emscripten_stem_family(): the launcher is the executable, the family is every other <name>.<anything> the link also wrote, read from the directory rather than a fixed extension list. pack.cppm gains a wasm packaging path (Plan::targetIsWasm, run_wasm) that stages the launcher plus the whole stem family, with no dependency-closure step (an ordinary Emscripten link is one static image). Verified end to end on this machine's installed emsdk@6.0.9 payload: `mcpp build/run/pack --target wasm32-emscripten` names bin/app.js + bin/app.wasm, runs and prints its marker (fixture declares its own `[target.wasm32-emscripten] runner = ["node"]`), and stages both files under `--format dir` and `--format tar` with both listed in .stage-manifest. The `shared` refusal is reached with MCPP_NO_AUTO_INSTALL=1 (no network). Tests: tests/unit/test_artifact_naming.cpp (new emscripten/none-OS cases), tests/unit/test_pack_stage_tree.cpp (emscripten_stem_family, positive and negative), tests/e2e/650_wasm_row_names_its_launcher.sh.
- modules/manifest/src/types.cppm: `Target::Kind` gains `Application`;
`Target::is_program()` answers "is this a program" ({Binary, Application})
so every "is this the program" call site stops asking `kind == Binary`.
- modules/manifest/src/toml.cppm: `kind = "app"` parses; the refusal for an
unrecognized kind lists all four; `main` is required for `app` as for
`bin`; `windows_subsystem`/`windows_entry` accept `is_program()`, not
`Binary` alone; target inference from a bare `src/main.cpp` stays `Binary`
on purpose (a platform fact an author states, never a default guessed).
- modules/manifest/src/xpkg.cppm: a Form B descriptor's target can be `app`.
- modules/toolchain-model/src/triple.cppm: `ApplicationForm` and
`application_form(const Triple&)`, beside `artifact_naming` -- `env ==
"android"` answers `SharedObject`, every other row answers `Executable`.
A manifest carries no predicate for this; the row alone decides.
- src/build/plan.cppm: the link-unit builder maps `Application` to a shared
link when `application_form(targetTriple)` is `SharedObject` and to an
ordinary executable link otherwise; `target_output` grows an
`asSharedObject` parameter so the Android row's `app` links as
`lib<name>.so`; the entry-`main` census and the entry-synthesis gate widen
from `is_program()` so an Android `app`'s `main` still compiles as a
translation unit of the shared library.
- src/build/prepare.cppm: a virtual workspace's default member picks a
program (`is_program()`); the host-tool provisioning site stays literally
`Binary` on purpose -- a build-time tool is exec'd directly and must be
the row's executable form, not whatever an `app` resolves to.
- modules/buildmcpp/src/directives.cppm: `windows-subsystem`/`windows-entry`
directives reach a program (`is_program()`), not only `Binary`.
- src/pack/route.cppm: `"app"` joins the kind-name table; an `Application`
is a program route on every row, exactly as `Binary` is.
- src/build/execute.cppm: `mcpp run` of an `app` whose row form is a shared
library, with no `--format`, is refused by name rather than falling into
the generic "no binary target" message, which would blame the user for a
target that does exist.
- tests/unit/{test_manifest,test_artifact_naming,test_build_directives}.cpp:
parsing, the four-kind refusal, `is_program()`'s exact membership,
`windows_subsystem`/`entry` acceptance, `application_form` per row.
- tests/e2e/652_an_application_is_a_kind.sh (`# requires: elf gcc`): the
host build, `mcpp run`, `mcpp pack`, and the older engine's three-kind
refusal (verified by hand against mcpp 2026.9.12.2, recorded in the
header rather than run).
- tests/e2e/652b_an_application_on_android_is_a_shared_library.sh
(`# requires: android-ndk`, new capability registered in run_all.sh): the
Android row links `lib<name>.so` and no executable, `bin` is unaffected,
and `mcpp run` without `--format` is refused with the exact sentence.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
#622 A4, A11) Two build-program surfaces, protocol 11: - `mcpp::deploy(from, to)` places a file the program produced or selected beside the artifact at a path relative to the executable -- the build- program form of `[runtime] deploy` (#615). `from` may be an action's own absolute declared output, which the manifest key can never name because it refuses an absolute path. Wire: `mcpp:deploy=<from>\t<to>` (tab-separated, because an absolute Windows `from` contains `:`). `to` obeys `deploy_path_problem`, checked before `apply` on both the run path and the cache-hit path. - `mcpp::min_platform_version()` / `MCPP_TARGET_MIN_PLATFORM_VERSION` hands a build program the same platform floor the compiler's `--target` flag and the fingerprint slot already resolve: `14.0` on macOS, `18.0` on iOS, an API level on Android, empty elsewhere. `dist-apple` and `dist-apk` read this instead of restating the value, where it was measured drifting (`dist/apple.cppm:140-143`). The deploy directive surfaced a pre-existing gap in the root package's directive-tail mirror (prepare.cppm): `packages[0]` is snapshotted from the root manifest BEFORE the root's own build.mcpp runs, so `apply()`'s later mutations reach `*m` but not `packages[0].manifest` -- the object `resolve_runtime_contract` (plan.cppm) actually reads. The existing mirror already repairs this for sources/cflags/cxxflags/includeDirs/ldflags; this change adds `runtimeConfig.linkIntent.deploy` to that list. Without it a directive-sourced deploy entry landed in `*m` and produced no copy edge. Docs are T7's. The two surfaces this task adds: `mcpp::deploy(from, to)` and `mcpp::min_platform_version()` / `MCPP_TARGET_MIN_PLATFORM_VERSION`.
… target axis (#622 A1, A6)
`mcpp run --format <name>` is `mcpp pack --format <name>` -- the two
prepares, the build, the staging, the provider's action -- followed by
the ordinary run, with the artifact the pack pipeline reported as the
operand. Runner resolution is unchanged (project runner, then a
dependency's, then the payload descriptor's); `--format` with
`--no-runner` is refused, and an unknown format is refused by the pack
pipeline's own message naming the available set.
`build_and_pack` now returns `PackOutcome{rc, artifacts}` instead of a
bare exit code, so the artifact(s) it reported travel to the caller
instead of only being printed; `mcpp pack` itself only ever needed the
exit code (cmd_publish.cppm). The runner-resolution + exec tail of
`build_run_target` is factored into `run_artifact_via_runner`, shared
between the ordinary link-output run and the `--format` run.
Second half of #622 A3: the pack pipeline's program-target selection
asks `Target::is_program()` rather than `LinkUnit::Binary` alone, so an
`app` whose row links it as a shared object (Android) is still found as
the program target; `make_plan`/`Plan` carry that fact
(`programIsSharedObject`), and `pack::run` stages such a file under
`lib/`, as the closure stages any shared object, without attempting a
dependency closure walk on an object this host cannot execute at all.
Tests: new tests/e2e/656_run_hands_the_distributable_to_the_runner.sh;
tests/e2e/652b_an_application_on_android_is_a_shared_library.sh
extended with the pack + MCPP_TARGET_MIN_PLATFORM_VERSION assertions.
…, not the row's (#622)
…as a program (#622 A3)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the engine half of #622, as designed in
.agents/docs/2026-09-12-622-a-ui-framework-on-android-ios-and-web.mdand planned in.agents/docs/2026-09-12-622-implementation-plan.md. One pull request carries every item, as the plan requires; the plugin and index halves follow the release (openxlings/xim-pkgindex#825 is already merged and published).wasm32-emscriptennames its executablebin/<name>.js;bin/<name>.wasmis an implicit output of the link;mcpp packstages every file the link wrote with that stem;kind = "shared"is refused naming-sSIDE_MODULE;LinkIntentFlavor::Wasmreplaces the ELF fallbacktests/unit/test_artifact_naming.cpp,tests/e2e/650mcpp::deploy(from, to), directive protocol 11: a build program deploys a file it generated; replayed on a cache hit; staged bymcpp packtests/unit/test_build_directives.cpp,tests/e2e/651MCPP_TARGET_MIN_PLATFORM_VERSION/mcpp::min_platform_version()in the build-program contracttests/e2e/638,652bkind = "app": an application, identical tobinon every row and a shared library on*-linux-android(application_form);mcpp packtreats it as the program target;mcpp runof a library-form app without--formatis refused naming the flagtests/unit/test_manifest.cpp,tests/e2e/652,652b[target.<sel>.abi] exceptions, the second member;-fexceptionsonos = "emscripten"only, through the dialect flags and the link;requires_abi = { exceptions = true }tests/unit/test_abi.cpp,tests/e2e/653[target.<sel>] requires_abi = {...}and[target.<sel>.feature-requires-abi], unioned when the selector matches; the refusal names the selectortests/unit/test_abi.cpp,tests/e2e/654frameworksunder[target.<sel>.runtime], appended after the top-level list, Mach-O onlytests/unit/test_target_runtime_frameworks.cpp,test_manifest.cpp[package] platformsaccepts `linuxmcpp run --format <name>: pack, then hand the reported artifact to the runner; refused with--no-runnertests/e2e/656tests/e2e/622); docs/31 states the second halfDocumentation: docs/04, 10, 21, 22, 30, 31 and their
docs/zhmirrors; CHANGELOG[Unreleased]per item. Version bumped to 2026.9.12.3 inmcpp.tomlandmodules/versioning/src/version.cppm; the bootstrap pin moves after the release is indexed.Measured locally on the merged branch with the bootstrap 2026.9.12.2:
mcpp test113 passed; the e2e suite is running as this PR is opened and its summary will be posted as a comment. Two facts the plan stated were corrected by measurement and are recorded in the design record: an mcpp 2026.9.12.2 reading[target.<sel>] requires_abiignores it silently rather than warning, and a.js-named launcher has no shebang, so the Web row now depends on the payload descriptor's runner that 2026.9.12.2 introduced.