Repository navigation
CMake parity for a framework and its ecosystem: per-row linkage, dependency identity, shared-library staging, multi-source tests, DMG / Setup.exe — each with a CI check #634
Description
Activity
- added 16 commits that reference this issue
on Sep 14, 2026 中文摘要
二十一项已按归属全部处理完毕并发布:引擎 mcpp 2026.9.14.2(mcpp#637)及补丁 2026.9.14.3(mcpp#638)、官方插件 mcpp:plugins 0.10.0(mcpp-plugins#24)、载荷 xim-pkgindex#838、索引 mcpplibs/mcpp-index#426。分类与决定见设计记录
.agents/docs/2026-09-14-634-cmake-parity-items-by-home.md,计划与台账见.agents/docs/2026-09-14-634-implementation-plan.md。- 与 issue 的主要不同:A1 用「条件声明替换无条件声明」加按行
[target.<sel>.targets.<n>] kind,不新增requires_linkage;A5 用[test] discover加「多源套件即包」,不做[[test]]/labels;A7 实测全新 home 的 SubOS 视图已能解析 gtk4(只缺 libxml2 的.pc,已补),37 条 pin 不需要;A8 不改引擎;A9 由引擎陈述平台下限事实、依赖用version-floor拒绝,不抬高下限;C4 不改 xlings。 - issue 之外实测并修复的缺陷:Android 测试程序因找不到 NDK 静态 libc++ 而全部依赖
libc++_shared.so(模拟器上无一能加载);rpath 中@executable_path被锚定到包目录;simctl launch永远返回 0;dist-apk第二次打包丢依赖库、多 ABI 失败;嵌套测试加载不到图构建的共享库;依赖与根构建程序同时提供同名 runner 时拼成一条 argv;在执行过xlings subos use的 shell 中 mcpp 的 registry 把工具与载荷装进另一个 subos(沙箱验证中发现,2026.9.14.3 修复)。 - HuxerUI 侧可以做的变更列在文末;本次未修改 HuxerUI。
What landed, per item
Each item was first located in the code and its premise measured (local probes, and the temporary measurement PR mcpp#635 on macos-15, an API 34 emulator and windows-2022); the decisions and every reading are in
.agents/docs/2026-09-14-634-cmake-parity-items-by-home.md, and the task ledger with the test that closes each task is.agents/docs/2026-09-14-634-implementation-plan.md. Routing followed one rule: the engine takes general capabilities, the official plugins take distribution formats and build rules, payloads take runner programs.A. Engine (mcpp 2026.9.14.2, mcpp#637; patch 2026.9.14.3, mcpp#638)
Item What landed What HuxerUI can write CI check A1 A matching [target.<sel>.dependencies]declaration replaces the unconditional declaration of the same identity on its rows (the rule every conditional scalar already follows; no manifest among 509 scanned declared a key in both tables). A package states its form per row with[target.<sel>.targets.<n>] kind = "shared"; a consumer'slinkage = "static"against it is warned naming that line, and--strictrefuses. The modifier-only spellinghuxerui.huxerui = { linkage = "shared" }is reported with the dependency restated with its source. A misspelled[target.<sel>]sub-table is reported. Notrequires_linkage: every otherrequires_*key refuses, andkind = "shared"already selects the shared form.The framework writes [target.'cfg(env = "android")'.targets.huxerui] kind = "shared"once; applications keep one unconditional dependency line, or write the conditional table with the source when an application rather than the framework decides.e2e 677, 678 A2 A path/gitdependency takes the identity its manifest declares, with one warning per declaring edge naming the requester, the key and both identities; one directory reached asfwandhuxdemo.fwis one package compiled once. Two identities over one source (a manifest without a namespace) are refused before scanning; the scanner's duplicate-provider message names both packages.The three keys in huxerui-build-rules,huxerui-tools,huxerui-testscan statehuxerui.*; until then they build with the warning.e2e 679 A3 mcpp packreads the Android and Mach-O closures from the files. Android stageslib/(perlib/<abi>/with several--target), graph libraries andlibc++_shared.soincluded; Mach-O stages dylibs beside the program, with no load-command edit.<tree>.stage-manifestgains `needs<pathplatform unresolved> lines;walkedis written only for a complete closure, anddir/tarrefuse an incomplete one naming the names. rpath entries starting with@executable_path,@loader_pathor@rpath` are no longer anchored to the package directory (a defect found while measuring B1).A4 An ELF shared library without a declared sonamerecords its file name as SONAME (bionic enforces one from API 23); a-sonameinldflagsstill wins. The Mach-O half was already@rpath/<file>.Nothing. e2e 667, 669 A5 [test] discover = [globs](the[build] sourcesvocabulary,!exclusions,[]disables); names stay path-relative. Not[[test]]with exclusive sources and labels: the target model compiles objects once and links them into every target, and sends a divergent program to a workspace member, so a suite built from several sources is a package selected withmcpp test -p.discover = ["checks/**/*.cpp"]for atests/another build owns; one package per multi-source suite.e2e 673 A6 On the emulator no test could load at all: the engine did not find the NDK's static libc++, degraded to libc++_shared.so, and the device has none. The static runtime is now located by asking the driver for the effective target (the NDK'slibc++.alinker script's archives also reach--exclude-libs: 161 exported dynamic symbols became 4). Every runner ofmcpp runandmcpp testreceivesMCPP_RUNTIME_FILES, a TAB-separated list of the deployed files and linked shared libraries, andadb-runpushes them beside the program. The iOS simulator reads the host filesystem and needed nothing.Drop -static-libstdc++from the Android test rows.ci-linux-e2ejobandroid(API 34 emulator:runsandreads_relativepass)A7 Measured on a fresh home: the SubOS pkg-config view already resolves gtk4completely; onlylibxml-2.0.pcwas missing (the recipe had no pkg-config handling, fixed in xim-pkgindex#838). The 37 pins are unnecessary. Build programs getmcpp::pkg_config_libdir(). The sandbox verification found that inside a shell that ranxlings subos use, mcpp's registry installed payloads into a SubOS of the shell's name, so the view the accessor names stayed empty; 2026.9.14.3 fixes it (mcpp#638).Declare the direct payloads and run PKG_CONFIG_LIBDIR=<mcpp::pkg_config_libdir()> pkg-config gtk4.e2e 683; sandbox A8 No engine change. A library's mcpp::deployresources already reach the application from two levels down, andmcpp why depsnow prints the resolved graph (below). A graph API for build programs waits for a need transitive deploy cannot answer.Each library deploys its resource package under a common directory. - A9 The engine states android.api-level,ios.deployment-targetandmacos.deployment-targetas facts; a dependency writes the ordinary[[runtime.requirements]] kind = "version-floor"against one and is refused before compiling, naming the key that set the value ("this build targets 21",set by: [target.x86_64-linux-android] min_api_level). Not raising the floor: it is already in the compiler's--targetwhen the graph is walked, and an application's floor is its own decision.value = "android.api-level >= 23"in the framework.e2e 680 A10 run,testandpackaccept--toolchain; a refused value is named as the command line's.--toolchain llvm@22.1.8instead ofMCPP_TOOLCHAIN.e2e 671 X (new) resolution.jsonrecordsgraph(each package, each request's key and declaring table, a library's link form and why) andmcpp why depsprints it; path dependencies, whichmcpp.lockdoes not record, are listed.- e2e 682 B.
mcpp:plugins0.10.0 (mcpp-plugins#24)Item What landed What HuxerUI can write CI check B1 dist-appleplaces the dylibs the stage manifest names inContents/Frameworks/(Frameworks/on iOS), links the program with@executable_path/../Frameworksthroughmcpp::link_flag, so no file is edited after the link, and signs a bundle without an identity ad hoc, frameworks first. Measured:codesign --verify --deep --strictis valid; the program exits 7 through the framework and 134 with "Library not loaded" without it.Nothing beyond the dependency. macOS job, app-framework-consumerB2 --format dmg: the bundle beside anApplicationslink in a UDZO image (hdiutil verifyvalid); refused on iOS.mcpp pack --format dmgmacOS job B3 Engine: mcpp run --format <f>uses the runner named<f>(e2e 674). Plugin: on macOSdist-applesupplies the runner namedapp, which isxim:macapp-run(C1), installed formcpp runonly;mcpp run --format appreturns the program's status (exit 7 measured). A manifest runner of that name wins.Remove the open -Wdefault runner.macOS job B4 dist-wix--format setup: a Burn bundle chaining the MSI, with WiX's stock bootstrapper application fromxim:wix5.0.2-1; the MSIwix burn extracttakes out of the bundle is byte-identical to the built one.options::bundle_output = "setup.exe"is refused beforewixruns, sincewixrefuses that name (WIX0388).mcpp pack --format setupWindows job, msi-consumerB5 dist-apkreads the closure the engine stages (lib/for one--target,lib/<abi>/for several) in place of its ownNEEDEDwalk and stamp: a second pack keeps the dependency's library, and one APK carries every ABI (aapt2reportsnative-code: 'arm64-v8a' 'x86_64';apksignerverifies). A stage withoutneedslines comes from an older engine and is refused naming 2026.9.14.2; every refusal is also amcpp::warning, because the engine discards a build program's output when it exits 0.--format aabbuilds an App Bundle withxim:bundletool(bundletool validatepasses; a universal APK carries the dependency's library).mcpp pack --target aarch64-linux-android --target x86_64-linux-android --format apk, or--format aabLinux job, apk-consumer-sharedB6 New member rules-metal(.metal): onexcrun metalaction per shader with-MMD, onexcrun metallibaction per library, deployed undermetallib/, whichdist-applemaps into the bundle's resources; a missing SDK and a missing compiler are refused naming the command. A header edit recompiled only the shaders that include it.tools-esbuildstays in the project.features = ["rules-metal"]in place of the project's Metal rulemacOS job, metal-consumerC2 (CI) The iOS fixture exits 7, and the simulator step asserts that status through simctl-run0.3.0.- macOS job C.
xim-pkgindex(xim-pkgindex#838, merged)Item What landed CI check C1 xim:macapp-run0.1.0:macapp-run <X.app> [args]requiresContents/Info.plist, readsCFBundleExecutableandexecs the executable, so output and status are the program's (exit 7 returned; abort 134).apple runners / macapp-runC2 apple-simulator-tools0.3.0: a bundle whose executable does not load UIKit is installed and its installed executable spawned (measured: status returned, output kept 20 of 20;simctl launchreturned 0 in 140 of 140 launches for an app exiting 7, and 0 forabort()); a UIKit bundle keepssimctl launchand says the status is simctl's.apple runners / simctl-runC3 wix5.0.2-1:mbanative.dllanchored,WixToolset.BootstrapperApplications.wixextadded, an install-verify job, and the recipe states the MSVC-ABI link with the Windows import libraries (both of mcpp's MSVC-ABI toolchains link and runDutilInitializewith them).wix build -o setup.exeis refused by WiX itself (WIX0388).wix bundleC4 Usage today: a fresh MCPP_HOMEwith[index.repos.xim] url = "<checkout>"inconfig.tomlresolves an unmerged recipe and follows commits on the branch. Engine: the same table now reaches an existing home (and its removal restores the entry), and installations from it print the source. xim-pkgindex's own PR workflow builds consumer fixtures through the override. No xlings change was needed.engine e2e 681; consumer-through-index-overrideB5 support xim:bundletool1.18.3 onjdk-temurin;android-platform-tools37.0.1-4 (adb-runruntime files).bundletool,android runner / adb-runD.
mcpp-indexD1 follows HuxerUI's release (the descriptors are the project's to publish).
mcpp.plugins0.10.0 is indexed in mcpplibs/mcpp-index#426, andlatestpoints at it.Found beyond the issue, all fixed
- Android
dir/tararchives omitted their closure and saidwalked. - Android test programs could not load on a device (the static libc++ lookup, above).
dist-apk0.9.3 dropped the dependency's library on a second pack, and refused two ABIs without showing why.- The link-form degradation did not name the package's
kind = "shared"statement. - The version-floor refusal said "this machine has" for any fact.
config.toml [index.repos.<name>]did nothing on an existing home.@executable_path/@loader_path/@rpathrpath entries were anchored to the package directory.- A runner a dependency's build program supplies and the project's build program also emits became one argv (
run-A.sh run-B.sh <artifact>); refused now, naming both. - A test built from a subdirectory of
tests/could not load a graph-built shared library. simctl launchnever reported an application's status;libxml2published no.pc.- Inside a shell that ran
xlings subos use <name>, mcpp passedXLINGS_ACTIVE_SUBOSto its own xlings, which installed mcpp's tools and a project's payloads intoregistry/subos/<name>while mcpp readssubos/default(found in the sandbox; fixed in 2026.9.14.3, e2e 686).
What HuxerUI can change now (not done here)
- A1: the framework's per-row
kind, or the conditional table with its source; remove the two complementary predicates. - A2: state
huxerui.*in the three rule/tool/test manifests. - A6: remove
-static-libstdc++from the Android rows. - A7: remove the 37 GTK pins; declare the direct payloads.
- A10 / B3:
--toolchaininstead ofMCPP_TOOLCHAIN; drop theopen -Wdefault runner (it hands a plainmcpp run's program toopen, which returns 0);dist-applenow suppliesapp. - A5:
[test] discoverand one package per multi-source suite.
Verification
- CI: mcpp#637 green on all 41 checks, including the new Android emulator job and the iOS simulator job; mcpp#638 green on all 40; mcpp-plugins#24 green on its three jobs against the released 2026.9.14.2 (run 34831041382); xim-pkgindex#838 and mcpp:plugins 0.10.0: dist-apk and dist-apple read the staged closure; dmg, aab and setup formats; rules-metal mcpplibs/mcpp-index#426 green.
- Release: the eight mirrored engine assets are byte-identical on GitHub and GitCode, and the published xim index artifact names the release as
latest; the plugins tag archive and its GitCode copy are byte-identical (one sha256), and the published mcpp-index artifact names 0.10.0 aslateston both hosts. - Sandbox: in
xlings subos use m634-eco --sandbox, with the xlings and mcpp mirrors set to CN, the released 2026.9.14.3 installed throughxlings installandmcpp:plugins0.10.0 resolved from the index pass 22 of 22 checks (A1, A2, A3 and A6 onx86_64-linux-android, A4, A5, A7 withpkg-config gtk4, A9, A10, B3, X, anddist-apkthrough the index: two packs carrylib/x86_64/libfw.so, and one APK carries it for both ABIs), and C4 on that existing home passes 4 of 4. The same script on 2026.9.14.1 fails 18 checks. The first run, on 2026.9.14.2, failed the two pkg-config view checks, which is how item 11 above was found.
- 与 issue 的主要不同:A1 用「条件声明替换无条件声明」加按行
- added a commit that references this issue
on Sep 14, 2026 Closing: all twenty-one items were delivered and released, per the triage comment above, and the follow-up work has since shipped on top of them.
- Engine (A): mcpp 2026.9.14.2 (The engine half of #634: conditional dependency replacement, manifest identity, staged Android and Mach-O closures, runner runtime files, the resolved graph (2026.9.14.2) #637) and the patch 2026.9.14.3 (mcpp's xlings invocations do not inherit XLINGS_ACTIVE_SUBOS (2026.9.14.3) #638).
- Plugins (B):
mcpp:plugins0.10.0 (0.10.0: dist-apk and dist-apple read the staged closure; dmg, aab and setup formats; rules-metal (mcpp#634) mcpp-plugins#24). - Payloads (C): feat: runner programs and payloads for mcpp#634 (macapp-run, simctl-run 0.3.0, adb-run runtime files, wix extension, bundletool, libxml2 pkg-config) openxlings/xim-pkgindex#838.
- Index (D): mcpp:plugins 0.10.0: dist-apk and dist-apple read the staged closure; dmg, aab and setup formats; rules-metal mcpplibs/mcpp-index#426.
Records:
.agents/docs/2026-09-14-634-cmake-parity-items-by-home.mdfor the classification and the decisions,.agents/docs/2026-09-14-634-implementation-plan.mdfor the plan and the ledger.Where the delivery differs from the issue text is stated in that comment and is worth repeating here, because a reader coming back to this issue will otherwise look for shapes that were not built: A1 became "a conditional declaration replaces an unconditional one" plus per-row
[target.<sel>.targets.<n>] kind, with no newrequires_linkage; A5 became[test] discoverplus "a multi-source suite is a package", with no[[test]]and no labels; A7 turned out not to need the 37 pins, because a fresh home's SubOS view already resolves gtk4 once libxml2's.pcwas added; A8 needed no engine change; A9 has the engine state the platform floor as a fact and the dependency refuse throughversion-floor, rather than raising the floor; C4 left xlings untouched.Five defects found while implementing, none of them reported here, also landed: Android test programs depending on
libc++_shared.sobecause the NDK static libc++ was not found (none of them loadable on the emulator);@executable_pathin an rpath anchored to the package directory;simctl launchalways returning 0;dist-apklosing dependency libraries on a second pack and failing on multiple ABIs; nested tests unable to load a graph-built shared library.This issue also carried #622's last open item (B4, as B6 here), so closing it closes that line too.
中文摘要
HuxerUI 的 mcpp 构建链在六个平台上已默认对齐 CMake 的链接、加载与资源布局约定(fork PR 见文末)。逐项对照 CMake 链之后,仍需要 mcpp 生态补齐的共 21 条,按归属分为引擎(A)、
mcpp:plugins(B)、xim-pkgindex(C)和mcpp-index(D)四类。每条都写明实测现象、需求原因、建议形状,以及可在 GitHub 托管 runner 上跑的 CI 验收方法;不需要真机、签名证书或 secret。closure = walked),Mach-O 闭包未遍历,所以 macOS 的.app带不上 dylib。mcpp run --format app拿不到程序退出码。--toolchain;Context
#622 asked for what a UI framework needs on Android, iOS and the Web; #630 listed what the first six-platform build still hit. Both are answered: 2026.9.13.2, 2026.9.14.1 (#633) and
mcpp:plugins0.9.3 are what HuxerUI now builds on. On every row its mcpp path takes the CMake path's conventions by default:libhuxerui.soloaded by the Java host;mcpp test.The work is on three fork PRs, each green on every row: Sunrisepeak/HuxerUI#7 (framework, rule packages, CLI), Sunrisepeak/Lib-Live2D#2 (an ecosystem library) and Sunrisepeak/HuxerUI#8 (templates).
This issue is what remains when that mcpp path is compared with the CMake path item by item: linking, loading, packaging, tests and distributables. Each item gives:
Android checks use the emulator (KVM on
ubuntu-24.04) and iOS checks the simulator (macos-15). No check needs a device, a signing identity or a secret.Measured with mcpp 2026.9.14.1,
mcpp:plugins0.9.3 and xlings 2026.9.12.1, on a Linux x86_64 host (GCC 16.1.0,xim:android-ndk30.0.16248370) and in CI, unless an item says otherwise. CMake references point at unchanged upstream files, through the fork at64264cb.The only open item of #622 (B4) is carried here as B6, so #622 can be closed in favour of this issue.
linkageper row: the first spelling is dropped silentlylinkagemin_api_level, iOS deployment target)--toolchainonrun/test/packdist-applecarries graph-built dylibs (the plugin-side A3, asdist-apk0.9.3 did)dist-apple: DMGdist-apple: a default macOS--format apprunner that returns the program's statusdist-wix: a Burn bundle (Setup.exe)dist-apk: an Android App Bundlerules-metal,tools-esbuild(carried from #622 B4)simctl-run: output lost under--console-pty; exit status unmeasuredhuxerui.huxeruiandhuxerui.live2d, so libraries declare rangesA. Engine (mcpp)
A1. One identity, a different
linkageper row — P1Measured. HuxerUI's framework must be a shared library on Android and static elsewhere. The Java host loads
libhuxerui.soby name, and CMake's_huxerui_select_framework_targetrefuses a static framework on Android (cmake/HuxerUILibraries.cmake#L14-L28). The application manifest can say this three ways:x86_64-linux-android[dependencies] huxerui.huxerui = { path = "../../.." }and[target.'cfg(env = "android")'.dependencies] huxerui.huxerui = { path = "../../..", linkage = "shared" }linkageis dropped silently. There is nolibhuxerui.so; the application's.sohas noNEEDED libhuxerui.soand defines all 54Java_org_huxerui_*symbols itself. No diagnostic.[dependencies]line and[target.'cfg(env = "android")'.dependencies] huxerui.huxerui = { linkage = "shared" }[dependencies]line;cfg(env = "android")withlinkage = "shared"pluscfg(not(env = "android"))without itbin/libhuxerui.so, the application hasNEEDED libhuxerui.soandRUNPATH $ORIGIN. This is what HuxerUI ships.The second spelling is rejected like this:
Why.
libhuxerui.sofor the Java host'sSystem.loadLibrary("huxerui").dlopencontract on one platform is in the same position.Proposed.
linkageapplies on the rows its predicate selects. If merging is unwanted, refuse and name both tables. Never drop it silently.{ linkage = "shared" }(noversion,pathorgit) as "the declaration above, with this linkage on these rows". If that shape stays refused, say so, instead of parsinglinkageas a package name.requires_abishape (docs/22):[target.'cfg(env = "android")'] requires_linkage = "shared". The root still decides. A build whose resolved linkage differs is refused before compiling, naming the package and the selector.CI check (
ubuntu-24.04,xim:android-ndk).libdependency.mcpp build --target x86_64-linux-android,bin/lib<dep>.soexists andllvm-readelf -dof the application's.soshowsNEEDED lib<dep>.so. The host build has nolib<dep>.so.requires_linkageand the root does not; the build exits non-zero with the named refusal.A2. A dependency key that names a different identity than its manifest — P1
Measured. In
mcpp/examples/03-library, both edges point at the same directory, whose manifest declaresnamespace = "huxerui",name = "huxerui":huxerui.huxerui = { path = "../../.." }(per row, as in A1);huxerui = { path = "../../../.." }, which normalises to(mcpplibs, huxerui)(docs/05).The graph therefore holds two packages built from one directory, and the scanner fails:
This happened on the macOS and Windows rows in CI (run 34772646907) and reproduces on Linux. The message names neither dependency edge; the fix (spell the library's key
huxerui.huxerui) was found by reading every manifest in the graph.Why. Every ecosystem library declares the framework it builds on. One key written without the namespace, in any library of the graph, breaks every application that also names the framework, and the diagnostic names a module rather than a manifest line.
Proposed.
pathorgitdependency's identity from the manifest it resolves to. A key that normalises to a different identity is reported, for example asdependency/identity-mismatch: the key, what it normalises to, and what the manifest declares. Then either refuse, or take the manifest's identity; pick one rule and state it.CI check (
ubuntu-24.04).ns.dep = { path }; its library declaresdep = { path }for the same directory, whose manifest isns.dep. The build shows the named diagnostic, not a scanner error.A3. Shared libraries the graph built are not staged; Mach-O closures are not walked — P1
Measured.
mcpp pack --target x86_64-linux-android --format apkfor an application whose framework islinkage = "shared":closure = walked.lib/libhuxerui_example_import.soand the deployed resource files, and nolib/libhuxerui.so, although the application's.sohasNEEDED libhuxerui.soand the build producedbin/libhuxerui.so.dist-apk0.9.3 now walksNEEDEDitself withllvm-readelf(0.9.3: dist-apk carries the graph's shared libraries, and the manifest template gains the version tokens mcpp-plugins#23), so the APK carries the library. The engine's staged tree still claims a walked closure it does not carry.pack::runstep 4 reports a Mach-O program asclosure = not-walkedon every host.macho_needed,resolve_macho_names) landed, and that bundling waits for theLC_RPATHmeasurement..appthat does not contain the dylib. HuxerUI's macOS default is static, as CMake's is, so this blocks the non-default shared mode.What the CMake path does.
_huxerui_deploy_runtime(cmake/HuxerUIRuntimeDependencies.cmake#L640) runs at install time:file(GET_RUNTIME_DEPENDENCIES)finds the dependencies./System/Library/and/usr/lib/are copied intoContents/Frameworks.install_name_toolrewrites names to@rpath/…, deletes the old rpaths, and adds@executable_path/../Frameworksto the executable and@loader_path/…to libraries.codesign --force --sign -runs on each file and on the bundle.codesign --verify --deep --strictchecks the result.The same function copies DLLs beside the executable on Windows and fills
lib/withpatchelfon Linux.Proposed.
needed_names(ELF, PE and Mach-O) instead of running the program. Cross rows then get a real walk, andclosure = walkedmeans complete.@rpath/lib<name>.dylibby default.LC_RPATH @executable_path/../Frameworks. Setting this at link time, as the §4 note suggests, may make a load-command editor unnecessary.CI check.
ubuntu-24.04: pack A1's fixture forx86_64-linux-android; the stage manifest listslib/lib<dep>.so.macos-15: an application with alinkage = "shared"dependency, packed withmcpp pack --format app:Contents/Frameworks/lib<dep>.dylibexists;otool -L Contents/MacOS/<exe>names@rpath/lib<dep>.dylib, andotool -lshowsLC_RPATH @executable_path/../Frameworks;codesign --verify --deep --strictpasses, andContents/MacOS/<exe>exits 0.A4. No SONAME / install name for a dependency made shared by
linkage— P3Measured.
libhuxerui.so, produced bylinkage = "shared"forx86_64-linux-android, has noDT_SONAME. The application recordsNEEDED libhuxerui.sofrom the file name, plusRUNPATH $ORIGIN. It loads on Android.[targets.<n>] sonameexists, but a dependency made shared by its consumer'slinkagehas nowhere to state one.Proposed. A target resolved Shared through
linkagedefaults to the SONAMElib<target>.so, and to the Mach-O install name from A3.[targets.<n>] sonamestill overrides it.CI check.
ubuntu-24.04:llvm-readelf -d bin/lib<dep>.soshowsSONAME lib<dep>.so.macos-15:otool -Dprints@rpath/lib<dep>.dylib.A5. A test program from several sources; test roots; labels — P2
Measured.
mcpp's model. In docs/08, a test is
tests/**/*.cpp, and one.cppmakes one program.[build] sourceslink into every test program.Why HuxerUI needs a separate package. Its tests live in
mcpp/huxerui-tests, because the repository'stests/belongs to CMake: 111 files, some driven by CMake or Python, some linking the CLI, some per platform.CMake's shape. CMake builds one executable per suite from a list of sources, with labels:
HuxerUIUnitTests(common unit, 19 files);HuxerUIRuntimeTests(common runtime, 41 files);HuxerUIUiTestsandHuxerUILibraryTests;platformplusnativeorportable.The release workflow selects them with
ctest -LE platform,-L portableand-L native.Measured today. The 41 runtime files compile and pass unmodified in the mcpp package:
They only fit by being added to
[build] sources, which puts them into the same program as the unit suite, and into the smoke program too.Why.
tests/directory already belongs to another build system has the same problem.Proposed.
Add
mcpp test --label <l>and--exclude-label <l>, and put labels into--message-format json.CI check (
ubuntu-24.04). A fixture with two[[test]]programs whose sources do not overlap:nmfinds each marker only in its own binary;mcpp test --label aruns one program;A6. Windowless tests on the Android emulator and the iOS simulator — P3, verification first
Measured. Both runners already accept a bare executable:
adb-runpushes it to/data/local/tmp, runs it with oneadb shell, and parses the status back;simctl-runruns it withsimctl spawn.mcpp test --target x86_64-linux-androidand--target aarch64-ios-simshould therefore run windowless test programs, but this is unmeasured. The likely gap is the files a test reads: HuxerUI's tests compile a resource package's host-absolute path in (HUXERUI_TEST_BUILTIN_RESOURCE_PACKAGE), and no runner transfers it. CMake has platform-specific carriers for the smoke (an instrumentation.soon Android, an XCTest bundle on iOS). mcpp could cover more of the suite with bare executables.Proposed. Measure it and document the result. If files are the gap, let a test's deployed files (
mcpp::deploy) travel with it, found through a relative path (the working directory, orMCPP_TEST_DATA_DIR).CI check. A fixture test reads a deployed file and exits 0 or 1 on its content:
ubuntu-24.04: on an x86_64 API 34 emulator (KVM),mcpp test --target x86_64-linux-androidpasses.macos-15:mcpp test --target aarch64-ios-simpasses.A7. A payload's recipe-dependency closure is invisible to build programs — P2
Measured.
What HuxerUI asks for. Its Linux build asks pkg-config for
gtk4,epoxy,gio-2.0andlibsoup-3.0.What happens with only those declared. Declaring just the four direct payloads (
xim:gtk4,xim:libepoxy,xim:libsoup,xim:glib) is not enough. The 33 payloads that make up their.pcclosure are invisible to the build program, even though they are installed on the machine (xpkg_diranswers""), and pkg-config fails:The workaround. HuxerUI declares and exactly pins all 37 payloads in
mcpp/huxerui-build-rules-gtk/mcpp.toml, and updates that closure by hand whenever a recipe changes.Why. Any package that consumes a pkg-config stack through xim payloads (GTK, GStreamer, FFmpeg) has to copy its closure into its own manifest.
Proposed.
xpkg_diranswers for the recipe-dependency closure of a declared payload, at the versions actually installed. Those versions are recorded inmcpp.lock, so two machines still build the same binary.lib/pkgconfigandshare/pkgconfigdirectories, asmcpp::pkg_config_path()orMCPP_PKG_CONFIG_PATH.CI check (
ubuntu-24.04). A fixture declares onlyxim:gtk4:pkg-config --cflags --libs gtk4against the provided path and exits 0;xpkg_dir("xim", "pango")is non-empty;mcpp.lockrecords pango's version.A8. Enumerating the resolved graph, and package metadata, from a build program — P3
Measured.
mcpp::dep_dir/MCPP_DEP_<NAME>_DIRanswer by name, for each declared dependency.huxerui_use_librarymerges each HuxerUI library's resource package into the application's. On the mcpp path, an application cannot find which of its transitive dependencies carry resources, so a library's resources stay unmerged. Lib-Live2D's string catalogue is today's case.Proposed.
mcpp::dependencies()returns, for each package of the resolved graph: identity, version, kind, resolved linkage, manifest directory and requesters.[package.metadata.<tool>]table is readable from consumers' build programs (for examplemcpp::dep_metadata(identity, "<tool>.<key>")), and editing it re-runs those build programs.CI check (
ubuntu-24.04). Fixture: application → lib A → lib B, where B declares[package.metadata.demo] resources = "res".A9. A platform floor a dependency needs — P3
Measured.
min_api_level([target.<triple>]) andios_deployment_target([build]) belong to the artefact, so only the root states them. HuxerUI's manifests state API 23 and iOS 15.0. Every application and template restatesmin_api_level = 23on both Android rows andios_deployment_target = "15.0", because a dependency has no way to say what it needs.Proposed. Follow the
requires_abishape:A root whose resolved floor is lower is refused before compiling, naming the package. An unset iOS floor resolves to the SDK version (#633) and is compared as that.
CI check.
ubuntu-24.04: the dependency requires 23 and the root states 21, so the build is refused by name. With the root at 24, the output showsTarget x86_64-linux-android → x86_64-unknown-linux-android24.macos-15: the same foraarch64-ios-sim.A10.
--toolchainonrun/test/pack— P3Measured.
mcpp buildaccepts--toolchain;mcpp run,mcpp testandmcpp packanswererror: unknown option: --toolchain. HuxerUI's clang CI leg therefore switches the whole job withmcpp toolchain default llvm@22.1.8.Proposed. Accept
--toolchainonrun,testandpack, meaning what it means forbuild.CI check (
ubuntu-24.04).mcpp test --toolchain llvm@22.1.8andmcpp pack --toolchain llvm@22.1.8compile with clang, asmcpp why toolchainor the compile lines show, while the default toolchain is gcc.B.
mcpp:pluginsB1.
dist-applecarries graph-built dylibs — P1 if it lands before A3The plugin-side counterpart of A3, as
dist-apk0.9.3 did for Android:LC_LOAD_DYLIBwithllvm-otool -Lorllvm-objdump --macho --dylibs-used.bin/intoContents/Frameworks/(on iOS,Frameworks/).@executable_path/../Frameworks(on iOS,@executable_path/Frameworks) withllvm-install-name-tool.CI check. The
macos-15check of A3, and the same foraarch64-ios-simwithsimctl-runlaunching the bundle.B2.
dist-apple: DMG — P2What the CMake path does.
huxerui package macos(tools/huxerui_cli/platform/macos.cpp#L86-L101):Applications → /Applicationssymlink beside the bundle;hdiutil create -volname <name> -srcfolder <staging> -format UDZO -ov <target>-<version>.dmg.On the mcpp path
dist-applestops at the.app.Proposed.
mcpp pack --format dmgon the macOS row: the.appthe member already produces, plus the symlink, throughhdiutilfrom the base macOS install (asdittoandcodesignare today). Options for the volume name, and later a background image.CI check (
macos-15).hdiutil verify <dmg>passes.hdiutil attach -nobrowse -readonly -mountpoint "$RUNNER_TEMP/m" <dmg>,<Name>.app/Contents/MacOS/<exe>exists andApplicationsis a symlink to/Applications.hdiutil detachsucceeds.B3.
dist-apple: a default macOS--format apprunner that returns the program's status — P2Measured.
NSBundle, somcpp run --format appmust run the bundle; HuxerUI's manifests declare[target.aarch64-macos] runner = ["open", "-W"]..apppath to that argv: a stubopenplaced first onPATHreceived-Wand/Users/runner/…/huxerui_example_import.app(run 34775426211, step "Hand the bundle to the manifest's runner").openreturns its own status, not the program's, and the program's stdout and stderr do not reach the terminal.mcpp run --format appcannot fail when the application does.Proposed.
dist-applesupplies the default runner for--format appon*-macosthroughmcpp::runner, which reaches the consumer (docs/41). It names a program (C1) that runs the bundle'sCFBundleExecutablein the foreground, with stdio attached, and returns its status. Applications then drop the table; an override stays possible.CI check (
macos-15). A fixture.appwhose program prints1-2-3and exits 7, with no runner in its manifest:mcpp run --format appprints1-2-3and exits 7.B4.
dist-wix: a Burn bundle (Setup.exe) — P3What the CMake path does.
huxerui_add_windows_installer()(cmake/HuxerUIWindowsInstaller.cmake#L116):platform/windows/windows_installer.cpp, a WiX Burn bootstrapper application whose interface is written with HuxerUI;wix buildon a bundle that chains the MSI, producing<target>-Setup-<version>.exe.wix.exeneeds .NET; CMake probes forMicrosoft.NETCore.App6.0 or newer.Measured on the mcpp side.
dist-wixproduces the MSI.xim:wix5.0.2 already carriesx64/burn.exebesidewix.exe(dist/wix.cppm,wix_payload_exe).Proposed.
mcpp pack --format setup(orbundle) builds a Burn bundle that chains the MSIdist-wixbuilds:WixToolset.BootstrapperApplications.wixext);options.bootstrapper = "<target>", reached as${mcpp.target_file:<target>}).CI check (
windows-2022).Setup.exe /quiet /norestart /log setup.logexits 0, and the installed executable is present.Setup.exe /uninstall /quietexits 0, and the executable is removed.B5.
dist-apk: an Android App Bundle — P3Measured.
dist-apkbuilds and signs an APK: the published debug key by default, or a release keystore given as a package with an alias and a password environment variable. Google Play takes an Android App Bundle for new applications. An application's Gradle shell produces one withbundleRelease;mcpp packhas noaab.Proposed.
mcpp pack --format aab:aapt2 link --proto-formatbuilds the resources;bundletool build-bundlepacks the module zip (manifest/,dex/,lib/<abi>/,assets/,res/,resources.pb);jarsignersigns it with the keystore options the APK already takes;--targetvalues make one bundle, as they make one universal APK today.This needs a
bundletoolpayload; it is a jar, run by thexim:jdk-*payload the member already uses.CI check (
ubuntu-24.04).bundletool validate --bundle app.aabpasses.bundletool build-apks --mode=universalproducesuniversal.apk, which containslib/x86_64/lib<app>.so.bundletool dump manifestshows the application id.B6.
rules-metal,tools-esbuild(carried from #622 B4) — P3Measured. Lib-Live2D's
mcpp/cubismpackage writes its Metal shader edges by hand:.metal→.air→.metallibthroughxcrun, 477 outputs checked in its CI. Its build program also bundles the TypeScript Web bridge with esbuild from a payload. Both are the same kind of rule asrules-spirv.Proposed.
rules-metal: actions with depfiles, choosing the macOS, iOS or iOS-simulator SDK from the target.tools-esbuild: one action with glob inputs and a declared output.CI check.
macos-15: two.metalfiles produce one.metallibeach foraarch64-macosand foraarch64-ios-sim; touching one.metalrebuilds only its output.ubuntu-24.04: a TypeScript entry produces a bundled.jsthat exports the entry's symbol.C.
xim-pkgindex(payloads and runner programs)C1. A macOS app-bundle runner program — P2
For B3:
macapp-run <App.app> [args…].CFBundleExecutablefromContents/Info.plist.<App.app>/Contents/MacOS/<exe>in the foreground with stdio attached;NSBundlestill resolves the bundle.It lives in
apple-simulator-toolsor in a newapple-app-tools.CI check (
macos-15). The B3 check, plus a missing or non-bundle operand producing a named error and exit code 2.C2.
simctl-run: output lost under--console-pty; exit status unmeasured — P2Measured.
simctl-runprinted only the launch lineios-app-consumer: 29750, not the program's1-2-3. Attempt 3 passed with no change.pkgs/a/apple-simulator-tools.lua, 0.2.0) records that whethersimctl launch --console-ptyreturns the launched application's status, or only simctl's, is unmeasured.Proposed.
--console(pipes) and--stdout=<file> --stderr=<file>followed by waiting for the process to exit, against--console-pty.CI check (
macos-15).1-2-3.mcpp run --target aarch64-ios-sim --format appexit 7.C3. WiX Burn native SDK payloads — P3
For B4 with a custom bootstrapper, two packages as HuxerUI's CMake path restores them (
cmake/HuxerUIWindowsInstaller.cmake#L40-L112):WixToolset.BootstrapperApplicationApi:BootstrapperApplication.h,build/native/v14/x64/balutil.lib,runtimes/win-x64/native/mbanative.dll;WixToolset.DUtil:dutil.h,build/native/v14/x64/dutil.lib.Also state whether
xim:wixcarries the .NET runtimewix.exeneeds, or which payload does.Open question for the check. Can mcpp's Windows toolchain (clang, lld-link) link the MSVC-built
v14static libraries? If not,windows = "msvc@system"is the path (#604 is closed).CI check (
windows-2022). A minimal bootstrapper program that calls onebalutiland onedutilfunction compiles and links under mcpp with the payloads, and B4's bundle builds with it.C4. Resolving an unmerged recipe from a consumer's CI — P2
Measured.
android-platform36-r2,glew,esbuild). They bridged them by copying the.luainto~/.mcpp/registry/data/xim-pkgindex/pkgs/<x>/and adding an entry to.xlings-index-cache.json, whichmcpp index updatewipes.--add-xpkgdid not help.xlings config --add-xpkgregistered the recipe underlocal:, which does not satisfy anxim:address (measured on Sept 12). xlings 2026.9.12.1 also hasconfig --index-repo <NS:URL>, andMCPP_HOMEkeepsxim-index-repos/andxim-pkgindex-local/; whether any of them can serve anxim:address from a PR branch was not evaluated.Proposed. One documented and CI-tested way for an mcpp build to resolve
xim:recipes from an unmerged xim-pkgindex branch:MCPP_HOME, or xlings'--index-repohonoured by mcpp;mcpp index update;mcpp why depsreports it as coming from the overlay.CI check (xim-pkgindex PR workflow,
ubuntu-24.04).mcpp index updatefollowed bymcpp buildstill resolves it.D.
mcpp-indexD1. Six-row
huxerui.huxeruiandhuxerui.live2d, so libraries declare ranges — P3, after the upstream releaseState.
huxerui.huxerui0.3.0 is in mcpp-index for Linux, Windows and macOS (Android, iOS and Web for a UI framework on mcpp: the engine, plugin and payload infrastructure it needs (tracking) #622).live2dtemplate pin HuxerUI by git revision, so every framework change means re-pinning a chain of commits.versionconstraints for one identity.Proposed. Once HuxerUI releases its six-row manifest:
huxerui.huxeruiwith sixplatforms, and publishhuxerui.live2d;huxerui.huxerui = "^0.3", and templates declare published versions.CI check (mcpp-index PR workflow).
mcpp xpkg parsepasses on both descriptors.huxerui.live2d's^0.3requirement and an application pin of0.3.x. It builds for the host, forx86_64-linux-androidand forwasm32-emscripten, andmcpp.lockrecords exactly onehuxerui.huxerui.E. Not asked of mcpp (HuxerUI's side, recorded for the boundary)
HuxerUIView's static initialiser loadslibhuxerui.sounconditionally. Measured: with the framework static, all 54Java_org_huxerui_*JNI functions are defined and exported by the application's.so. Beyond A1, nothing is needed from the engine.huxerui.rulescan supply the Android and iOS-simulator runners throughmcpp::runner(docs/41), which removes the[target.*] runnertables from application manifests. macOS waits for B3 and C1.tests/runtimemoves intomcpp/huxerui-tests; measured to pass, and cleaner with A5.tests/runtime/ui_testing.cppneeds a resource-compiler fixture.tests/runtime/libraries.cppneedstests/libraries/root_serviceas an mcpp package.importStdMinLevel = 23instead of probing the MSVC STL #603, which is closed, and can be removed.F. CI checks at a glance
NEEDED lib<dep>.soon Android for spellings 1 and 2; named refusal forrequires_linkagelib<dep>.so;.apphasFrameworks/lib<dep>.dylib, rpath, valid signature, runslinkageSONAME/@rpathinstall name[[test]]programs--labelselects; labels in JSONxim:gtk4declaredpkg-config gtk4succeeds; lock lists the closurerequires_platformtestandpackhonour--toolchainhdiutil verify; mounted layout1-2-3mcpp run --format appexits 7 and printsbundletool validate; universal APK contents.metalsources; TypeScript entryindex updatehuxerui.live2don^0.3huxerui.huxeruiinmcpp.lockon three rowsG. Suggested order
mcpp runreports the program's own status on macOS and on the iOS simulator, which makes CI results trustworthy.H. Evidence
dist-apk0.9.3NEEDEDwalk and version tokens), mcpp:plugins 0.9.3: dist-apk carries the graph's shared libraries, version tokens mcpplibs/mcpp-index#412._huxerui_select_framework_target;HuxerUIViewloader.importStdMinLevel = 23instead of probing the MSVC STL #603: closed.