Repository navigation
Description
TL;DR
We ported a real C program — ChenPi11/cmd (a cmd.exe re-implementation for Unix, ~18.7k lines of C89, already shipping an mcpp.toml) — to every platform its README lists, using only mcpp: ok images (x86_64/aarch64-openkal, one .okb), FreeBSD 14.5, and an i386 probe towards SVR4. Most of it worked, several parts beautifully (the platform package, patches, run_under, okb packing). Everything that did not work traces back to one root cause:
Facts about operating systems and architectures are hard-coded in the engine (the closed target vocabulary, per-row flags, host-centred runtime validation, carrier rows, per-OS descriptor blocks) instead of being data that packages (platforms, sysroots, runners) declare.
This issue lists 12 engine gaps (G1–G12) and 3 bugs (B1–B3) found along the way. Each one has evidence (permalinks at 34406d6), a reproduction, the scenario that needs it, and a proposal. The acceptance test is concrete: every workaround listed in §6 disappears.
中文摘要
我们用 mcpp 把一个真实的 C 项目 ChenPi11/cmd 移植到它 README 里列出的所有平台:ok image(x86_64 和 aarch64,合成一个 okb)、FreeBSD 14.5,以及一个面向 SVR4 的 i386 探针。大部分都跑通了,其中 platform 包、patches、run_under、okb 打包这几项体验很好。
所有卡住的地方都指向同一个根因:系统和架构的事实被写死在引擎里(封闭的 target 词汇表、各行强加的参数、以本机为中心的运行时校验、carrier 行、描述符的平台块),而不是由 platform、sysroot、runner 这些包作为数据来声明。
下面按 G1–G12(引擎能力缺口)和 B1–B3(顺带发现的 bug)逐条给出:证据(固定到 34406d6 的源码链接)、复现、需要它的场景,以及建议。验收标准是:§6 列出的每一个绕路做法都可以删除。
1. Context
| mcpp | 2026.11.7.2 (source read at 34406d6, which is on origin/main) |
| toolchain | llvm@23.1.3 (xim) |
| openkal family | openkal-platform 0.24.3, openkal-run/tools 0.5.3, openkal-musl 0.28.1, openkal 0.24.0 |
| host | Linux x86_64; FreeBSD 14.5-RELEASE (official VM image) under qemu/KVM for the run tests |
| project | ChenPi11/cmd @ 1edab21 — C89, POSIX layer l*.c, Win32 layer lw*.c, two programs (cmd.exe, COMMAND.COM) |
mcpp itself was not modified. Everything below was measured, not reasoned — except where marked (documented, not exercised).
2. What already works (please keep it)
| # | Result |
|---|---|
| E2 | cmd's C sources build unchanged for x86_64-openkal. Its batch-script regression output is identical to the native build (only MKLINK /H differs: ENOSYS, as openkal-musl documents) |
| E3 | openkal-posix (an openkal implementation over the host's POSIX libc, 942 lines) plus upstream openkal-run with one patch (§G7) builds a working okrun via mcpp. mcpp reports kernel-abi openkal (openkal-posix, graph). Output is byte-identical to the official okrun |
| E4 | A platform package (targets = ["x86_64-none-elf"] plus flags) makes unmodified mcpp emit a static FreeBSD 14.5 ELF — and the okrun above, built for FreeBSD, runs the same cmd.oke built on Linux inside a FreeBSD VM: pipes, FOR /F, batch files, interactive line editing |
| E6 | C++23 named modules plus import std in an ok image run identically under the official okrun and okrun-posix |
| E8 | aarch64-openkal runs through MCPP_TARGET_AARCH64_OPENKAL_RUN_UNDER=qemu-aarch64-static, which packs the linux-aarch64 carrier automatically |
| E9 | mcpp pack --target openkal --pkg-format okb produces one 2.9 MB okb (x86_64 + aarch64) that runs directly |
| — | Value provenance in reports (Resolved … ← source, (platform), (graph)), patch variants recorded in lock and SBOM, layer provider resolution — all excellent |
We also checked, and these are not gaps: the engine already passes --no-default-config on the compile and link lines (the xlings clang .cfg only bites when clang is called by hand), and multi-architecture okb packing exists.
3. Principle proposed
Mechanism in the engine, facts in packages. The engine owns the axes (arch, os, env, object format, C environment, execution host) and the protocols (merge order, provenance, validation, packing). It should not own the values — which OSes exist, which code model a row uses, which loader a program has, which systems a carrier serves.
mcpp already does exactly this for the two openkal rows, where a target is served by a versioned package (openkal-platform). This issue asks to generalise that model: any target may be defined by a package, and the built-in table becomes the index of default platform packages.
4. Engine gaps
G1 — The target vocabulary is closed: a platform cannot define a target
- Observed: a triple whose OS segment the parser does not know is unparseable —
x86_64-freebsd,i686-svr4,x86_64-haiku. Socfg(os = "freebsd")can never hold, and a platform can only serve existing rows. For FreeBSD we had to borrowx86_64-none-elfand restate the real target in compile flags. The vocabulary has no 32-bit x86 row at all. - Evidence:
triple.cppm#L12(os ∈ {linux, macos, windows});triple.cppm#L1515(an unrecognized segment yieldsnullopt). The[target.X]escape hatch only helps for parseable triples. - Scenario: BSDs, illumos, Haiku, Hurd and SVR4 are each served by one small platform package (sysroot, clang triple, link conventions). Today every one of them must masquerade as a bare-metal row, which drags in G2–G5.
- Proposal: a platform package may declare
[[platform.define]]rows:triple,arch,os,env,clang-triple,object-format,data-model,exe-suffix,loader/interp,code-model,link-emulation,descriptor-key. The parser accepts anyos/envtoken that some resolved platform defines.cfg(os = …)evaluates against the defined row.
G2 — Flags the engine injects per row cannot be replaced, and their provenance is invisible
- Observed: for an i386 target served through
x86_64-none-elf, the row's-mcmodel=smallis rejected by clang for every i386 spelling (i386-unknown-none-elf,i386-unknown-linux-gnu,i386-pc-solaris2.11,x86_64 … -m32):error: unsupported argument 'small' to option '-mcmodel='. Platform flags can only be appended (SPEC-013 §6), so the only way through was a toolchain wrapper (MCPP_TOOLCHAIN=path:…whoseclangdrops that one argument). With it, mcpp produced and ran anELF 32-bit LSB executable, Intel 80386, statically linked(E7). The same row also injects-m elf_x86_64 -Bstatic --no-dynamic-linker. - Evidence:
target.cppm#L241(row:x86-64 / sysv / small / elf_x86_64);target.cppm#L365;linkline.cppm#L124-L134. - Scenario: any target whose facts differ from the row it rides on: i386, x32, a kernel at
-mcmodel=kernel. - Proposal: (a) the engine's row defaults are data with a source, replaceable by a more specific declarer (platform, then project) with an explicit
replace, while conventions stay append-only; (b)mcpp explain flags <unit>prints every flag with the layer that contributed it. With G1 this mostly dissolves, because the row would be the platform's own.
G3 — Hostedness has two answers; the sysroot's declaration is ignored
- Observed: platform
sysroot = { path = "<FreeBSD base>", provides = ["mcpp:c-abi=freebsd-libc", "hosted-standard-library"] }is accepted and nicely reported (c-abi sysroot (path:…, prebuilt)), but-ffreestandingis still added (verified inbuild.ninja). A C++int mainis then mangled (undefined symbol: main … did you mean extern "C"), so we had to append-fhosted. - Evidence:
target.cppm#L413:if (!targetCxxRuntime) out.emplace_back("-ffreestanding");— only a graph C++ runtime counts. - Proposal: hostedness is one declared fact of the C environment. A sysroot,
c-abiprovider or platform that declareshosted-standard-library(orhosted = true) turns off-ffreestandingand the freestanding treatment ofmain.
G4 — link.driver = "compiler" is not honoured on rows that link with ld.lld directly
- Observed: with
[platform.link] driver = "compiler", the link line is stillld.lld -m elf_x86_64 -Bstatic --no-dynamic-linker --gc-sections …. So the platform must re-implement what clang's FreeBSD driver already knows:crt1.o/crti.o/crtbeginT.obefore the objects,crtend.o/crtn.oaslink.inputsafter them, the library group, and--sysrootagain for lld (FreeBSD'slibc.sois a linker script naming/lib/libc.so.7). - Evidence:
ninja_backend.cppm#L1595treatslink.driveronly as an opt-in to"lld"; the row'slldEmulationpath (linkline.cppm#L124) wins otherwise. - Proposal: the link model is platform data and is honoured on every row. With
driver = "compiler",--targetand a sysroot, clang's own driver for that OS supplies the start files and libraries. Don't re-encode, per package, knowledge the compiler driver already has.
G5 — Runtime closure validation assumes every ELF with PT_INTERP is a program for this host
- Observed: a correct dynamically linked FreeBSD executable (
interpreter /libexec/ld-elf.so.1, for FreeBSD 14.5) fails the build:runtime closure validation failed (proven Linux ELF defect)/rule B: … uses PT_INTERP /libexec/ld-elf.so.1 but RuntimeBinding glibc@2.44.3 selects …/ld-linux-x86-64.so.2.MCPP_ALLOW_HOST_LIBS=1does not help. We were forced to link statically. Haiku cannot link statically at all, so it is blocked outright. - Evidence:
runtime_validation.cppm#L651and#L683(the escape is the triple'shas_own_sysroot(), which a platform cannot set);ninja_backend.cppm#L5151. Same family as A stated sysroot with its own glibc: link fails, the artifact loads the host's loader, rule B compares the wrong glibc #833. - Proposal: validate against the artifact's declared execution environment (from the G1 row or the platform). For a foreign OS: validate the closure against that platform's sysroot and loader, or report "not for this host — closure not checked". Never call it a defect of this host.
G6 — Source-only packages are resolved through per-host-OS descriptor blocks
- Observed:
error: dependency 'openkal': index entry has no versions for platform 'none'. The tarball is identical in every block; we neededdescriptor_keys = ["linux"]on the platform. - Evidence:
specs/platforms.md#L71(closed key set). - Proposal: descriptors separate the source artifact (one, target-independent) from prebuilt payloads (per target, matched on the G1 axes with fallbacks and
any). A new platform then needs nodescriptor_keys, and the key set stops being a closed list.
G7 — Capability slots with default providers for ordinary packages
- Observed:
openkal-runhard-wires its implementation per OS ([target.linux.dependencies] openkal-linux = …, and the same for macOS and Windows). To use another implementation ofmcpp:kernel-abi=openkal, the consumer must patch the package (our 35-line patch removes those tables). For a FreeBSD build no table matches, so nothing is selected. - Scenario: this is exactly what mcpp already solves for platform layers ("a graph package that provides the layer takes the platform's place", SPEC-013 §7.2). Ordinary packages need the same thing.
- Proposal: a package declares
requires = ["mcpp:kernel-abi=openkal"]plus[defaults."mcpp:kernel-abi"] linux = "openkal-linux@^0.25" …. A provider already in the graph, chosen by the consumer or brought by the platform, takes precedence. No patch is needed, and new systems need no change in the consuming package.
G8 — Patch root: what a patch must name is the store layout, not the package
- Observed: for
openkal-run(descriptormcpp = "*/run/mcpp.toml"), a patch naminga/mcpp.tomlora/run/mcpp.tomlfails with'…' is not in the package. It works only asa/openkal-image-0.5.3/run/mcpp.toml: the root is the store's version directory, which contains the archive's top-level directory. A patch therefore embeds the version in its paths and cannot be reused across versions; the author has to inspect~/.mcpp/xpkgsto write it. - Evidence:
patch.cppm#L248,#L328; docs:05-dependencies.md#L651. - Proposal: the patch root is the package root (the directory holding the resolved manifest), or an explicit
patch_root. Also addmcpp patch edit <dep>/mcpp patch save <dep>(edit the variant tree, emit the diff) — the workflow is otherwise hand-made.
G9 — Per-artifact compile variants of shared sources
- Observed (in the project itself): cmd's
mcpp.tomlsays "mcpp compiles shared sources once, so the entry file is the only place a per-target define can take effect". Its authors added amcpp/cmain_command.cthat#includes../cmain.c, just to giveCOMMAND.COM-DENABLE_AUTOEXEC=1. Every other build system in that repo (CMake, Meson, xmake, Make) compiles the sources once per program. - Evidence: cmd
mcpp.toml#L7-L11. - Proposal:
[targets.X] defines(and flags) may apply to the shared sources of that artifact. The engine compiles a variant of each affected unit, keyed by its flags, only where the flags differ.
G10 — Carriers and packs exist only for the built-in rows (documented, not exercised)
- SPEC-013 §11 fixes the carrier's rows to
<arch>-linux-musl,<arch>-macosand<arch>-windows-gnu(specs/platforms.md#L349). The FreeBSD okrun we built cannot become a single-file carrier or a--systems freebsdpack. - Proposal: any row a resolved platform can build may host a carrier or pack, as that platform declares.
G11 — Execution hosts beyond user-mode emulation
- Observed:
run_underworks well for qemu-user (E8). Running on FreeBSD needed a hand-written driver: boot the qemu/KVM image, move the loader console to serial, log in, mount a virtual FAT disk, copy, run, collect output, power off. - Proposal: an execution-host protocol — transfer artifact(s) → run argv → stream output → exit status — implemented by distributable runner packages (system VM, ssh, device; docs/41 already has the device half). Then
mcpp test --target x86_64-freebsdworks in CI.
G12 — A missing 32-bit x86 row blocks a whole class of targets
Kept separate from G1 because it is valuable even without G1. An i686-none-elf row (empty code model, elf_i386) would let a platform reach i386 SVR4, i386 Hurd and other legacy x86 systems. It is the single blocker found for the openkal "linked arrangement" (implementation linked into a native static program) on SVR4.
5. Bugs found along the way (happy to split them into separate issues)
B1 — Generated manifests do not quote target names containing .. mcpp pack on a package with [targets."COMMAND.COM"] writes target/launcher-pack/COMMAND.COM/mcpp.toml containing:
[targets.COMMAND.COM]
kind = "bin"which reparses as table COMMAND → COM: error: targets.COMMAND.kind missing or not a string. The same format string appears at pack.cppm#L2561, manifest_emit.cppm#L197, distribution.cppm#L1076 and #L1423. Fix: emit TOML-quoted keys.
B2 — A project's artifact_name override defeats the platform's image naming. cmd's build.mcpp calls mcpp::artifact_name("exe", "{name}") to suppress the host suffix. For x86_64-openkal this also removes .oke, and the platform's own okb step fails: okld: …/lib/aarch64/COMMAND.COM: an image is named <name>.oke or <name>.okso. Expected: a platform-defined format suffix (link.exe) is not overridden by a host-suffix rule, or the conflict is reported at plan time.
B3 — mcpp pack silently packs one program of several. With package cmd and programs cmd and COMMAND, mcpp pack (no target named) packed only COMMAND. This held for both okb and tar, with no warning; mcpp pack cmd packs cmd. The selection is the package-name match, then "the first binary target" (pipeline.cppm#L532-L548), and neither branch is reported. That the package-name branch did not pick cmd here was not investigated further. Expected: refuse or warn when several programs qualify, and offer a way to ship all programs of a package in one distribution (cmd needs cmd.exe and COMMAND.COM together).
6. Acceptance criteria: the workarounds that should disappear
| Workaround used in this port | Removed by |
|---|---|
Borrowing x86_64-none-elf to build a FreeBSD program |
G1 |
Toolchain wrapper that strips -mcmodel=small for i386 |
G2 / G12 |
-fhosted appended to undo -ffreestanding |
G3 |
Hand-written crt1/crti/crtbeginT … crtend/crtn plus library group in ldflags and link.inputs |
G4 |
| Forced static linking (and Haiku not possible) | G5 |
descriptor_keys = ["linux"] |
G6 |
35-line patch of openkal-run removing its per-OS implementation tables |
G7 |
Patch paths naming openkal-image-0.5.3/run/… |
G8 |
mcpp/cmain_command.c wrapper in cmd |
G9 |
| Hand-written qemu serial driver for testing on FreeBSD | G11 |
Suggested priority: G1, G2/G12, G7 first (they unblock and remove most of the rest), then G3–G5 and G9, then G6, G8, G10 and G11. B1–B3 are small and independent.
7. Out of scope for mcpp (for routing; we will raise these upstream separately)
- openkal-image: libokld accepts only
EM_X86_64andEM_AARCH64ELF64 images. An i386 ok image needs ELF32/REL support and animage_abi.hbranch (spec clause 12 says "of the architecture's class", so the format is ready). - openkal-run: bind only the interfaces an implementation provides (today every surface name is bound, which forces stub functions in a partial implementation), plus G7 on its manifest.
- openkal: no asynchronous interrupt notification. Under okrun a shell cannot survive Ctrl+C while a child runs (measured with both runners).
Appendix — minimal reproductions
A. FreeBSD through a platform package (G1, G3, G4, G5, G6)
# platforms/freebsd/mcpp.toml (SR = an extracted FreeBSD 14.5 base.txz: usr/include, usr/lib, lib)
[package]
name = "freebsd-platform"
version = "0.0.1"
[platform]
targets = ["x86_64-none-elf"] # G1: no x86_64-freebsd row can be defined
toolchain = "llvm@23.1.3"
descriptor_keys = ["linux"] # G6
[platform.layers]
"c-abi" = false
"c++-abi" = false
"compiler-runtime" = false
"kernel-abi" = false
[platform.build]
cflags = ["-fhosted", "--target=x86_64-unknown-freebsd14.5", "--sysroot=SR"] # -fhosted: G3
cxxflags = ["-fhosted", "--target=x86_64-unknown-freebsd14.5", "--sysroot=SR",
"-nostdinc++", "-isystem", "SR/usr/include/c++/v1"]
# G4: driver = "compiler" is ignored, so ld.lld's own terms; static because of G5
ldflags = ["--sysroot=SR", "--eh-frame-hdr", "-LSR/usr/lib", "-LSR/lib",
"SR/usr/lib/crt1.o", "SR/usr/lib/crti.o", "SR/usr/lib/crtbeginT.o",
"--start-group", "-lc++", "-lcxxrt", "-lthr", "-lc", "-lm", "-lgcc", "-lgcc_eh", "--end-group"]
[platform.link]
inputs = [{ file = "crt/crtend.o" }, { file = "crt/crtn.o" }]mcpp build --target x86_64-none-elf --platform ./platforms/freebsd
# → ELF 64-bit LSB executable, x86-64, statically linked, for FreeBSD 14.5 (NT_FREEBSD_ABI_TAG 1405000)
# A dynamic link instead (ldflags "-Bdynamic", "--dynamic-linker=/libexec/ld-elf.so.1", crtbegin.o, -lc …)
# links correctly (interpreter /libexec/ld-elf.so.1) and is then refused:
# → error: runtime closure validation failed (proven Linux ELF defect) (G5)B. i386 (G2, G12)
[platform]
targets = ["x86_64-none-elf"]
toolchain = "llvm@23.1.3"
[platform.build]
cflags = ["-fhosted", "--target=i386-unknown-none-elf", "-march=i386", "-fno-pic"]
ldflags = ["-m", "elf_i386", "-e", "_start"]clang: error: unsupported argument 'small' to option '-mcmodel=' for target 'i386-unknown-none-elf'
With MCPP_TOOLCHAIN=path:<llvm 23 with a clang wrapper dropping -mcmodel=small for i386>:
ELF 32-bit LSB executable, Intel 80386, statically linked — the program runs (int $0x80 probe).
C. The openkal-run patch needed today (G7, G8)
--- a/openkal-image-0.5.3/run/mcpp.toml
+++ b/openkal-image-0.5.3/run/mcpp.toml
@@ -14,30 +14,11 @@
openkal = "^0.24.0"
openkal-ld = { path = "../ld" }
-[target.linux.dependencies]
-openkal-linux = "^0.25.0"
+# The implementation is the consumer's choice (patched for openkal-posix).
-[target.macos.dependencies]
-openkal-macos = "^0.22.1"
-
-[target.windows.dependencies]
-openkal-windows = "^0.20.0"
-
-# The interfaces each system's implementation provides decide one unit: where
-# openkal.space is provided its names are bound, and elsewhere an image that
-# requires it is refused at load, naming it.
[build]
-sources = ["src/okrun.cppm", "src/run.cpp", "src/bindings.cpp", "src/seal.cpp", "src/prof.cpp"]
+sources = ["src/okrun.cppm", "src/run.cpp", "src/bindings.cpp", "src/seal.cpp", "src/prof.cpp", "src/bindings_space.cpp"]
include_dirs = ["include"]
flags = [
{ glob = "src/*.cpp", cxxflags = ["-fno-exceptions", "-fno-rtti"] },
]
-
-[target.linux.build]
-sources = ["src/bindings_space.cpp"]
-
-[target.macos.build]
-sources = ["src/bindings_space.cpp"]
-
-[target.windows.build]
-sources = ["src/no_space.cpp"][dependencies]
openkal-run = { version = "0.5.3", patches = ["patches/0001-openkal-run-implementation-chosen-by-consumer.patch"] }
openkal-posix = { path = "../openkal-posix" } # provides = ["mcpp:kernel-abi=openkal"]
Activity
2026.11.9.1: targets and providers; a platform defines rows; mcpp#911…
Sunrisepeak commentedon Nov 9, 2026
Re-measured on 2026.11.9.1: most of G1–G12 / B1–B3 hold; 8 follow-up observations (N1–N8)
Thank you for #912. That was a very fast turnaround. I re-ran the whole port with the released 2026.11.9.1 (linux-x86_64 tarball, llvm 23.1.3). For openkal-run 0.5.4 I used
git = "https://github.com/mcpplibs/openkal-image.git", tag = "0.5.4", since mcpplibs/mcpp-index#743 is not merged yet. Below is what I measured, followed by a few follow-up observations. The observations come with workarounds that already work today, and a possible direction for each. They are offered for consideration only. How or whether to change anything is of course your call.Permalinks are pinned to the
v2026.11.9.1tag commit9edccc4.中文摘要
用 2026.11.9.1 发布版把移植实验完整重跑了一遍。G1–G4、G6、G7、G11、G12 和 B1–B3 实测都已生效。cmd 现在能不改源码直接编译成 FreeBSD 原生程序。借助一个运行器脚本,
mcpp run --target x86_64-freebsd能在真实的 FreeBSD 14.5 虚拟机里执行并返回退出码。另外观察到 8 个后续问题(N1–N8),每个都附上了现在就能用的绕过方式和一个可供参考的思路,不要求上游按此修改:
has_own_sysroot()、freestanding::resolve()等判断仍只查内置表。另外更正 #911 第 7 节的一处说法:openkal 其实有
kal_process_stop_requested(),cmd 调用一次即可挺过 Ctrl+C。那一条不是缺口。1. What now works (measured)
[[platform.define]] x86_64-freebsd+[[platform.provider]]. The build goes totarget/x86_64-freebsd/,cfg(os = "freebsd")applies, andmcpp why platformlists the row.mcpp why flagswas the tool that located N3 below. Very useful.hosted = trueprovider servingx86_64-none-elf:-ffreestandingis gone.driver = "compiler"on the FreeBSD row: crt objects, libraries andPT_INTERPcome from the driver. The hand-written crt list is gone.descriptor_keys. Index dependencies (openkal, openkal-run) resolve forx86_64-freebsd.openkal-posixasmcpp:kernel-abi. The lock has no openkal-linux, and the runner's output is byte-identical to the official okrun.a/mcpp.tomlapplies to openkal-run 0.5.3 from the index.[targets."COMMAND.COM".build] definescompiles the 73 shared units underobj/_variant/COMMAND_COM/.mcpp pack --target x86_64-freebsd --mode staticpacks.runner = ["<vm-runner>", "{}"]boots a FreeBSD 14.5 VM, runs the program and returns its status.mcpp run cmd.exe --target x86_64-freebsd -- /d /c "ver & exit /b 3"ends with 3.--target i686-none-elfbuilds an ELF32 i386 program that runs, with no toolchain wrapper.why flagsshowsrow -march=i686androw -m elf_i386.[targets."COMMAND.COM"]in the generated launcher manifestcmd.exe.oke, with the conflict reported--all-programsgives an okb with 2 architectures × 2 images.The following ran in a real FreeBSD 14.5 VM, all dynamically linked:
libc++.so.1,libcxxrt.so.1);x86_64-freebsd, which passes the same batch test as the Linux build;okrunover openkal-posix built for FreeBSD, running the 9.1-builtcmd.exe.okewith the same output (except hard links, which openkal does not have).Of the workarounds in the §6 acceptance table:
x86_64-none-elf, the i386 toolchain wrapper,-fhosted, the hand-written crt list, anddescriptor_keys. The VM driver is still there, but it is now plugged in as a runner, so testing goes throughmcpp run(G11).2. Follow-up observations
The table gives my own reading of each item. A ✓ in the last column means a workaround that does not change mcpp was verified here.
mainfile share one entry object compiled with one program's definesMCPP_ALLOW_HOST_LIBS=1-L/-rpathreach a defined row with a sysrootc++-abilayer package; static link avoids the RPATHpatcheson a git workspace member breaks the member'spathedges[platform.link] flagsis silently unused withdriver = "compiler"[platform.build] ldflagsmarch/link-emulationare read but not appliedbuild/link.flagspackruns hostlddon a foreign dynamic ELF;--mode staticlabels a dynamic program-staticA possible common cause of N2, N3 (link side) and N7
The defined row is registered at run time with all its fields (
prepare/platform.cpp#L185-L215), and selection, reports andcfgall use it. However, a few predicates further down still read only the compiled-in tables:Triple::has_own_sysroot()looks only atkKnownTargetsand at a few OS names. It decides the loader exemption inhermetic.cppm#L246-L253(N2), and the early return of the host C-runtime group inlinkmodel.cppm#L503-L504(probably the gcc-runtime-L/-rpathin N3).freestanding::kTable/resolve()is built frombuiltin::kRowsat compile time (N7).A direction you may wish to consider: let these predicates ask the resolved row and provider (a declared sysroot,
execution≠ this machine, the row's machine description) rather than the compiled-in tables. One change there might settle three observations at once. SPEC-016 already reads that way: §2.1 says the machine description is "引擎原样使用" (used by the engine as written), and §5 says "平台定义的行不取宿主载荷的 C 库" (a platform-defined row does not take the host payload's C library). I have not verified that the llvm payload's-L …/lib/x86_64-unknown-linux-gnuin N3 comes from the same place, so please treat that part as a guess.N1 — one entry object for two programs that share
mainbuild.ninjacontains oneobj/cmd/cmain.owith-DENABLE_AUTOEXEC=1, linked into bothbin/cmd.exeandbin/COMMAND.COM, and nothing is reported. The cause appears to beentry_object: it reuses the unit by source path, andentryFlagsAppliedapplies the first program's flags once. With the[targets.X.build]spelling, the 73 shared sources do get their variant, but the entry stays shared. That is why cmd still needs itsmcpp/cmain_command.cwrapper.Possible directions: when two programs give one entry different flags, compile one entry object per program (as G9 already does for shared sources), or refuse and name both programs. Either would avoid the silent mix. Separately,
docs/04-mcpp-toml.md#L544-L546still says a shared source is never compiled two different ways, which G9 now does.N2 — hermetic check vs a target-side loader
A dynamic program for the FreeBSD row (with
sysroot = { path = … }) stops at:/libexec/ld-elf.so.1is the target'sPT_INTERP, the same situation that the comment abovehermetic.cppm#L246describes for Android. Today every consumer of such a platform needsMCPP_ALLOW_HOST_LIBS=1or[build] allow_host_libs, which reads as "link host libraries" although nothing from the host is linked. Static linking avoids the check entirely (verified). Direction: see the common cause above.N3 — what a hosted defined row receives from the toolchain payload
With only
sysroot = { path = "sysroot", provides = ["mcpp:c-abi=freebsd-libc"] }(also tried"hosted-standard-library"and"mcpp:c++-abi=libc++"), the report showsc++-abi —, andmcpp why flagsshows:What works today (usage): a
c++-abi+compiler-runtimelayer package for the system's own libc++. With it,cxxFromGraphholds and the payload's headers leave the compile (hostflags.cppm#L411-L419). It needed two details:<SR>/usr/lib/libc++.so, orlibc++.a+libcxxrt.afor static), because a-lc++resolves to the payload's Linuxlibc++first: the host-Lprecede the package's flags.The three host paths remain in the FreeBSD program's
RPATH.Possible directions, smallest first:
N4 — interposition warning against host glibc
For the FreeBSD C++ program,
symbol_provision.cppm#L271reportsenvironand__prognameas "also provided by<glibc payload>/lib/libc.so.6". The program loadslibc.so.7from FreeBSD. Direction: skip the check where the row'sexecutionis not this machine, or resolveDT_NEEDEDinside the declared sysroot. It is only a warning, so it is low priority.N5 — patches on a git dependency that is a workspace member
For a member,
dep_rootisgitRoot / member, andgraph.cpp#L1677-L1692materializes only that directory. The member'sopenkal-ld = { path = "../ld" }then points outside the variant. The same patch on the index archive works, because the whole archive is copied.Direction: copy the clone root into the variant and keep the member's relative position. Alternatively, resolve the member's path edges against the original clone, since §"A package of a git repository" already says such an edge names the same git source at the same commit.
N6 —
[platform.link] flagswithdriver = "compiler"This is documented (
specs/platforms.md#L282: only fordriver = "lld"), and[platform.build] ldflags = ["-pthread"]is the right spelling. I only mention it because the value was dropped without a word. A manifest warning, in the style of the other platform-statement checks, might save the next author a link failure.N7 — a defined row's machine description
mcpp why flags --target i386-svr4shows norowlayer at all (no-march=i386). Withdriver = "lld"the link line is-flavor gnu --compress-debug-sections=zlibwith no-m elf_i386. The program still comes out as ELF32, because lld infers the emulation from the objects. The built-ini686-none-elfgets both flags. Stating them in the platform'sbuild.cflagsandlink.flagsworks today. Direction: see the common cause above.N8 — packing a defined row's dynamic program
mcpp pack --target x86_64-freebsd(vendored):error: ldd failed on …/bin/COMMAND.COM: command exited with 127. Only an ELF withoutPT_INTERPand withoutDT_NEEDEDskipsldd(pack.cppm#L3163-L3173).--mode staticproducescmd-0.1.0-x86_64-freebsd-static.tar.gzwhose program is still dynamically linked (interpreter /libexec/ld-elf.so.1).program_rowlabels a defined rowstaticwithout changing the link.Possible directions:
executionis not this machine, either compute the closure fromDT_NEEDEDand the declared sysroot without running anything, or refuse vendored mode with a message that names--mode static/ static linking;--mode static, check the produced program or refuse when the row's provider does not link statically.With static linking (
ldflags = ["-pthread", "-static"]in the platform) the defaultmcpp packalready succeeds.3. A correction to §7 of the issue
§7 said "openkal: no asynchronous interrupt notification; under okrun a shell cannot survive Ctrl+C". That is not right. openkal has
kal_process_stop_requested()(0.11; openkal-linux takes over SIGINT/SIGTERM/SIGHUP). Calling it once at the top of cmd'smainunder__OPENKAL__makes the ok image survive Ctrl+C as the native build does. So that item is a porting step in cmd, not an openkal gap. Sorry for the noise.Appendix — the FreeBSD row as measured (dynamic)
Happy to re-test any of these on a branch, or to split items into separate issues if that is easier to track.
speak-agent commentedon Nov 9, 2026
Implemented in mcpp 2026.11.9.1 (#912, released and in the xlings index): targets and providers (SPEC-016) — a platform defines rows and providers (G1), every flag names its layer (
mcpp why flags, G2), hosted C (G3), the compiler-driver link (G4), foreign artifacts (G5, G11), target-independent sources (G6), capability defaults (G7), the patch root (G8), per-artifact variants (G9), platform rows that pack and carry (G10),i686-none-elf(G12), and B1–B3. The decisions on what was not taken, and why, are in the design's §19 (.agents/docs/2026-11-09-target-model-design.md).