Repository navigation
mcpp: six platforms from one manifest — iOS, Android, Web, every distribution format, and the CLI mapping - #6
Open
Sunrisepeak wants to merge 39 commits into
Open
Sunrisepeak wants to merge 39 commits into
Sunrisepeak wants to merge 39 commits into
Conversation
The msi and appimage providers huxerui.rules carried were the same thing mcpp:plugins 0.8.0 ships as dist-wix and dist-appimage, and 0.9.0 adds dist-apple's iOS row, dist-apk and dist-web. The rule package now depends on the collection and translates HuxerUI's option surface into each member's options; the WiX definition, the AppImage helper, the argv builders and their tests go with the providers they served. xim:wix leaves both manifests: a dist member declares the payload it runs on its own feature axis, and a host module's declaration reaches every build program it is compiled into, so neither the framework nor an application names it (mcpp#622 A8). The CMake installer -- HuxerUIWindowsInstaller.cmake, the bootstrapper application and its WiX sources -- is untouched.
…he Web One manifest now serves six rows. The framework's `platforms` lists them; the iOS, Android and Web sections mirror IOS.cmake, Android.cmake and Web.cmake as the desktop sections already mirrored theirs, and huxerui-build-check compares all six pairs. Android is `env = "android"` on `os = "linux"`, so the GTK sections narrow to the desktop. The resource package reaches each runtime where it reads it: deployed per file (mcpp 2026.9.13.1 protocol 11) under `<target>.resources/` on the desktop rows, `HuxerUI/` in the Apple bundles, `assets/` in the APK, and preloaded into MEMFS on the Web. hrc:merge declares the paths it writes, and huxerui-source-select predicts them so a stale declaration is a test failure rather than a missing file. Every distribution format is provided on its row with nothing stated: `mcpp pack --format msi|appimage|app|web|apk` works on a fresh project, and `configure()`'s options only change what a format produces. When a member declines the format `mcpp pack` asked for, its reason -- which mcpp discards from a successful build program -- is repeated as a warning. The Java host loads the application's library from `org.huxerui.app_library` before HuxerUIView looks for libhuxerui.so, which under mcpp is the same file; the manifest the rule ships names it. The host compiles against API 36, the compileSdk the Gradle build states, so the rule package pins xim:android-platform 36-r2 over dist-apk's 35. An application exits on its own when HUXERUI_SMOKE_EXIT_MS is set, which is what lets CI run each row's program and assert it started. The exception model is required on the Web only (`requires_abi`, a target-axis key mcpp 2026.9.13.1 honours); the `-pthread` dialect flag is gone, since no translation unit carries it and the payload glibc has pthread in libc. tools/resource_compiler is 0.3.1 so that mcpp's per-version tool cache picks up the depfile flag.
`huxerui build|run|package|doctor|platform add` on a project whose root is an mcpp.toml maps onto `mcpp build|run|pack --target <triple> [--format <f>]`: linux to <arch>-linux-gnu and appimage, windows to <arch>-windows-msvc and msi, macos to <arch>-macos and app, web to wasm32-emscripten and web (run serves the packed directory), android to x86_64-linux-android or aarch64-linux-android and apk, ios to aarch64-ios-sim or aarch64-ios and app. `create --build mcpp --platform` narrows the manifest's `platforms`, and `platform add` extends it, with web written as mcpp's emscripten. The CMake-only options are refused by name on an mcpp project rather than ignored.
The design records the six-row table, the resource deployment prefix per row, the ABI switches (exceptions on the Web, threads nowhere), the distribution members and their defaults, and the known gaps: store signing, Kotlin and iOS devices stay with CMake, a universal APK is one ABI at a time, the min_api_level sweep warning, the cached host tool hazard, and the doubled xim:android-platform pin. The guides carry the mcpp command for each platform beside the CMake one, and the smoke variable.
Pins mcpp 2026.9.13.1. The Linux job asserts the resource package beside the executable, runs the example under xvfb with HUXERUI_SMOKE_EXIT_MS and packs the AppImage. New jobs build and pack the Web directory (page, launcher, wasm magic, preload data), the iOS simulator bundle (MinimumOSVersion 15.0, the resource index inside, a simctl-run smoke) and the x86_64 APK (the application library, the resource index, the dex, the signature, apksigner verify). .agents/ holds working design notes and mcpp.lock is a resolution record of exactly-pinned manifests; neither is committed.
A restored cache keeps whichever mcpp was active when it was saved, and `xlings install mcpp@<version>` adds the version without switching to it -- the log said so: "installed, but 'mcpp' still resolves to 2026.9.10.2". Every job now runs `xlings use` and asserts the version it got.
huxerui::rules::linux_gtk(link) runs the pkg-config probe the framework's build program used to carry. The payload table it reads is declared in the rule package's manifest: xpkg_dir answers only for payloads the building package declared, a host module's [xlings.workspace] counts as that package's own, and the rule package is compiled into every application's and library's build program -- so a library whose sources include GTK headers (Lib-Live2D's GL surface) calls the rule instead of copying 37 entries. huxerui-build-check asserts the rule's table and the root manifest's stay identical. A library's build program no longer deploys the builtin resource package: on the rows where every package's prefix is the same (`HuxerUI/`, the APK's assets) a library deploying it beside the application's copy was `runtime deploy collision`, measured on the macOS row with example 03. A library with resources of its own gets them compiled for its own header, and says that they do not reach the application's package under mcpp yet. The application's own accessor header (`<namespace>_resources.h`) is now on its include path. A non-inline function pins std::vector<std::string>'s initializer_list constructor into the rule module's object: GCC 16 emits an instantiation an imported inline function needs only in a translation unit that instantiated the class for itself, and a one-line build program does not.
…agic as hex The pwsh and clang jobs lacked the `xlings use` the others gained; on both, the restored cache's mcpp 2026.9.10.2 stayed active and compiled the plugin collection against an API without pack_format. The Web check compared `od -c` output against a pattern with single spaces, which od does not print.
Its build program has no launcher to package, so a member it asked only declined beside the application's own answer -- and, since the rule repeats a declined format's reason as a warning, said so on every `mcpp pack`. The design's known gaps record what the six rows measured: a library's resources stay with the library, the iOS simulator row's link (libc++'s atomic-wait table and compiler-rt's `@available` support are both missing from what the engine composes), and dist-apple placing deployed directories where NSBundle does not look.
The recipe is in Sunrisepeak/xim-pkgindex#1 and not yet in the published index. The Android job gives the synced index copy the recipe file and its cache entry, in a step that says it is temporary and what removes it.
…cted to fail The Windows job asserted a rendered Package.wxs, the file the rule's own WiX provider used to write; dist-wix renders its own and the File table is the check that matters. The iOS simulator job keeps running with continue-on-error until mcpp's row links a real application (design §9).
…tages Mach-O programs Measured on the macOS row: `mcpp pack --format app` refuses to stage a program in every --mode (the engine would have to run it to trace its dynamic closure, and dyld ignores LD_TRACE_LOADED_OBJECTS), so dist-apple bundles the bare executable and nothing deployed reaches it.
…builtins The simulator row failed to link on two things the engine composes: the llvm 22.1.8 payload's libc++ headers reference runtime symbols (`__atomic_*_global_table`, `__hash_memory`) that the SDK's libc++.dylib does not export, and `@available` compiles to `__isPlatformVersionAtLeast` from compiler-rt's iOS builtins, which nothing links. Every application manifest -- the examples and the templates -- pins the iOS rows to llvm@20.1.7, whose headers reference only what Xcode 26's libc++ has; the pin is the root's because a toolchain is. The framework's build program locates the builtins archive (the payload first, then Xcode's through xcrun) and names it on the link line, which reaches every consumer's link.
huxerui.rules was one 990-line interface holding four things. mcpp compiles
one interface unit per host-module package (a partition is not built --
measured: `huxerui.rules:probe: failed to read compiled module`), so the
split is by package, and `huxerui.rules` re-exports the two it grew:
huxerui-build-rules-gtk the Linux GTK payload table (its manifest) and
huxerui::rules::linux_gtk(); huxerui-build-check
keeps the table identical to the root's
huxerui-build-rules-dist the five distribution formats as mcpp:plugins'
members, their option types, the manifest and
page templates they ship; provide_formats()
huxerui-build-rules options, codegen, resources, configure()
A consumer's `import huxerui.rules;` sees the same names as before.
The launcher icon set the two application templates carried is gone rather
than shipped once by the rule: dist-apk 0.9.0 links a res/ directory as an
aapt2 overlay without --auto-add-overlay, which rejects every resource the
base does not define, so no res/ can be supplied through it yet (design §9).
HUXERUI_SMOKE_EXIT_MS is read by the Runtime: armed on the first frame, so the delay counts from the moment the application is up, scheduled with RequestFrameAfter on the frame scheduler, and ended with the same RequestApplicationQuit an application's own Quit() takes -- so a CI smoke run exercises shutdown too, and the exit status is main()'s. The detached thread that called _Exit(0) from beside the platform loop is gone.
The manifests' comments say why a key is there in one to three lines; the operator== note names the MSVC STL bug (microsoft/STL#6294) and when to default it again.
With the llvm@20.1.7 pin and Apple's builtins the simulator row links and dist-apple produces the bundle; what mcpp 2026.9.13.1 cannot do yet is stage a Mach-O program, so the bundle holds the executable alone. The job asserts the bundle and its Info.plist, says which state the run is in, and runs the simulator smoke only once the engine stages. No longer expected-fail.
Armed on the first frame, the timer never armed under a bare Xvfb: the Linux adapter waits for the paint of one frame before scheduling the next, and that paint never comes there, so both Linux jobs timed out at the smoke run. The Runtime now starts a timer thread at construction and, when it elapses, hands the quit to the platform's UI-thread dispatcher -- the one channel that reaches the loop from elsewhere -- guarded by a flag the state's destructor clears on that same thread. The exit path is still RequestApplicationQuit, and the status still main()'s.
…ndency `huxerui create app <name> --build mcpp --template live2d` renders an application that loads a Live2D model (Mao, from Live2D's samples, Free Material License, under resources/raw/) through the generated raw-resource identifiers and shows it with ModelView -- the ecosystem-library case as a template: one `huxerui.live2d` line in the manifest, nothing about Cubism in the application, all six rows. The template pins HuxerUI and Lib-Live2D by git revision -- Lib-Live2D pins the framework at the revision its mcpp build was made against, and mcpp refuses two sources for one package, so the application names the same one; both become versions once published. The CLI therefore leaves this template's dependency lines alone instead of rewriting them to a path, and the templates job creates and builds it beside the others.
The live2d template's model PNG carries the byte pair the token scan looks for; a file with a NUL byte is a copied asset, not a template.
hrc gains --module-name and writes, beside the header, a module interface unit exporting the same declarations over `import huxerui;`. The rule declares that unit to mcpp as a generated module interface -- `provides` and `imports` on the hrc edge, the mechanism hcg's transformed units already use -- so an application's module unit names its resources with `import app.resources;` and opens with no global module fragment. The live2d template does; the header stays for the CMake build. hrc is 0.3.2 for mcpp's per-version tool cache.
HuxerUI at the commit whose hrc writes `app.resources`, and Lib-Live2D at the commit that pins that same HuxerUI -- one HuxerUI for the graph.
A header-style package -- Lib-Live2D's preview, its app.cpp including the headers -- has <typeinfo> and the scope prelude forced into every one of its translation units, and a forced include lands before `export module`, which no compiler accepts: every native row of the preview failed on the generated unit. The rule now writes the module only for a package with module interface units, where nothing is forced; the header-style one keeps the header it already includes.
…ackage The root manifest carried the same 37 entries the rule package declares. A host module's [xlings.workspace] provisions the payloads and reaches every build program the framework's included, so the root copy added nothing: with it removed the framework builds, the example runs and its RPATH is byte-identical. huxerui-build-check now refuses a second copy at the root and requires the four modules the probe asks for in the one that remains. The table keeps its transitive closure: pkg-config searches the declared payloads and nothing else, and the exact pins are what make two machines build one binary.
The previous pin was rewritten away by an amend on the library's branch; from here on that branch only grows, so a pin stays fetchable.
openxlings/xim-pkgindex#834 is merged and the published index (f8c6801) resolves xim:android-platform@36-r2, so the Android job installs it like any other payload.
The floor moves to 0.9.1 (mcpp-plugins#21, indexed by mcpp-index#407): dist-apk links an application's `res/` as its base resources instead of an aapt2 overlay, and pins `xim:android-platform` 36-r2 itself, so the rule package's own pin -- and the two-versions warning on every Android build -- go. With a `res/` that can hold an icon, the rule supplies one: an application's `android/res` when it has one, else the launcher icon set the SDK's Gradle template already ships (`tools/huxerui_cli/templates/platform/android/app/app/src/main/res`), so the icon lives once in the repository and the templates carry no copies. The manifest names `@mipmap/ic_launcher` unconditionally, which lets the `@@ICON@@` rendering step and the rule's own `read_file` go: dist-apk reads the SDK's template by absolute path and declares it `rerun_if_changed` itself. CI: `xlings install -y --use` activates the pinned mcpp in one line; the version assertion stays. Measured: example 01 packed for x86_64-linux-android lists `res/mipmap-*` and `resources.arsc`, `aapt2 dump badging` reports the icon and `targetSdkVersion:'36'`, and the pack log has no version-override warning.
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.
HuxerUI's mcpp build grows from three desktop rows to six — Linux, Windows, macOS, iOS, Android, Web — with every distribution format reachable from one manifest and one
build.mcpp, the SDK CLI driving mcpp projects with the same verbs it drives CMake projects with, and CI asserting each row. The CMake build system is untouched: the only deletions are the WiX definition and AppImage helper the mcpp path itself used to carry, now provided bymcpp:plugins.Closes HuxerUI#130, closes HuxerUI#131.
Floors
kind = "app", per-filemcpp::deploy, target-axisrequires_abi, per-target[runtime] frameworks,mcpp run --format).mcpp:plugins0.9.0 (dist-wix,dist-appimage,dist-apple,dist-apk,dist-web), a build-dependency ofhuxerui-build-rulesonly.xim:android-platform@36-r2, which is in Sunrisepeak/xim-pkgindex (PR linked below) and not yet upstream — see Not verified.Commits
ci(mcpp)2026.9.12.2 → follow-upsoperator==for the MSVC STL,windows_subsystem, HuxerUI#130, HuxerUI#131, manifest metadata.refactor(mcpp)pluginshuxerui.rulesreaches the formats throughmcpp:plugins; its own WiX/AppImage code and tests go.feat(mcpp)six rowsmcpp.toml; resources deployed where each runtime reads them; every format provided on its row with nothing stated; the Java host loads the application library;HUXERUI_SMOKE_EXIT_MS; no-pthread,requires_abi = { exceptions }on the Web only.feat(cli)huxerui build/run/package/doctor/platform addon an mcpp project map tomcpp build/run/pack --target <triple> [--format];create --build mcpp --platformnarrowsplatforms.docs(mcpp)mcpp/README.md, the platform guide, the build guide.ci(mcpp)rowsweb,ios-sim,androidjobs.feat(mcpp)GTK probe + librarieshuxerui::rules::linux_gtk(link)replaces the framework's private pkg-config probe, reading a payload table the rule package declares (host-module[xlings.workspace]reaches every build program);huxerui-build-checkkeeps it identical to the root's. A library's build program deploys no resource package and provides no format (the macOSruntime deploy collisionin example 03).ci(mcpp)fixesxlings usein every job (a restored cache kept 2026.9.10.2 active); the wasm magic read as hex; the Android job bridgesxim:android-platform@36-r2from openxlings/xim-pkgindex#834 until the index carries it.fix(mcpp)iOS linkllvm@20.1.7in every application manifest (22's libc++ headers reference symbols the SDK'slibc++.dyliblacks) and the framework's build program links Apple'slibclang_rt.iossim.afor@available. The simulator row links and bundles.refactor(mcpp)packageshuxerui.rulesis three host-module packages —huxerui-build-rules(options, codegen, resources,configure()),-gtk(the payload table +linux_gtk()),-dist(the five formats + the templates they ship) — re-exported through oneimport huxerui.rules;. A partition is not built for a host module, so the split is by package.feat(app)smokeHUXERUI_SMOKE_EXIT_MSis the Runtime's: a timer thread hands the quit to the platform's UI-thread dispatcher (a bare Xvfb never paints a frame, so nothing frame-based fires there), ended throughRequestApplicationQuit()— shutdown runs, the exit status ismain()'s.docs(mcpp)operator==note names microsoft/STL#6294.refactor(mcpp)one payload tablehuxerui-build-rules-gtk's manifest (4 direct modules + their 33-entry.pcclosure, pinned); the root's duplicate is gone — measured identical build, run and RPATH — andhuxerui-build-checkrefuses a second copy.feat(mcpp)resources modulehrc --module-namewrites<namespace>_resources.cppm(export module app.resources; import huxerui; …) beside the header; the rule declares it to mcpp as a generated module interface (provides/importson the hrc edge — the mechanism hcg's transformed units use), so a module-style application writesimport app.resources;and no header. The header stays for CMake.feat(templates)live2dhuxerui create app <name> --build mcpp --template live2d: a Live2D model (Mao, Free Material License) on all six rows through onehuxerui.live2ddependency; pins HuxerUI and Lib-Live2D by git revision (mcpp refuses path + git for one package), so the CLI leaves its dependency lines alone; the templates job creates and builds it. No headers:import app.resources;. Verified in a browser frommcpp pack --target wasm32-emscripten --format web.What to look at hardest
Every format is provided on its row without opt-in.
mcpp pack --format apk|app|web|appimage|msiworks on ahuxerui create --build mcppproject whose build program states only.target; theconfigure()options (.installer,.appimage,.apple,.android,.web) only change what a format produces. Off its row a format is unknown (mcpp pack --format apkon a Linux desktop build), which is the honest answer. A member that declines the requested format has its reason repeated as a warning — mcpp discards a successful build program's stderr, and that reason used to vanish behindno action claimed --format 'apk'.Resources.
hrcwrites the package once; the rule deploys it per file under<target>.resources/(Linux/Windows),HuxerUI/(macOS/iOS bundles, with an AppKit fallback to the directory beside the executable),assets/(APK) and--preload-file(Web).huxerui-source-selectpredicts the set of files hrc writes so the merge step's declared outputs cannot drift.Android.
kind = "app"is a shared object on this row;HuxerUIActivityreadsorg.huxerui.app_libraryfrom the manifest the rule ships and loads it, thenHuxerUIView'sloadLibrary("huxerui")is tolerated failing (the framework is inside the application's library). The Java host uses API 36, so the rule package pinsxim:android-platform = "36-r2"over dist-apk's 35-r2; mcpp resolves the two by distance and prints a two-versions warning on every Android build — expected until the member's pin moves (§9).ABI.
-pthreadis dropped entirely (no TU carries it; the payload glibc has pthread in libc) so no BMI records a thread model. Onlyrequires_abi = { exceptions = true }oncfg(os = "emscripten")remains, and every template writes the matching[target.'cfg(os = "emscripten")'.abi].CLI mapping. linux →
<arch>-linux-gnu/appimage; windows →<arch>-windows-msvc/msi; macos →<arch>-macos/app; web →wasm32-emscripten/web (run packs and serves the directory); android →x86_64-linux-android(emulator) oraarch64-linux-android(device)/apk; ios →aarch64-ios-simoraarch64-ios/app. CMake-only options (--source,--build-dir, …) are refused by name.Verified locally (Linux, mcpp 2026.9.13.1, plugins 0.9.0)
mcpp testinmcpp/huxerui-source-select(incl. the new resource-output prediction);huxerui-build-checkparity with six rows;mcpp buildof the root,mcpp/huxerui-tests, examples 01/02/03.bin/huxerui_example_import.resources/huxerui/resources.binpresent,HUXERUI_SMOKE_EXIT_MSrun exits 0 with the marker,mcpp pack --format appimage→ 20.7 MB AppImage.mcpp pack --target wasm32-emscripten --format web→index.html,.js,.wasm(magic checked),.data.mcpp pack --target x86_64-linux-android --format apk→ signed APK withlib/x86_64/libhuxerui_example_import.so,assets/huxerui/resources.bin,classes.dex;apksigner verifypasses (with the local 36-r2 recipe).huxerui_cli_tests: 45 cases / 452 assertions, including the new mcpp backend cases.CI, honestly
Green: parity, Linux, Linux/clang, macOS, Web, Windows, generated templates, Android, and — since the llvm@20.1.7 pin — the iOS simulator. What the green does not yet cover, recorded in design §9:
.appare the executable only. mcpp 2026.9.13.1 refuses to stage a Mach-O program in every--mode(it would have to run it to trace its dynamic closure), so nothingmcpp::deployplaced —HuxerUI/resources — reaches the bundle. The iOS job asserts the bundle and its Info.plist and skips the simulator smoke until the engine stages; the macOS job packs the library.Resolved along the way, on the ecosystem side:
xim:android-platform@36-r2,xim:glew,xim:esbuildare in the published index (openxlings/xim-pkgindex#834);mcpp:plugins0.9.1 (mcpp-community/mcpp-plugins#21, mcpp-index#407) links an application'sres/as the APK's base resources and pins 36-r2 itself, so every APK now carries a launcher icon (the SDK's Gradle-template icon set unless the application suppliesandroid/res) and the rule package pins nothing of its own.Not verified
.appbundle, the simulator run throughsimctl-run, and the Objective-C fallback inappkit_adapter.mmare exercised only by CI.androidCI job will fail untilxim:android-platform@36-r2is in the upstream index (the fork PR adds it). Locally the recipe was patched into the registry copy to prove the path.mcpp run --format apkon an emulator was not run; CI inspects the archive.