You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Build programs: query the resolved dependency graph and package metadata; compile Swift sources; Android on a macOS host; feature and tool contract mismatches #647
mcpp:plugins 0.11.0 (mcpp-community/mcpp-plugins#26) gives dist-apk and dist-apple what an application module states in Gradle or Xcode:
Android libraries from source, local AARs and JARs, and Maven coordinates through a lock file;
a subset merge of the libraries' manifests;
a project's own Info.plist entries.
Every one of these is an option of the application's build program. The application names each library's directories itself.
That is enough for an application whose libraries it knows by path. It is not enough for a framework whose libraries are packages in the graph, which is the case CMake's framework functions serve by walking their targets. Two engine questions remain. Measured with mcpp 2026.9.14.3 and mcpp:plugins 0.11.0.
#
What
Needed by
E1
A build program enumerates the resolved graph and reads what each package states about itself
a framework merging its libraries' resources; dist-apk / dist-apple collecting library contributions
E2
.swift sources
Apple platform code in a library
E3
An Android row links on a macOS host (a defect, measured while implementing 0.11.0)
every Android build on a Mac
E4
Feature forwarding to build dependencies, [feature-deps] tools and dep_bin spellings match their docs (defects, measured while verifying a framework's prerequisites)
a framework that opens a plugin feature from its application, and a tool built only for packaging
E1. The resolved graph and package metadata, from a build program
This restates #634 A8 with the two needs that now depend on it.
Measured.
mcpp::dep_dir / MCPP_DEP_<NAME>_DIR answer by name, for a dependency the package declares itself. They give the source directory and nothing a dependency states about itself.
A build program cannot enumerate the graph: a transitive dependency has no name it can ask for.
Need 1: one resource package from every library. A UI framework's application compiles the resources of all its libraries into one package at build time. That includes transitive libraries, and the override order is framework, then libraries, then application. The runtime reads that one package.
mcpp::deploy does not answer this. It stages files, while the merge is a build action of the application, whose inputs have to be known while its build program runs.
Reading one package per library at run time would give the mcpp build a different program model from the framework's CMake build, where the merge happens at build time.
Need 2: platform contributions from libraries. When an application is packed, a library in the graph contributes what dist-apk 0.11.0 takes as options::libraries: Java/Kotlin roots, res/, a manifest, JARs/AARs. For dist-apple it contributes an Info.plist fragment. Today the application lists each of these by path, so a library's contribution changes every application that uses it.
Proposed (for discussion).
Enumerate the graph.mcpp::dependencies() returns the resolved graph in dependency order, dependencies before dependents. Each node has identity (namespace, name), version, kind, resolved linkage, manifest directory, and the packages that requested it.
Read package metadata. A [package.metadata.<tool>] table is readable from any consumer's build program, for example mcpp::dep_metadata(identity, "<tool>.<key>"). Paths in it resolve against that package's manifest directory. Editing it re-runs those build programs.
Open question: whether a dependency's own build program may also emit key/value metadata for its consumers (a generated resource directory, for instance), or whether manifest metadata is enough to start.
With that, an application's build program fills dist-apk's options::libraries from the graph, and a framework's resource rule merges in dependency order. Neither needs a project name in the engine or the plugins.
CI check (ubuntu-24.04). Fixture: application → lib A → lib B, where B declares [package.metadata.demo] resources = "res".
The application's build program prints the graph in dependency order and B's value, resolved to an absolute path; the check asserts both.
Editing B's metadata re-runs the application's build program; editing an unrelated file of B does not.
E2. Swift sources
Measured. A target's inferred sources are src/**/*.{cppm,cpp,cc,c,S,s,asm}, so a .swift file is not compiled, and no member of mcpp:plugins compiles one.
Need. On Apple platforms a library's platform code is often Swift: a view, a system API wrapper, a Swift package. Swift is also what Apple's newer APIs are documented in. Through Xcode or CMake, such a library is a Swift target linked into the application.
Proposed (for discussion).
Compile .swift sources with swiftc from the host's Xcode toolchain, located rather than installed (as rules-metal locates metal), for the macOS, iOS and iOS Simulator rows.
Interoperate with C and Objective-C through a bridging header or a module map, and emit the generated Objective-C header for C++/Objective-C callers.
Link the Swift runtime: the rpath and the swiftCompatibility* libraries the deployment target needs.
SwiftPM package dependencies can come later.
Where it belongs is part of the question. A rules-swift member can compile one package's files. However, a Swift import of another package's module needs that package's .swiftmodule directory, which is graph information (E1), and the runtime link flags are a property of the target the engine links.
CI check (macos-15). Fixture: a C++ program calling a Swift function exported with @_cdecl, and a Swift function calling back into C.
mcpp build && mcpp run prints a marker produced on both sides.
The same fixture builds for aarch64-ios-sim and runs under simctl-run.
E3. An Android row does not link on a macOS host
Measured on macos-15 (arm64) with mcpp 2026.9.14.2 and xim:android-ndk 30.0.16248370, in mcpp-plugins' CI (run 34968851641, tests/apk-consumer, a kind = "app" target):
The same fixture links, packs and verifies on ubuntu-24.04.
Cause, read from src/build/flags.cppm at 2fc7b5b, which is after 2026.9.15.2:
The link line is composed by the branch for the host. On macOS that is needs_explicit_libcxx, and its f.ld carries the target triple only as apple_cross_ld, which is set for an Apple row (appleSdkRoot).
It does not include link_toolchain_flags, where the own-sysroot branch put crossTarget for this row. The Linux branch does include it.
So the NDK's clang links an Android object as the host, and -fuse-ld=lld selects ld64.lld. This is the defect the comment on crossTarget in the same function records for a Mach-O cross from Linux.
The line also carries the macOS -isysroot and -mmacosx-version-min, which do not belong to an Android link.
Why.xim:android-ndk publishes a macOS archive, and a Mac is an ordinary Android development host; today the Android row builds on Linux only.
CI check (macos-15). An Android kind = "app" fixture: mcpp build --target x86_64-linux-android links an ELF shared object (file reports ELF), and in mcpp-plugins tests/apk-consumer packs an APK that apksigner verify accepts. mcpp-plugins 0.11.0 took its macOS dist-apk steps out for this reason and brings them back once a release links the row.
E4. Three contract mismatches around features and tools
Measured with mcpp 2026.9.15.2 on Linux x86_64, with throwaway fixtures. Each shape comes from a framework's application template.
1. A feature forwarded along a [build-dependencies] edge is applied, and is reported as undeclared.
Fixture. An application depends on spike.fw = { path = "../fw", features = ["kotlin"] }.
rules declares [build-dependencies.mcpp] plugins = { ..., features = ["dist-apk"], host-module = true } and [features] kotlin = ["mcpp.plugins/dist-apk-kotlin"].
Result. The forward works: mcpp build --target x86_64-linux-android provisions xim:kotlin@2.4.20 with the feature requested and not without it. Each forward also prints:
warning: feature 'kotlin' of 'fw' forwards to dependency 'spike.rules' (as 'spike.rules/kotlin') which is not declared in [dependencies] or [feature-deps]
warning: feature 'kotlin' of 'rules' forwards to dependency 'mcpp.plugins' (as 'mcpp.plugins/dist-apk-kotlin') which is not declared in [dependencies] or [feature-deps]
and --strict fails the build with exit 2.
Cause, read from src/build/prepare.cppm.validateForwards looks the key up in dependencies and devDependencies only, while injectForwards applies the forward to the build-dependency edge.
2. [feature-deps.<f>] cannot add tools to a dependency declared elsewhere.
What the docs say. docs/05 states that "[feature-deps.<name>] may add tools to a dependency already declared unconditionally".
What the parser does. It refuses such an entry:
error: [feature-deps.installer].spike.installer.tools must be a string, inline dep table, or nested table. 'tools' is an option of a dependency, not a package: a dependency table names its source (path, version, git or workspace) beside its options.
The fix. Either the docs or the parser should change.
3. The fully-qualified dep_bin variable is not published for a namespaced path dependency.
What the docs say. docs/30 states that "the fully-qualified MCPP_DEP_<NS>_<NAME>_BIN_<TOOL> is always published".
What the build program sees. For spike.installer = { path = "../installer", tools = ["spike-installer"] } it received MCPP_DEP_INSTALLER_BIN_SPIKE_INSTALLER and no MCPP_DEP_SPIKE_INSTALLER_BIN_SPIKE_INSTALLER. So mcpp::dep_bin("spike.installer", "spike-installer") returns "", while dep_bin("installer", ...) answers.
CI check (ubuntu-24.04).
The two-level forward above builds under --strict with no warning.
The docs and the parser agree on a source-less [feature-deps] tools entry.
A build program sees both spellings of a namespaced path dependency's tool.
Not asked here
Anything project-specific: the needs above come from a framework, and the shapes name none.
Store signing, and a device in CI: dist-apple 0.11.0 signs a device bundle with a provisioning profile and runs it through devicectl-run. That is a plugin and payload concern, and whether devicectl returns the program's exit status is still to be measured on a device.
中文摘要
mcpp:plugins0.11.0 让应用的构建程序可以显式声明 Android 库贡献(源码库、AAR/JAR、Maven 坐标、manifest 合并)和 Apple 的 Info.plist 片段。还缺两项引擎能力,插件侧做不了:E1(重提 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 A8,附具体需求):构建程序查询解析后的依赖图与包元数据。
E2:编译 Swift 源码。 Apple 平台库的平台代码常用 Swift 编写,需要与 C / Objective-C 互操作并链接 Swift 运行时。
E3(实现 0.11.0 时实测到的缺陷):macOS 主机上 Android 行链接失败。
mcpp build --target x86_64-linux-android在macos-15上报ld64.lld: error: unknown argument '-soname'。原因是 macOS 主机的链接行只对 Apple 行写目标三元组,-fuse-ld=lld于是选了 Mach-O 版的 lld。E4(核实前置条件时实测):feature 与工具的三处契约不一致。
[build-dependencies]的 feature 生效了,却报"未声明",--strict下失败;[feature-deps]不能像文档说的那样给已声明的依赖只加tools;dep_bin的全限定变量。E1 / E2 只登记需求,不要求马上实现,形状供讨论;E3、E4 是缺陷。每项附一个 GitHub 托管 runner 上可跑的 CI 检查。
Context
mcpp:plugins0.11.0 (mcpp-community/mcpp-plugins#26) givesdist-apkanddist-applewhat an application module states in Gradle or Xcode:Every one of these is an option of the application's build program. The application names each library's directories itself.
That is enough for an application whose libraries it knows by path. It is not enough for a framework whose libraries are packages in the graph, which is the case CMake's framework functions serve by walking their targets. Two engine questions remain. Measured with mcpp 2026.9.14.3 and
mcpp:plugins0.11.0.dist-apk/dist-applecollecting library contributions.swiftsources[feature-deps]tools anddep_binspellings match their docs (defects, measured while verifying a framework's prerequisites)E1. The resolved graph and package metadata, from a build program
This restates #634 A8 with the two needs that now depend on it.
Measured.
mcpp::dep_dir/MCPP_DEP_<NAME>_DIRanswer by name, for a dependency the package declares itself. They give the source directory and nothing a dependency states about itself.Need 1: one resource package from every library. A UI framework's application compiles the resources of all its libraries into one package at build time. That includes transitive libraries, and the override order is framework, then libraries, then application. The runtime reads that one package.
mcpp::deploydoes not answer this. It stages files, while the merge is a build action of the application, whose inputs have to be known while its build program runs.Need 2: platform contributions from libraries. When an application is packed, a library in the graph contributes what
dist-apk0.11.0 takes asoptions::libraries: Java/Kotlin roots,res/, a manifest, JARs/AARs. Fordist-appleit contributes an Info.plist fragment. Today the application lists each of these by path, so a library's contribution changes every application that uses it.Proposed (for discussion).
mcpp::dependencies()returns the resolved graph in dependency order, dependencies before dependents. Each node has identity (namespace, name), version, kind, resolved linkage, manifest directory, and the packages that requested it.[package.metadata.<tool>]table is readable from any consumer's build program, for examplemcpp::dep_metadata(identity, "<tool>.<key>"). Paths in it resolve against that package's manifest directory. Editing it re-runs those build programs.With that, an application's build program fills
dist-apk'soptions::librariesfrom the graph, and a framework's resource rule merges in dependency order. Neither needs a project name in the engine or the plugins.CI check (
ubuntu-24.04). Fixture: application → lib A → lib B, where B declares[package.metadata.demo] resources = "res".E2. Swift sources
Measured. A target's inferred sources are
src/**/*.{cppm,cpp,cc,c,S,s,asm}, so a.swiftfile is not compiled, and no member ofmcpp:pluginscompiles one.Need. On Apple platforms a library's platform code is often Swift: a view, a system API wrapper, a Swift package. Swift is also what Apple's newer APIs are documented in. Through Xcode or CMake, such a library is a Swift target linked into the application.
Proposed (for discussion).
.swiftsources withswiftcfrom the host's Xcode toolchain, located rather than installed (asrules-metallocatesmetal), for the macOS, iOS and iOS Simulator rows.swiftCompatibility*libraries the deployment target needs.Where it belongs is part of the question. A
rules-swiftmember can compile one package's files. However, a Swiftimportof another package's module needs that package's.swiftmoduledirectory, which is graph information (E1), and the runtime link flags are a property of the target the engine links.CI check (
macos-15). Fixture: a C++ program calling a Swift function exported with@_cdecl, and a Swift function calling back into C.mcpp build && mcpp runprints a marker produced on both sides.aarch64-ios-simand runs undersimctl-run.E3. An Android row does not link on a macOS host
Measured on
macos-15(arm64) with mcpp 2026.9.14.2 andxim:android-ndk30.0.16248370, in mcpp-plugins' CI (run 34968851641,tests/apk-consumer, akind = "app"target):The same fixture links, packs and verifies on
ubuntu-24.04.Cause, read from
src/build/flags.cppmat 2fc7b5b, which is after 2026.9.15.2:needs_explicit_libcxx, and itsf.ldcarries the target triple only asapple_cross_ld, which is set for an Apple row (appleSdkRoot).link_toolchain_flags, where the own-sysroot branch putcrossTargetfor this row. The Linux branch does include it.-fuse-ld=lldselectsld64.lld. This is the defect the comment oncrossTargetin the same function records for a Mach-O cross from Linux.-isysrootand-mmacosx-version-min, which do not belong to an Android link.Why.
xim:android-ndkpublishes a macOS archive, and a Mac is an ordinary Android development host; today the Android row builds on Linux only.CI check (
macos-15). An Androidkind = "app"fixture:mcpp build --target x86_64-linux-androidlinks an ELF shared object (filereports ELF), and in mcpp-pluginstests/apk-consumerpacks an APK thatapksigner verifyaccepts. mcpp-plugins 0.11.0 took its macOS dist-apk steps out for this reason and brings them back once a release links the row.E4. Three contract mismatches around features and tools
Measured with mcpp 2026.9.15.2 on Linux x86_64, with throwaway fixtures. Each shape comes from a framework's application template.
1. A feature forwarded along a
[build-dependencies]edge is applied, and is reported as undeclared.Fixture. An application depends on
spike.fw = { path = "../fw", features = ["kotlin"] }.fwdeclares[build-dependencies] spike.rules = { path = "../rules", host-module = true, reexport = true }and[features] kotlin = ["spike.rules/kotlin"].rulesdeclares[build-dependencies.mcpp] plugins = { ..., features = ["dist-apk"], host-module = true }and[features] kotlin = ["mcpp.plugins/dist-apk-kotlin"].Result. The forward works:
mcpp build --target x86_64-linux-androidprovisionsxim:kotlin@2.4.20with the feature requested and not without it. Each forward also prints:and
--strictfails the build with exit 2.Cause, read from
src/build/prepare.cppm.validateForwardslooks the key up independenciesanddevDependenciesonly, whileinjectForwardsapplies the forward to the build-dependency edge.2.
[feature-deps.<f>]cannot addtoolsto a dependency declared elsewhere.What the docs say. docs/05 states that "
[feature-deps.<name>]may addtoolsto a dependency already declared unconditionally".What the parser does. It refuses such an entry:
What works. Restating the source, for example
[target.'cfg(os = "windows")'.feature-deps.installer] spike.installer = { path = "../installer", tools = ["spike-installer"] }, builds the tool only under--features installer, includingmcpp pack --format tar --features installer(A required llvm resolves to the NDK's version on a fresh home; llvm.libcxx cannot be used by a c++20 graph; a tool's dependency-rooted source passes MAX_PATH on Windows #641 item 4), and only on matching rows.The fix. Either the docs or the parser should change.
3. The fully-qualified
dep_binvariable is not published for a namespaced path dependency.MCPP_DEP_<NS>_<NAME>_BIN_<TOOL>is always published".spike.installer = { path = "../installer", tools = ["spike-installer"] }it receivedMCPP_DEP_INSTALLER_BIN_SPIKE_INSTALLERand noMCPP_DEP_SPIKE_INSTALLER_BIN_SPIKE_INSTALLER. Somcpp::dep_bin("spike.installer", "spike-installer")returns"", whiledep_bin("installer", ...)answers.CI check (
ubuntu-24.04).--strictwith no warning.[feature-deps]tools entry.Not asked here
dist-apple0.11.0 signs a device bundle with a provisioning profile and runs it throughdevicectl-run. That is a plugin and payload concern, and whetherdevicectlreturns the program's exit status is still to be measured on a device.