Skip to content

Android, iOS and Web for a UI framework: the engine half of #622 (2026.9.12.3) - #623

Merged
Sunrisepeak merged 19 commits into
mainfrom
feat/622-ui-framework-on-three-rows
Sep 12, 2026
Merged

Android, iOS and Web for a UI framework: the engine half of #622 (2026.9.12.3)#623
Sunrisepeak merged 19 commits into
mainfrom
feat/622-ui-framework-on-three-rows

Conversation

@Sunrisepeak

Copy link
Copy Markdown
Member

Implements the engine half of #622, as designed in .agents/docs/2026-09-12-622-a-ui-framework-on-android-ios-and-web.md and 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).

item change held by
A5 wasm32-emscripten names its executable bin/<name>.js; bin/<name>.wasm is an implicit output of the link; mcpp pack stages every file the link wrote with that stem; kind = "shared" is refused naming -sSIDE_MODULE; LinkIntentFlavor::Wasm replaces the ELF fallback tests/unit/test_artifact_naming.cpp, tests/e2e/650
A4 mcpp::deploy(from, to), directive protocol 11: a build program deploys a file it generated; replayed on a cache hit; staged by mcpp pack tests/unit/test_build_directives.cpp, tests/e2e/651
A11 MCPP_TARGET_MIN_PLATFORM_VERSION / mcpp::min_platform_version() in the build-program contract tests/e2e/638, 652b
A3 kind = "app": an application, identical to bin on every row and a shared library on *-linux-android (application_form); mcpp pack treats it as the program target; mcpp run of a library-form app without --format is refused naming the flag tests/unit/test_manifest.cpp, tests/e2e/652, 652b
A1 [target.<sel>.abi] exceptions, the second member; -fexceptions on os = "emscripten" only, through the dialect flags and the link; requires_abi = { exceptions = true } tests/unit/test_abi.cpp, tests/e2e/653
A6 [target.<sel>] requires_abi = {...} and [target.<sel>.feature-requires-abi], unioned when the selector matches; the refusal names the selector tests/unit/test_abi.cpp, tests/e2e/654
A2 frameworks under [target.<sel>.runtime], appended after the top-level list, Mach-O only tests/unit/test_target_runtime_frameworks.cpp, test_manifest.cpp
A7 [package] platforms accepts `linux macos
A10 mcpp run --format <name>: pack, then hand the reported artifact to the runner; refused with --no-runner tests/e2e/656
A8 no engine change: a host module's declaration already reaches the consumer's build program (tests/e2e/622); docs/31 states the second half docs

Documentation: docs/04, 10, 21, 22, 30, 31 and their docs/zh mirrors; CHANGELOG [Unreleased] per item. Version bumped to 2026.9.12.3 in mcpp.toml and modules/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 test 113 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_abi ignores 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.

speak-agent and others added 19 commits September 12, 2026 13:42
…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`.
`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.
@Sunrisepeak
Sunrisepeak merged commit c765c8c into main Sep 12, 2026
40 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants