Skip to content

Commit fe1db3e

Browse files
authored
0.9.0: dist-apk takes a manifest template and several Java roots, dist-web copies through the engine (#20)
* dist-apk: a project manifest template with six tokens, and java_sources as an array options::manifest_template renders a project AndroidManifest.xml through six tokens substituted verbatim: {{application_id}}, {{label}}, {{activity}}, {{lib_name}}, {{min_sdk}}, {{target_sdk}}. Three are required, not merely substituted, because their value is also written to assets/mcpp-run.json, which adb-run reads to start the application: {{application_id}} and {{activity}} always, {{lib_name}} at level 0. A missing required token or an unknown {{...}} token is refused at plan time, naming it. The built-in default is 0.8.0's manifest_xml output expressed with these tokens, so level 0 with no template renders byte-identical to 0.8.0's. options::java_sources becomes std::vector<std::string>: one javac over every root's .java files and one d8 over the result. rerun_if_changed_glob is declared only for a root under mcpp::manifest_dir(); a root outside it (a dependency's own Java tree) is not walked by the glob fingerprint regardless, and its files are already inputs of the javac action while its version is already in the build's fingerprint. Design record: .agents/docs/2026-09-13-four-upstream-asks-from-a-ui-framework.md, §3. * dist-web: copy through ${mcpp.self} stage instead of cp The two copy steps become argv { "${mcpp.self}", "stage", "--verify", "content", "--output", dst, src }, the same copier every stage_file edge in build.ninja already runs: it creates the destination's parent, compares content and writes only on a difference. Plan-time create_directories is removed with it. The POSIX-only note leaves the header; this member's floor rises to the mcpp release that carries ${mcpp.self} and mcpp stage's argument shape as an engine contract. Design record: .agents/docs/2026-09-13-four-upstream-asks-from-a-ui-framework.md, §4. * tests: dist-apk's manifest-template and Java-root criteria, dist-web's idempotent pack tests/apk-consumer: build.mcpp reads APK_CONSUMER_TEMPLATE and APK_CONSUMER_LEVEL1 to reach five configurations without a second fixture. check-apk-features.sh runs all five: (a) level 0 with no template, byte-identical to the 0.8.0 manifest fixture; (b) a template naming a uses-permission and a receiver, both present in aapt2 dump xmltree on the linked base.apk; (c) a template missing {{application_id}}, refused naming the token and assets/mcpp-run.json; (d) a template naming {{bogus}}, refused naming it; (e) two Java roots, one under the project (tests/apk-consumer/java) and one a sibling directory reached by an absolute path (tests/apk-consumer-external-java), producing one classes.dex with classes from both. (c) and (d) read build.mcpp's own stdout/stderr by re-invoking the compiled binary directly with the documented MCPP_* contract, because mcpp discards a build program's captured output on a plan-time refusal's exit code of 0. tests/web-consumer/check-web-idempotent.sh: a second, no-op mcpp pack --format web changes no staged file's mtime, the criterion ${mcpp.self} stage gives for free and cp never could. A sibling of check-web-plan.sh, which keeps passing unchanged. .github/workflows/ci.yml: both wired in beside the existing "dist-apk produces a signed APK, level 0 and level 1" and "dist-web produces a static directory, and node runs it" steps; MCPP_VERSION raised to 2026.9.13.1, the release both P1-P3 need. Design record: .agents/docs/2026-09-13-four-upstream-asks-from-a-ui-framework.md, §3.5, §4.3, §9.2. * 0.9.0: raise the floor of dist-apk and dist-web to 2026.9.13.1 README rows for dist-apk and dist-web state the new floor and why: dist-web needs ${mcpp.self} and mcpp stage's argument shape as an engine contract; dist-apk's own manifest-template and Java-array changes ask nothing new of the engine, but this collection publishes one package at one version, and this is the release CI verifies it under from here on. mcpp.toml and src/plugins.cppm: version 0.9.0. The mcpp 2026.9.13.1 release this depends on does not exist yet, so CI is red on the fetch step until it does (see the pull request body). * dist-apk: a java_sources root's rerun glob must be manifest-relative mcpp::rerun_if_changed_glob((root + "/**/*.java").c_str()) declared an ABSOLUTE pattern for a root under mcpp::manifest_dir(), because opt.java_sources's own roots (manifest_dir() composed with a subdirectory) are absolute. The engine matches a glob by comparing the candidate file made relative to the package root against the pattern (modules/manifest/src/glob.cppm, path_matches_glob); an absolute pattern is compared against a relative candidate and never matches anything, so the fingerprint is always the empty set and a .java file appearing under the project root never changes it -- "a .java added under the project root re-runs the build program" was not true. root_in_project now returns the manifest-relative form (std::optional <std::string>) instead of a bool, and the glob is declared with that relative path. Measured directly on the recorded cache fingerprint (build.mcpp.cache's "glob <hash> java/**/*.java" line), which is the precise evidence: with the pre-fix absolute pattern, the recorded hash was identical (cbf29ce484222325) whether or not a .java file existed under the project root; with the fix, the hash changes (78b24cb88185e8c9 -> 4cd5de0f1766a171) when one is added. tests/apk-consumer/ check-apk-features.sh's new leg (f) asserts this directly, plus the external root's continuing absence from any recorded glob, plus (as a functional sanity check, not a caching claim -- this engine reruns build.mcpp on every `mcpp pack` of this fixture regardless of any glob, so a dex-content check alone cannot distinguish the two) that classes.dex picks up a class added under either root.
1 parent db16e27 commit fe1db3e

15 files changed

Lines changed: 706 additions & 70 deletions

File tree

.github/workflows/ci.yml

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ env:
2929
# every dispatched format was unreachable on macOS, including one that never
3030
# reads the staged tree. Staging is a service to the provider in 2026.9.11.2,
3131
# and `dist-apple` has been unreachable, not broken, since it was written.
32-
MCPP_VERSION: 2026.9.12.3
32+
MCPP_VERSION: 2026.9.13.1
3333
# PINNED, AND WITHOUT IT THE CACHE BELOW CACHED NOTHING.
3434
#
3535
# A released mcpp is self-contained: with no `MCPP_HOME`, `mcpp self env`
@@ -1127,7 +1127,11 @@ jobs:
11271127
# --target wasm32-emscripten` -- see `tests/web-consumer/
11281128
# check-web-plan.sh`'s own header for the host-toolchain defect this
11291129
# needed fixed first (mcpp commit 23974c5e, #622 e2e 657) and the
1130-
# MCPP_VERSION this step therefore needs at least. `mcpp toolchain
1130+
# MCPP_VERSION this step therefore needs at least. Since 0.9.0 the same
1131+
# pin is also the floor for `${mcpp.self}` and `mcpp stage`'s argument
1132+
# shape as an engine contract (design record
1133+
# `2026-09-13-four-upstream-asks-from-a-ui-framework.md`, §4), which
1134+
# `dist-web`'s two copy steps now use in place of `cp`. `mcpp toolchain
11311135
# install` first, because this job's `~/.mcpp` cache does not carry
11321136
# the emsdk payload the way the local sandbox this member was
11331137
# developed against does.
@@ -1137,6 +1141,14 @@ jobs:
11371141
- name: dist-web produces a static directory, and node runs it
11381142
working-directory: tests/web-consumer
11391143
run: MCPP="$MCPP" ./check-web-plan.sh
1144+
1145+
# P3's own criterion: a second, no-op pack copies nothing, which `cp`
1146+
# could never give and `${mcpp.self} stage --verify content` gives for
1147+
# free. A sibling script, not an addition to the one above, so that
1148+
# one keeps passing exactly as it did before this batch.
1149+
- name: dist-web's second pack copies nothing
1150+
working-directory: tests/web-consumer
1151+
run: MCPP="$MCPP" ./check-web-idempotent.sh
11401152
# `kind = "app"` on `*-linux-android` links a shared object (#622 A3),
11411153
# so this fixture's own toolchain resolution provisions `xim:android-
11421154
# ndk` for `--target x86_64-linux-android` exactly as mcpp's own CI
@@ -1195,6 +1207,20 @@ jobs:
11951207
|| { echo "FAIL: minSdkVersion is not 24 (this fixture's own min_api_level)"; cat -A badging.log | head -12; exit 1; }
11961208
echo "ok: dist-apk level 0 -- a signed, aapt2-readable APK carrying the native library, the deployed asset and the run sidecar"
11971209
1210+
# P1/P2's own criteria (design record §3.5 / §9.2), beside the step
1211+
# above rather than folded into it: (a) level 0 with no template is
1212+
# 0.8.0's manifest byte-for-byte; (b) a template's own permission and
1213+
# receiver reach the linked apk; (c),(d) a template missing a required
1214+
# token, or naming an unknown one, is refused at plan time, checked on
1215+
# build.mcpp's own stdout/stderr since mcpp discards it on a plan-time
1216+
# refusal's exit code of 0 (the script's own header says why); (e) two
1217+
# Java roots, one under this project and one a sibling directory
1218+
# reached by an absolute path, dex into one file carrying classes from
1219+
# both.
1220+
- name: dist-apk's manifest template and Java roots, (a) to (e)
1221+
working-directory: tests/apk-consumer
1222+
run: MCPP="$MCPP" ./check-apk-features.sh
1223+
11981224
# Compiles the device unit on a machine with no GPU: the clang route
11991225
# produces sm_89 code from the payload toolkit. Running it needs a
12001226
# device, so the run is of the CPU variant, which the same seam serves.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ engine's own module family and is not used here.
7474
| `dist-appimage` | `mcpp.dist.appimage` | 2026.9.11.1 | `xim:appimagetool`, which this feature declares on the `cfg(linux)` axis. Linux only. Turns the tree `mcpp pack` staged into one AppImage: the staged bundle is already an AppDir bar three files, so the member writes an `AppRun`, a `.desktop` entry and an icon into it and invokes one tool -- it never copies or re-lays-out a tree that can be hundreds of megabytes |
7575
| `dist-wix` | `mcpp.dist.wix` | 2026.9.11.1 | `xim:wix`, which this feature declares on the Windows target axis; the .NET 6 runtime the tool needs is a Windows component the payload does not carry, and `wix --version` names it when it is missing. Windows only. Renders a `.wxs` and passes the program in as a preprocessor variable, because a bind path that resolves to nothing is silent |
7676
| `dist-apple` | `mcpp.dist.apple` | 2026.9.11.2 (macOS), 2026.9.12.3 (iOS) | the base macOS install (`ditto`, and `codesign` only when an identity is given). macOS: `Contents/`-shaped, as always. iOS (`aarch64-ios-sim`, `aarch64-ios`): a flat bundle at the same call site -- no separate feature, no separate module -- with `MinimumOSVersion` from `mcpp::min_platform_version()` (#622 A11), `CFBundleSupportedPlatforms` read from `env == "sim"`, `UIDeviceFamily`, `LSRequiresIPhoneOS`, and a directory of flat PNGs listed under `CFBundleIcons` in place of macOS's single `.icns` file. Signing is skipped on the simulator row (`options::identity` is ignored, with a `mcpp::warning` naming why) and unchanged on the device row. The iOS row is measured end to end on `macos-15`: a real `mcpp build`, `mcpp pack --format app` and `mcpp run` against `aarch64-ios-sim`, through `xim:apple-simulator-tools`' `simctl-run`. **The macOS floor is one release higher than its siblings** and the reason is not this member: under 2026.9.11.1 `mcpp pack` staged before dispatching and let a staging failure fail the command, so on a Mach-O program -- which the built-in closure walk refuses, because it uses `LD_TRACE_LOADED_OBJECTS` and dyld answers that by running the program -- every dispatched format was unreachable, including one that reads no staged tree. 2026.9.11.2 makes staging a service to the provider |
77-
| `dist-web` | `mcpp.dist.web` | 2026.9.12.3, the release that carries #622: the `.js` launcher and the staged stem family, `mcpp::deploy`, and the build program's host compiler under the Web row | nothing beyond mcpp: `wasm32-emscripten` only. Copies `${mcpp.stage_dir}/bin/` -- the `.js` launcher, the implicit `.wasm`, the `.data` when present, and every `mcpp::deploy`'d file, all of which #622 A5 and A4 already stage there -- to `<out_dir>/web/`, dropping the `bin/` prefix a browser has no use for, and writes an `index.html` rendered from a project template or a built-in default that loads the script with a plain `<script src>`. **POSIX hosts only for now**: each file is copied with one `cp SRC DST` action (argv only, no shell), and `cp` does not exist on Windows; a portable copier is future work this member does not need yet |
78-
| `dist-apk` | `mcpp.dist.apk` | 2026.9.12.3, the release that carries #622's engine additions (`kind = "app"`, `mcpp::deploy`, `mcpp::min_platform_version`) | `xim:android-build-tools`, `xim:android-platform` (versioned by API level, read back for `targetSdkVersion`), `xim:jdk-temurin` (`javac`/`jar`; `android-build-tools`' own runtime dependency provisions a JDK for its OWN wrappers only), `xim:android-debug-keystore`, all on the `cfg(env = "android")` axis. Generates `AndroidManifest.xml` and signs with the published Android debug key by default. Level 0 needs no Java (`hasCode="false"`, `android.app.NativeActivity`); `options::java_sources` adds `javac` + `d8` and a real `<activity>`. Android only -- an `app` target is a shared object on this row (#622 A3), and this member reads its native library from the staged tree and whatever `mcpp::deploy` placed beside the ordinary build's link output, because Android's own closure stages neither the dependency set nor the deployed files (see `dist/apk.cppm`'s header). CI packages both level 0 and level 1 and checks the archive (`libc++_shared.so` bundled only when the closure's `NEEDED` entries require it, `mcpp::deploy`'d files under `assets/`); the runner has no emulator or device, so the two rows that actually run were measured locally on 2026-09-12, through `adb-run`, against a KVM-accelerated x86_64 emulator and a physical arm64-v8a phone, both printing `1-2-3` and exiting 0 |
77+
| `dist-web` | `mcpp.dist.web` | 2026.9.13.1, the release that carries `${mcpp.self}` and `mcpp stage`'s argument shape as an engine contract (`stage --verify content --output <dst> <src>`) -- what lets this member's copy run on every host mcpp does, Windows included, in place of the `cp` this member used through 0.8.0 | nothing beyond mcpp: `wasm32-emscripten` only. Copies `${mcpp.stage_dir}/bin/` -- the `.js` launcher, the implicit `.wasm`, the `.data` when present, and every `mcpp::deploy`'d file, all of which #622 A5 and A4 already stage there -- to `<out_dir>/web/`, dropping the `bin/` prefix a browser has no use for, and writes an `index.html` rendered from a project template or a built-in default that loads the script with a plain `<script src>`. Each staged file and the rendered page are copied with `${mcpp.self} stage --verify content --output <dst> <src>`, the same copier every `stage_file` edge in `build.ninja` already runs -- no host-specific copy tool, no plan-time `create_directories` (`stage` creates the destination's parent), and a second `mcpp pack --format web` with nothing changed copies nothing |
78+
| `dist-apk` | `mcpp.dist.apk` | 2026.9.13.1, raised alongside `dist-web` in the same 0.9.0 release: this member's own manifest-template and Java-array changes ask nothing new of the engine, but this collection publishes one package at one version, and this is the release CI verifies it under from here on | `xim:android-build-tools`, `xim:android-platform` (versioned by API level, read back for `targetSdkVersion`), `xim:jdk-temurin` (`javac`/`jar`; `android-build-tools`' own runtime dependency provisions a JDK for its OWN wrappers only), `xim:android-debug-keystore`, all on the `cfg(env = "android")` axis. Generates `AndroidManifest.xml` and signs with the published Android debug key by default. Level 0 needs no Java (`hasCode="false"`, `android.app.NativeActivity`); `options::java_sources` adds `javac` + `d8` and a real `<activity>`. `options::manifest_template` renders a project manifest with six tokens substituted verbatim; `{{application_id}}` and `{{activity}}` are required always and `{{lib_name}}` at level 0, each refused by name at plan time when missing (naming `assets/mcpp-run.json`, which `adb-run` reads them from too) or when the template names an unknown token; empty renders 0.8.0's manifest byte-identically. `options::java_sources` is an array: one `javac` over every root's `.java` files and one `d8` over the result, so a project's own sources and a path dependency's join without being merged into one directory first, and `rerun_if_changed_glob` is declared only for a root under `mcpp::manifest_dir()` -- a dependency root's files are already inputs of the `javac` action and its version is already in the build's fingerprint. Android only -- an `app` target is a shared object on this row (#622 A3), and this member reads its native library from the staged tree and whatever `mcpp::deploy` placed beside the ordinary build's link output, because Android's own closure stages neither the dependency set nor the deployed files (see `dist/apk.cppm`'s header). CI packages both level 0 and level 1 and checks the archive (`libc++_shared.so` bundled only when the closure's `NEEDED` entries require it, `mcpp::deploy`'d files under `assets/`); the runner has no emulator or device, so the two rows that actually run were measured locally on 2026-09-12, through `adb-run`, against a KVM-accelerated x86_64 emulator and a physical arm64-v8a phone, both printing `1-2-3` and exiting 0 |
7979

8080
### Each rule brings its own environment
8181

0 commit comments

Comments
 (0)