Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
8b3406f
The C++ layer answers for its own headers, and the iOS rows take libc…
speak-agent Sep 13, 2026
66fa538
manifest: min_api_level is a known [target.<triple>] scalar key (#610…
speak-agent Sep 13, 2026
4cc3370
pm: an OS-only selector is a platform for `mcpp emit xpkg` (#630 item 7)
speak-agent Sep 13, 2026
1ffefea
e2e 663: the baseline is read from the link line's archives, not from…
speak-agent Sep 13, 2026
8757230
One identity, two declarations, said aloud (T3, #630)
speak-agent Sep 13, 2026
3f074fd
The tool store's key holds the source: a git tool by its commit, a pa…
speak-agent Sep 13, 2026
7056f28
Merge branch '630/wt-b' into 630/engine-batch
speak-agent Sep 13, 2026
e1ea8c2
Merge branch '630/wt-d' into 630/engine-batch
speak-agent Sep 13, 2026
74c9740
Merge branch '630/wt-e' into 630/engine-batch
speak-agent Sep 13, 2026
1151c8d
docs: the iOS rows take libc++ and the builtins from the graph; the #…
speak-agent Sep 13, 2026
6e0ce92
An Apple cross target without a graph libc++: the SDK's headers when …
speak-agent Sep 13, 2026
ed28442
pack: stage what is declared before walking what is discovered (#630 §3)
speak-agent Sep 13, 2026
1d610bf
pack.binfmt: a Mach-O reader for needed_names (#630 §4)
speak-agent Sep 13, 2026
434fef1
Merge branch '630/wt-d' into 630/engine-batch
speak-agent Sep 13, 2026
90accbb
docs: the no-package iOS path is reported, not refused
speak-agent Sep 13, 2026
c8453c1
Merge branch '630/wt-c' into 630/engine-batch
speak-agent Sep 13, 2026
675b4f4
e2e 666: on macOS a Mach-O program is staged without its closure and …
speak-agent Sep 13, 2026
64997a9
record: what landed for the Mach-O reader
speak-agent Sep 13, 2026
abe529f
The package std module on an Apple cross target keeps the SDK on its …
speak-agent Sep 13, 2026
9723b5c
record: progress of the batch
speak-agent Sep 13, 2026
a946adc
T9 (#630 A9): the universal APK is the library route applied to an app
speak-agent Sep 13, 2026
140357f
Merge branch '630/wt-f' into 630/engine-batch
speak-agent Sep 13, 2026
8c69429
The tool store's rule in docs/30, the examples/12 README and the CI p…
speak-agent Sep 13, 2026
609629f
2026.9.13.2: the #630 batch
speak-agent Sep 13, 2026
bcbf1a7
e2e 304, 661, 666 on macOS; the builtins package is 22.1.8.4
speak-agent Sep 13, 2026
72d9aa8
ci-macos-ios: the report names 22.1.8.4
speak-agent Sep 13, 2026
b6b5759
record: what landed for #630 and where each claim was measured
speak-agent Sep 13, 2026
6525801
ci-macos-ios: a degradation is read by its sentence, not its domain
speak-agent Sep 13, 2026
5abed48
the builtins package is 22.1.8.5
speak-agent Sep 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ clang. It needs:
| | comes from | why |
|---|---|---|
| the compiler | `xim:llvm` | any sufficiently new clang emits arm64 Mach-O for an iOS deployment target |
| the C++ runtime | the payload's libc++ | as on every other Apple row |
| the C++ runtime | the payload's libc++ | as on every other Apple row -- superseded: the payload's archives are macOS objects and the SDK's libc++ is another release; the rows take `llvm.libcxx` from the graph (2026-09-13 record for #630, §5) |
| the **SDK** | the machine's Xcode | headers and stub libraries, not redistributable |
| running on a simulator | the machine's `simctl` | a proprietary runtime that exists only on its own OS |

Expand Down

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion .agents/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ superseded_by: 2026-09-07-....md # when status is superseded
---
```

283 records.
284 records.

## By subject

Expand Down Expand Up @@ -58,6 +58,7 @@ Records that declare one. Everything else is listed by date below.

### triage

- [What a framework and its ecosystem library still hit in the engine: the ten items of #630, read against the code](2026-09-13-630-what-a-framework-still-hits-in-the-engine.md) — landed
- [The engine gaps left open after the SDK batch](2026-09-12-engine-gaps-after-the-sdk-batch.md) — landed
- [Six open issues: what each one actually is, and what would answer it](2026-09-11-six-open-issues-analysis.md) — active

Expand All @@ -66,6 +67,7 @@ Records that declare one. Everything else is listed by date below.
### 2026-09

- [Four upstream asks from a UI framework: what each one is under mcpp's design, and the combined plan](2026-09-13-four-upstream-asks-from-a-ui-framework.md) — landed
- [What a framework and its ecosystem library still hit in the engine: the ten items of #630, read against the code](2026-09-13-630-what-a-framework-still-hits-in-the-engine.md) — landed
- [The engine gaps left open after the SDK batch](2026-09-12-engine-gaps-after-the-sdk-batch.md) — landed
- [A verified Web run that asked the host for node](2026-09-12-a-verified-web-run-that-asked-the-host-for-node.md) — landed
- [Implementation plan: a UI framework on Android, iOS and Web (#622)](2026-09-12-622-implementation-plan.md) — landed
Expand Down
54 changes: 36 additions & 18 deletions .github/workflows/ci-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -366,27 +366,36 @@ jobs:
# `n.value * 2` keeps `0` at `0`: a probe that changed every literal
# would turn `while (b != 0)` into a loop that divides by zero, and
# this step would report a crash rather than an answer.
probe_on() { sed -i 's/return std::format("{}", n.value);/return std::format("{}", n.value * 2);/' ../toyc/src/compile.cppm; }
probe_off() { sed -i 's/return std::format("{}", n.value \* 2);/return std::format("{}", n.value);/' ../toyc/src/compile.cppm; }
install_toyc() {
( cd ../toyc && "$MCPP" build >/dev/null )
cp "$(find ../toyc/target -name toyc -type f -perm -u+x | head -1)" "$1"
# THE PROBE COMPILER IS BUILT FROM A COPY OF THE TREE. The store's
# key carries a stamp of the tool's tree (#630, item 6), so touching
# `../toyc` would move the key and the next run would rebuild the
# tool from the restored source -- the isolating change here is
# different bytes at the SAME path, which needs the tree untouched.
probe_tree=/tmp/toyc-probe
rm -rf "$probe_tree"; cp -r ../toyc "$probe_tree"; rm -rf "$probe_tree/target"
probe_on() { sed -i 's/return std::format("{}", n.value);/return std::format("{}", n.value * 2);/' "$1/src/compile.cppm"; }
probe_off() { sed -i 's/return std::format("{}", n.value \* 2);/return std::format("{}", n.value);/' "$1/src/compile.cppm"; }
install_toyc() { # $1 = the tree to build, $2 = the store path to overwrite
( cd "$1" && "$MCPP" build >/dev/null )
cp "$(find "$1/target" -name toyc -type f -perm -u+x | head -1)" "$2"
}

# `mcpp cache dir` prints a legacy-directory note on a second line.
# The newest entry, since a tree edited earlier in this job may have
# left another one.
store="$("$MCPP" cache dir | head -1)/tool"
cached="$(find "$store" -path '*toyc@0.1.0*/bin/toyc' | head -1)"
cached="$(ls -t $(find "$store" -path '*toyc@0.1.0*/bin/toyc') 2>/dev/null | head -1)"
[ -n "$cached" ] || {
echo "FAIL: no toyc in the tool store under $store"; exit 1; }

probe_on; install_toyc "$cached"; probe_off
probe_on "$probe_tree"; install_toyc "$probe_tree" "$cached"; probe_off "$probe_tree"
"$MCPP" run > /tmp/toy3.log 2>&1 || true
grep -q 'answer() = 168' /tmp/toy3.log || {
cat /tmp/toy3.log
echo "FAIL: a changed compiler binary did not reach the artifact."
echo " rules-toy must declare the compiler among the action's inputs."
exit 1; }
install_toyc "$cached"
install_toyc "$probe_tree" "$cached"
"$MCPP" run > /tmp/toy4.log 2>&1 || true
grep -q 'answer() = 42' /tmp/toy4.log || {
cat /tmp/toy4.log
Expand All @@ -395,25 +404,34 @@ jobs:
echo " rules-toy must declare the compiler among the action's inputs."
exit 1; }

# THE STORE HOLDS NO SOURCE CONTENT, which the example's README and
# docs/30 both state. Editing the compiler's sources at the same
# version changes nothing, because nothing rebuilds the tool.
probe_on
# THE STORE'S KEY HOLDS THE SOURCE (#630, item 6), which the
# example's README and docs/30 both state: editing the compiler's
# sources at the same version rebuilds the tool, and so does the
# reversal. Both directions, because a key that only ever grew
# would pass the first and fail the second.
sleep 1; probe_on ../toyc
"$MCPP" run > /tmp/toy5.log 2>&1 || true
grep -q 'answer() = 42' /tmp/toy5.log || {
grep -q 'answer() = 168' /tmp/toy5.log || {
cat /tmp/toy5.log
echo "FAIL: the tool store now sees source content."
echo " The example's README and docs/30 state that it does not; update them."
echo "FAIL: editing the compiler's sources at the same version did not reach the artifact."
echo " The tool store's key must carry the path package's tree stamp."
exit 1; }
sleep 1; probe_off ../toyc
"$MCPP" run > /tmp/toy5b.log 2>&1 || true
grep -q 'answer() = 42' /tmp/toy5b.log || {
cat /tmp/toy5b.log
echo "FAIL: reverting the compiler's sources did not reach the artifact."
exit 1; }

# ... and bumping the version is the way out both of them offer.
# ... and a version bump still rebuilds it, as it always did.
sleep 1; probe_on ../toyc
sed -i 's/^version = "0.1.0"/version = "0.1.1"/' ../toyc/mcpp.toml
"$MCPP" run > /tmp/toy6.log 2>&1 || true
sed -i 's/^version = "0.1.1"/version = "0.1.0"/' ../toyc/mcpp.toml
probe_off
probe_off ../toyc
grep -q 'answer() = 168' /tmp/toy6.log || {
cat /tmp/toy6.log; echo "FAIL: bumping the tool version did not rebuild it"; exit 1; }
echo "ok: the compiler is a declared input, and the store is keyed on the version"
echo "ok: the compiler is a declared input, and the store is keyed on the tool's source"

- name: "Graphics example: render offscreen on lavapipe and assert the pixels"
run: |
Expand Down
79 changes: 77 additions & 2 deletions .github/workflows/ci-macos-ios.yml
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,19 @@ jobs:
[build]
ios_deployment_target = "18.0"

# THE C++ STANDARD LIBRARY AND THE COMPILER RUNTIME ARE PACKAGES ON
# THESE ROWS (#630). The payload's static libc++ is a macOS object
# and its resource directory carries no iOS builtins archive, so the
# engine used to link the SDK's libc++ under the payload's newer
# headers -- which fails at link on the first inline path the older
# dylib does not export. `llvm.libcxx` brings headers, module and
# objects as one release; `llvm.compiler-rt-builtins` brings
# `__isPlatformVersionAtLeast`. Declared by git until the index
# carries them.
[target.'cfg(os = "ios")'.dependencies]
llvm.libcxx = { git = "https://github.com/mcpplibs/libcxx.git", tag = "22.1.8.1" }
llvm.compiler-rt-builtins = { git = "https://github.com/mcpplibs/compiler-rt-builtins.git", tag = "22.1.8.5" }

# THE RUNNER IS AN ARGV PREFIX AND THE SESSION BELONGS TO A
# PACKAGE. `simctl-run` comes from `xim:apple-simulator-tools`; it
# chooses a device, boots it if it is not booted, waits, spawns, and
Expand All @@ -212,8 +225,18 @@ jobs:
TOML
cat > /tmp/iostest/src/main.cpp << 'CPP'
import std;
// The two inline paths that failed at link under the payload's
// headers over the SDK's libc++ (`__hash_memory`,
// `__atomic_notify_all_global_table`), and an availability check,
// which is `__isPlatformVersionAtLeast` from the builtins package.
int main() {
std::vector<int> v{3, 1, 2};
std::unordered_map<std::string, int> m;
m["three"] = 3;
std::atomic<int> a{1};
a.notify_all();
int two = 2;
if (__builtin_available(iOS 17, *)) two = 2;
std::vector<int> v{m["three"], a.load(), two};
std::ranges::sort(v);
std::print("{}-{}-{}\n", v[0], v[1], v[2]);
}
Expand Down Expand Up @@ -274,9 +297,61 @@ jobs:
run: |
set -euo pipefail
cd /tmp/iostest
"$MCPP_DEV" build --target aarch64-ios-sim
"$MCPP_DEV" build --target aarch64-ios-sim 2>&1 | tee build-sim.log
/tmp/assert-artefact.sh aarch64-ios-sim arm64 7 18.0

# THE TWO LAYERS THE PACKAGES SUPPLY, READ FROM THE REPORT AND FROM THE
# ARTEFACT. A build that succeeds cannot tell a self-contained libc++
# from the SDK's; the load commands can, and the report says which
# package answered for each layer.
- name: "aarch64-ios-sim: the C++ runtime and the builtins are the graph's"
run: |
set -euo pipefail
cd /tmp/iostest
grep -E 'c\+\+-abi +libc\+\+ +\(libcxx@22\.1\.8\.1, graph\)' build-sim.log \
|| { echo "FAIL: the report does not name llvm.libcxx as the C++ layer"; exit 1; }
grep -E 'compiler-runtime +compiler-rt +\(compiler-rt-builtins@22\.1\.8\.5, graph\)' build-sim.log \
|| { echo "FAIL: the report does not name llvm.compiler-rt-builtins as the compiler runtime"; exit 1; }
art=$(ls /tmp/iostest/target/aarch64-ios-sim/*/bin/iostest | head -1)
if otool -L "$art" | grep -q 'libc++'; then
echo "FAIL: the artefact links a libc++ dylib"; otool -L "$art"; exit 1
fi
echo "ok: no libc++ dylib in the load commands; both layers are the graph's"

# THE NEGATIVE DIRECTION: without the packages a program that imports
# std still builds, as it did before this release, and the degradation
# names the two lines; one that does not import std takes the SDK's
# headers under the SDK's libc++. Without this step the change could be
# read as "every iOS build now needs a package".
- name: "aarch64-ios-sim: without the packages, import std builds with the hazard named and plain C++ takes the SDK's headers"
run: |
set -euo pipefail
rm -rf /tmp/iosplain && mkdir -p /tmp/iosplain/src && cd /tmp/iosplain
cat > mcpp.toml << 'TOML'
[package]
name = "iosplain"
version = "0.1.0"
[build]
ios_deployment_target = "18.0"
TOML
printf 'import std;\nint main() { std::print("x\\n"); }\n' > src/main.cpp
"$MCPP_DEV" build --target aarch64-ios-sim > mixed.log 2>&1 || { echo "FAIL: import std without llvm.libcxx no longer builds"; cat mixed.log; exit 1; }
# A degradation renders its `what` text, not its domain, so the
# sentence is what a log can be asked for.
grep -q "links the SDK's libc++ under the toolchain payload's libc++ headers" mixed.log || { echo "FAIL: no degradation named the mixed libc++"; cat mixed.log; exit 1; }
grep -q 'llvm.libcxx' mixed.log || { echo "FAIL: the degradation does not name llvm.libcxx"; cat mixed.log; exit 1; }
printf '#include <cstdio>\n#include <string>\nint main() { std::string s = "1-2-3"; std::puts(s.c_str()); }\n' > src/main.cpp
rm -rf target
"$MCPP_DEV" build --target aarch64-ios-sim 2>&1 | tee plain.log
grep -q 'carries no compiler runtime for aarch64-ios-sim' plain.log || { echo "FAIL: no degradation named the missing compiler runtime"; exit 1; }
grep -q "links the SDK's libc++ under" plain.log && { echo "FAIL: a program without import std was reported as mixing libc++"; exit 1; }
ninja=$(ls target/aarch64-ios-sim/*/build.ninja | head -1)
grep -q -- '-isystem[^ ]*iPhoneSimulator[^ ]*/usr/include/c++/v1' "$ninja" || { echo "FAIL: the plain program does not take the SDK's C++ headers"; grep -o -- '-isystem[^ ]*c++/v1' "$ninja" | sort -u; exit 1; }
grep -q -- '-isystem[^ ]*xim-x-llvm[^ ]*/c++/v1' "$ninja" && { echo "FAIL: the plain program still takes the payload's C++ headers"; exit 1; }
art=$(ls target/aarch64-ios-sim/*/bin/iosplain | head -1)
otool -L "$art" | grep -q '/usr/lib/libc++.1.dylib' || { echo "FAIL: the plain program does not link the SDK's libc++"; otool -L "$art"; exit 1; }
echo "ok: import std builds with the hazard named; plain C++ takes the SDK's headers and libc++; the builtins degradation is printed"

# THE SUPPORTED PATH, which is what the `verified` tier claims: a runner
# the manifest declares and a program a package provides. The program's
# own line is compared whole. An iOS-simulator Mach-O does not execute on
Expand Down
62 changes: 62 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,68 @@

## [Unreleased]

### 一个框架与它的生态库仍会撞到的引擎缺口:#630 的十项

#630 汇总了 HuxerUI 在六个平台上落地后引擎仍欠的十项。每一项都先在
`b63dc4e5` 上定位到代码行再分类,其中四处与 issue 的叙述不同(设计记录 §0):
「根赢」其实是工作队列先出队者赢;`mcpp pack` 早已把 Mach-O 的拒绝带到
dispatched 格式,只是交出去的树是空的;第 7 项的后果在安装期而不在链接期;
A8 在 2026-09-12 的记录里已判定为声明放错了包。

**一个身份,两条声明,要说出来(项 1、2)。** 同一依赖身份的两条 `git`/`path`
声明此前没有任何字段被比较,留下的是先出队的那条。现在根的声明跨 kind、跨引用
都赢,并以 `dependency/source-override` 告警点名双方与各自引用;依赖对根所钉
checkout 的版本要求照 `addrset::unify` 的 Holds/Violated 校验;两个非根声明的
kind 冲突仍拒绝,并提示在根里声明以决之。`docs/05` 中英文各得一张决策表。

**先暂存声明的文件,再走发现的闭包(项 3a)。** `mcpp pack` 在闭包之前拷贝程序
与 `mcpp::deploy` 的文件;本机走不了闭包的格式(Mach-O 程序;Windows 宿主上的
非 PE 产物)把树交给 dispatched 格式,stage manifest 记 `closure = not-walked`
与原因;`tar`/`dir` 仍拒绝。`binfmt` 得到 Mach-O 读取器(thin 与 fat、两种字节
序、`LC_LOAD_DYLIB`/`LC_RPATH`)、`is_system_lib` 的 Mach-O 行与 `@rpath` 解析
器(项 3b);闭包步骤对 Mach-O 仍报 not-walked,打包 dylib 待 `LC_RPATH` 改写
的实测。

**C++ 层回答自己的头文件(项 4)。** 四处用 `cAbi.prebuilt()` 代答「载荷的
libc++ 是否适用」,在 openkal 与原生构建重合,在 iOS 行(SDK 的 C 库 + 图里的
libc++)分开:引擎把 libc++ 22 的头配到 SDK 的 libc++ 19 上,程序在
`__hash_memory` 处链接失败。现改读 `plan.targetSide.cxx.fromGraph()`;iOS 行的
C++ 运行时与编译器运行时成为图里的源码包(`llvm.libcxx@22.1.8.1`、
`llvm.compiler-rt-builtins@22.1.8.5`,与 `openkal-llvm-runtime` 同一机制,框架
声明一次、应用继承)。不声明时:不导入 `std` 的程序取 SDK 的头;导入的保留昨天
的搭配并由 prepare 报告一次(`target/cxx-runtime`)点名两行;载荷没有该平台的
builtins 归档时报告一次(`target/compiler-runtime`),从不去 Xcode 里找。
`graph_runtime_compile_flags` 只在 C 库来自图时给 Mach-O 加 `-femulated-tls`。

**`min_api_level` 是已知键(项 5)。** 它被 Android 行读取、被 `dist-apk` 写成
`minSdkVersion`,却被 `[target.<triple>]` 的未知键扫描报成 unsupported,`--strict`
下变硬错。加入已知表;单测的分母取自解析器自己的 `body.find` 站点。

**工具仓库的键里有源码(项 6)。** host 工具按 包×版本×宿主×编译器×feature×闭包
版本 键控,`path` 包改源码不抬版本即被无视。现在 `git` 包按解析出的 commit、
`path` 包按树的 stat 印记(相对路径、大小、mtime)进键,上游同理;改动与回退各
到达消费者,未变的树仍命中(e2e 187 的断言不变)。

**只点操作系统的 selector 就是平台(项 7)。** `mcpp emit xpkg` 把 `cfg(linux)`、
`cfg(os = "windows")`、`cfg(macos)`、`cfg(unix)` 映射到描述符的平台块;其余
selector 保留告警并说明这一例外。

**一个 app 的产物是共享库时,pack 接受多个 triple(A9)。** 路由按产物形态而非
target 的 kind:Android 行的 `kind = "app"` 走库路线的多腿暂存,一棵树里
`lib/<abi>/lib<name>.so` 各一份,一次 dispatch 得到 universal APK。

- 判据:`tests/e2e/661`(六种声明组合,双向)、`662`(先暂存后闭包,dispatched
格式看到部署文件)、`663`(Linux 上以 glibc 为 C 库、`llvm.libcxx` 为 C++ 层:
报告、`-nostdinc++`、`-nostdlib++`、`ldd` 无 libc++、程序运行;不带包时命令行
逐字节不变)、`665`(工具跟随源码,双向,未变命中)、`666`(macOS:Mach-O 程序
被暂存并交给 dispatched 格式,`closure = not-walked`)、`641` 第 9 例(`--strict`
下 `min_api_level` 静默);`ci-macos-ios` 在模拟器行上用两个包构建并运行,并断言
不带包的两个方向;单测 `test_hostflags`、`test_targetside`、`test_tool_store`、
`test_pack_binfmt`、`test_target_scalar_keys`、`test_cfg_os_only_platform`。
- 生态:`mcpplibs/libcxx`(新仓)、`mcpplibs/compiler-rt-builtins#1`、
`mcpplibs/mcpp-index#408`,GitHub 与 GitCode 双端资产逐字节核验。
- 设计记录:`.agents/docs/2026-09-13-630-what-a-framework-still-hits-in-the-engine.md`。

### `mcpp::action` 的列表不再有长度上限;`${mcpp.self}` 让 action 叫出引擎自己

内置的 `mcpp` 模块此前把一个 action 的 `inputs`、`outputs`、`command` 等六个列表
Expand Down
Loading
Loading