Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
0283c59
docs(design): #634 triage record (revision 2), its probes, and the im…
speak-agent Sep 14, 2026
5fc9be6
feat(manifest): a conditional dependency declaration replaces the unc…
speak-agent Sep 14, 2026
483ffb2
feat(resolve): a path or git dependency takes the identity its manife…
speak-agent Sep 14, 2026
1279cba
feat(resolve): resolution.json records the resolved dependency graph,…
speak-agent Sep 14, 2026
a9d02ce
The static C++ runtime is located by asking the driver for the effect…
speak-agent Sep 14, 2026
898ade6
run, test and pack declare --toolchain (#634 A10)
speak-agent Sep 14, 2026
e8a957f
[test] discover; every runner receives MCPP_RUNTIME_FILES; a format i…
speak-agent Sep 14, 2026
a5ed75f
docs: [test] discover, MCPP_RUNTIME_FILES, format-named runners, --to…
speak-agent Sep 14, 2026
fb5f018
feat(resolve): the engine states the target's platform floor as a fac…
speak-agent Sep 14, 2026
6b9e7bc
test(unit): [test] discover names, exclusions and refusals; the archi…
speak-agent Sep 14, 2026
0b5d3b3
feat(config): [index.repos.<name>] reaches an existing registry and i…
speak-agent Sep 14, 2026
fd62df7
test(e2e): #634 A1, A2, A9, C4, X and A7, each failing on 2026.9.14.1
speak-agent Sep 14, 2026
b59e688
docs: conditional dependency replacement, per-row library kinds, path…
speak-agent Sep 14, 2026
7ea7beb
fix(build): an rpath that begins with a loader token is left as writt…
speak-agent Sep 14, 2026
a44d3d3
feat(build): an ELF shared library records its file name as its defau…
speak-agent Sep 14, 2026
ca90579
feat(pack): the Android and Mach-O closures are read from the files a…
speak-agent Sep 14, 2026
15b7b59
Merge feat/634-pack: Android and Mach-O closures, rpath loader tokens…
speak-agent Sep 14, 2026
18429c8
docs: not-walked names an incomplete closure, and an ELF shared libra…
speak-agent Sep 14, 2026
737cd95
Merge feat/634-run: driver-queried static runtime, runtime files for …
speak-agent Sep 14, 2026
f27e79b
fix(runner): a runner a dependency supplies and the project's build p…
speak-agent Sep 14, 2026
5fc0abf
fix(toolchain): a refused toolchain spec names where it was written, …
speak-agent Sep 14, 2026
abf685d
docs(machine-output): 126 also answers a directory distributable that…
speak-agent Sep 14, 2026
00e85f9
fix(plan): a consumer outside a graph-built shared library's director…
speak-agent Sep 14, 2026
e36f13d
ci: the Android rows run their e2e tests and mcpp test on an API 34 e…
speak-agent Sep 14, 2026
7b067cb
docs(design): #634 revision 3 folded into the triage record, and the …
speak-agent Sep 14, 2026
9f6c747
docs(changelog): the engine half of #634
speak-agent Sep 14, 2026
7876e6b
chore: bump version to 2026.9.14.2
speak-agent Sep 14, 2026
5829734
fix(manifest): the row-kind check uses plain loops, which clang 20.1.…
speak-agent Sep 14, 2026
99378fc
docs: the reverse index names [test] discover and MCPP_RUNTIME_FILES
speak-agent Sep 14, 2026
c18bf69
test(unit): a Mach-O install name in the closure test is a POSIX path…
speak-agent Sep 14, 2026
8425777
test(e2e): 683 compares the registry in the accessor's spelling on Wi…
speak-agent Sep 14, 2026
5a37147
test(e2e): 249 and 250 run their pack legs on macOS, where a Mach-O p…
speak-agent Sep 14, 2026
cdc57bd
test(unit): the PE closure fixture keeps its RVAs in std::size_t, whi…
speak-agent Sep 14, 2026
d0da8de
fix(config): only a [index.repos.<name>] table is reconciled into an …
speak-agent Sep 14, 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
859 changes: 859 additions & 0 deletions .agents/docs/2026-09-14-634-cmake-parity-items-by-home.md

Large diffs are not rendered by default.

578 changes: 578 additions & 0 deletions .agents/docs/2026-09-14-634-implementation-plan.md

Large diffs are not rendered by default.

363 changes: 363 additions & 0 deletions .agents/docs/2026-09-14-634-probes.sh

Large diffs are not rendered by default.

6 changes: 5 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
---
```

284 records.
286 records.

## By subject

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

### triage

- [#634 implemented across five repositories: the plan, its review, and the ledger that tracks it](2026-09-14-634-implementation-plan.md) — active
- [A framework's CMake parity list: the twenty-one items of #634, read against the code and routed to where each one belongs](2026-09-14-634-cmake-parity-items-by-home.md) — active
- [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 +68,8 @@ Records that declare one. Everything else is listed by date below.

### 2026-09

- [#634 implemented across five repositories: the plan, its review, and the ledger that tracks it](2026-09-14-634-implementation-plan.md) — active
- [A framework's CMake parity list: the twenty-one items of #634, read against the code and routed to where each one belongs](2026-09-14-634-cmake-parity-items-by-home.md) — active
- [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
Expand Down
128 changes: 128 additions & 0 deletions .github/tools/android_emulator_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#!/usr/bin/env bash
# `mcpp test` on the x86_64 Android row, run through `adb-run` on an emulator.
#
# android_emulator_test.sh prebuild <dir> before the emulator: write the
# fixture, provision the row and
# compile its test programs
# android_emulator_test.sh run <dir> inside the emulator step: run
# them and assert on the stream
#
# The fixture's tests measure the two properties #634 A6 decided (triage
# record §5.6), each of which failed on 2026.9.14.1 on an API 34 emulator:
#
# runs the program loads: on this row it needs no
# `libc++_shared.so`, because the engine finds the NDK's
# static C++ runtime by asking the driver
# reads_relative a file `[runtime] deploy` placed beside the program is
# readable there, because the runner receives
# MCPP_RUNTIME_FILES and `adb-run` pushes what it lists
#
# `MCPP` names the engine under test; the assertion reads the JSON test stream
# (docs/50 §8), not the human summary.
set -euo pipefail

mode="${1:?usage: android_emulator_test.sh prebuild|run <dir>}"
dir="${2:?usage: android_emulator_test.sh prebuild|run <dir>}"
: "${MCPP:?set MCPP to the engine under test}"
TARGET=x86_64-linux-android

write_fixture() {
rm -rf "$dir"
mkdir -p "$dir/src" "$dir/tests" "$dir/share"
echo "m634-data" > "$dir/share/data.txt"
cat > "$dir/mcpp.toml" <<'TOML'
[package]
name = "droidtest"
version = "0.1.0"

[targets.droidtest]
kind = "bin"
main = "src/main.cpp"

[runtime]
deploy = [ { from = "share/data.txt", to = "data" } ]

[target.x86_64-linux-android]
min_api_level = 24
runner = ["adb-run"]

[target.x86_64-linux-android.xlings.workspace]
"xim:android-platform-tools" = "37.0.1-4"
TOML
printf 'int main() { return 0; }\n' > "$dir/src/main.cpp"
printf 'int main() { return 0; }\n' > "$dir/tests/runs.cpp"
cat > "$dir/tests/reads_relative.cpp" <<'CPP'
#include <cstdio>
#include <cstring>
#include <string>
int main(int, char** argv) {
std::string p = argv[0];
auto s = p.rfind('/');
std::string f = (s == std::string::npos ? std::string(".") : p.substr(0, s)) + "/data/data.txt";
FILE* fp = std::fopen(f.c_str(), "r");
if (!fp) { std::printf("open failed: %s\n", f.c_str()); return 3; }
char buf[64] = {0};
std::fgets(buf, sizeof buf, fp);
std::fclose(fp);
std::printf("read %s: %s", f.c_str(), buf);
return std::strncmp(buf, "m634-data", 9) == 0 ? 0 : 4;
}
CPP
}

status_of() { # status_of <stream> <test>
python3 - "$1" "$2" <<'PY'
import json, sys
for line in open(sys.argv[1]):
line = line.strip()
if not line.startswith("{"):
continue
rec = json.loads(line)
if rec.get("test") == sys.argv[2]:
print(rec.get("status"), rec.get("exit_code"))
break
else:
print("absent")
PY
}

case "$mode" in
prebuild)
write_fixture
cd "$dir"
# Compiles the tests and provisions the NDK and the platform tools. No
# device is attached yet, so the runs are reported, not asserted.
"$MCPP" test --target "$TARGET" --message-format json > prebuild.json 2> prebuild.err || true
tail -5 prebuild.err
# The property the runner step depends on, checked on the build
# machine: no test program names the shared C++ runtime.
found=0
while IFS= read -r prog; do
found=$((found + 1))
if readelf -d "$prog" | grep -q 'NEEDED.*libc++_shared.so'; then
echo "FAIL: $prog needs libc++_shared.so"
exit 1
fi
done < <(find target -type f \( -name runs -o -name reads_relative \) -path '*/bin/*')
[ "$found" -ge 2 ] || { echo "FAIL: expected two test programs, found $found"; exit 1; }
echo "prebuild: $found test programs, none needs libc++_shared.so"
;;
run)
cd "$dir"
adb devices
"$MCPP" test --target "$TARGET" --message-format json > test.json 2> test.err || true
tail -5 test.err
cat test.json
runs=$(status_of test.json runs)
reads=$(status_of test.json reads_relative)
echo "runs: $runs"
echo "reads_relative: $reads"
[ "$runs" = "pass 0" ] || { echo "FAIL: runs did not pass on the emulator"; exit 1; }
[ "$reads" = "pass 0" ] || { echo "FAIL: reads_relative did not pass on the emulator"; exit 1; }
echo "PASS: mcpp test on the x86_64 Android emulator"
;;
*)
echo "usage: android_emulator_test.sh prebuild|run <dir>" >&2
exit 2
;;
esac
87 changes: 87 additions & 0 deletions .github/workflows/ci-linux-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,93 @@ jobs:
cat 338_cortex_m_picolibc_sysroot.sh.log
echo "338 neither passed nor reported why it did not run"; exit 1; }

# ──────────────────────────────────────────────────────────────────
# Android: the rows no sharded runner can reach.
#
# `run_all.sh` adds the `android-ndk` capability only when the NDK payload
# is already installed, and no other job installs it, so every
# `# requires: android-ndk` test skipped on every CI runner while reporting
# green. This job installs the payload, runs those tests directly and demands
# each PASS line, then runs `mcpp test` on an API 34 emulator through
# `adb-run` (the configuration measured on mcpp#635), which is the only
# place a test program's loading on the device is observed.
# ──────────────────────────────────────────────────────────────────
android:
name: android e2e (NDK rows + x86_64 emulator)
runs-on: ubuntu-24.04
timeout-minutes: 90
env:
MCPP_HOME: /home/runner/.mcpp
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/bootstrap-mcpp

- name: Build mcpp from source (self-host)
run: |
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
"$XLINGS_BIN" config --mirror GLOBAL 2>/dev/null || true
"$MCPP" self config --mirror GLOBAL 2>/dev/null || true
"$MCPP" build

- name: Install the NDK (xim:android-ndk)
timeout-minutes: 20
run: |
XLINGS_HOME="$MCPP_HOME/registry" "$XLINGS_BIN" install xim:android-ndk@30.0.16248370 -y
# Present before the tests, so a missing payload fails this step
# rather than an assertion deep inside a test.
ls "$MCPP_HOME"/registry/data/xpkgs/xim-x-android-ndk/*/toolchains/llvm/prebuilt/*/bin/clang++

- name: Android e2e (no device)
timeout-minutes: 40
run: |
MCPP=$(realpath "$(find target -type f -name mcpp -printf '%T@ %p\n' | sort -rn | head -1 | cut -d' ' -f2)")
test -x "$MCPP"
export MCPP
export MCPP_VENDORED_XLINGS="$XLINGS_BIN"
export MCPP_E2E_TOOLCHAIN_MIRROR=GLOBAL
"$MCPP" self config --mirror "$MCPP_E2E_TOOLCHAIN_MIRROR"
"$MCPP" toolchain install gcc 16.1.0
"$MCPP" toolchain default gcc@16.1.0
# Run directly, not through run_all.sh, so a skip cannot pass as a
# green: each script either prints its PASS line or this step fails.
declare -A pass=(
[652b_an_application_on_android_is_a_shared_library]='652b: kind = "app" on Android is a shared library OK'
[664_a_universal_apk_is_two_legs_in_one_tree]='664: the universal APK is the library route applied to an app OK'
[667_an_android_pack_stages_its_closure]='667: an Android pack stages its closure OK'
[675_android_test_programs_carry_their_cxx_runtime]='PASS: 675_android_test_programs_carry_their_cxx_runtime'
[680_a_dependency_floor_on_the_android_api_level]='PASS: 680_a_dependency_floor_on_the_android_api_level'
)
for t in "${!pass[@]}"; do
echo "=== $t ==="
bash "tests/e2e/$t.sh" 2>&1 | tee "$t.log"
rc=${PIPESTATUS[0]}
[ "$rc" = "0" ] || { echo "$t failed (exit $rc)"; exit 1; }
grep -qF "${pass[$t]}" "$t.log" || { echo "$t did not print its PASS line"; exit 1; }
done
echo "MCPP=$MCPP" >> "$GITHUB_ENV"

- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm

- name: Compile the device tests before the emulator starts
timeout-minutes: 20
run: bash .github/tools/android_emulator_test.sh prebuild "$RUNNER_TEMP/droidtest"

- name: mcpp test on an API 34 emulator
timeout-minutes: 30
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 34
arch: x86_64
target: google_apis
force-avd-creation: false
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
script: bash .github/tools/android_emulator_test.sh run "$RUNNER_TEMP/droidtest"

# ──────────────────────────────────────────────────────────────────
# Hermetic (no host toolchain): the ONLY environment class that
# faithfully reproduces issue #195. Standard runners ship gcc +
Expand Down
54 changes: 54 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,60 @@

## [Unreleased]

### 一个框架的 CMake 对齐清单:#634 的二十一项中引擎的部分(2026.9.14.2)

#634 列出 HuxerUI 从 CMake 迁到 mcpp 时仍缺的二十一项。分类与决定见设计记录
`2026-09-14-634-cmake-parity-items-by-home.md`,实现计划与台账见
`2026-09-14-634-implementation-plan.md`;引擎只承接通用能力,专有功能归官方插件
(mcpp-plugins 0.10.0)、载荷(xim-pkgindex#838)或工程本身。

**条件依赖声明替换无条件声明(A1)。** 选择器命中的行上,
`[target.<sel>.dependencies]` 中某个身份的声明替换 `[dependencies]` 中同一身份的
声明,按规范化身份比较,多段按清单顺序、后者为准;`dev-dependencies`、
`build-dependencies`、`feature-deps` 同理。此前保留的是无条件那条,条件表里的
`linkage = "shared"` 在它自己的行上被静默丢弃。只写选项、不写来源的条件表给出
补全来源后的写法;`[target.<sel>]` 下 mcpp 不读取的子表被报出(已知表由单测从
解析点双向核对)。`[target.<sel>.targets.<n>] kind` 是库目标形态的按行形式,
消费者的 `linkage = "static"` 被拒时,告警点名这一行。

**`path`/`git` 依赖的身份取自其清单(A2)。** 键规范化到另一身份时采用清单声明并
每条声明边告警一次;同一来源上两个命名空间不同的键在扫描前拒绝。mcpp 自己的三个
示例改写为声明的身份。

**Android 与 Mach-O 的闭包被读取并暂存(A3)。** `mcpp pack` 从文件读闭包:Android
暂存到 `lib/`(多 triple 为 `lib/<abi>/`),含图里的共享库与 `libc++_shared.so`;
Mach-O 的 dylib 暂存在程序旁,不改 load command、不重签。stage manifest 增加
`needs<TAB><name><TAB><路径|platform|unresolved>` 行;闭包不完整时写 `not-walked`,
`dir`/`tar` 拒绝并点名。rpath 中以 `@executable_path`、`@loader_path`、`@rpath`
开头的项不再被锚定到包目录。

**ELF 共享库默认以文件名为 SONAME(A4)。** 位置在 `$ldflags` 之前,工程自己写的
`-soname` 仍生效。

**测试与运行器(A5、A6、A10、B3)。** `[test] discover` 指定测试从哪些 glob 发现;
静态 C++ 运行时通过询问驱动(带 API level)定位,Android 行的测试程序不再依赖
`libc++_shared.so`,NDK 的 `libc++.a` 链接脚本里的归档名进入 `--exclude-libs`;
每个 runner 收到 `MCPP_RUNTIME_FILES`(部署文件与链接的共享库,TAB 分隔);
`run`、`test`、`pack` 接受 `--toolchain`;`mcpp run --format <f>` 使用名为 `<f>` 的
runner,没有 runner 能到达的目录分发物在启动前以 126 拒绝。依赖的构建程序与根的
构建程序提供同名 runner 时拒绝并点名二者。

**解析记录与平台下限(X、A9)。** `resolution.json` 增加 `graph`(每个包、每条请求
的键与声明表、库的链接形态与原因),`mcpp why deps` 打印它;引擎把
`android.api-level`、`ios.deployment-target`、`macos.deployment-target` 陈述为事实,
依赖以 `version-floor` 要求拒绝过低的下限,拒绝写明「this build targets」与设定来源。

**配置与访问器(C4、A7)。** `config.toml` 的 `[index.repos.<name>]` 对已存在的 home
生效,删去后恢复原条目;从被覆盖的索引安装时打印来源。构建程序新增
`mcpp::pkg_config_libdir()`。

**实现中发现并修复的缺陷。** 嵌套在 `tests/` 子目录中的测试加载不到图构建的共享库;
被拒的 `--toolchain` 值被记在 `[toolchain].<platform>` 名下。

- 判据:`tests/e2e/266`、`666`–`685`(677–685 在 2026.9.14.1 上失败),
单测 `test_pack_closure`、`TargetScalarKeys.EveryParsedSubTableIsKnownToTheSweep`;
`ci-linux-e2e` 新增 `android` job,在 API 34 模拟器上跑 `mcpp test`。

### 图里的 libc++ 之下,预编译 C 库的头文件也要进 std 模块的命令;iOS 行未写的部署下限取 SDK 的版本

`llvm.libcxx` 的 CI 扩到它声明的每一行后量到两处引擎缺口。包提供的 std 模块在
Expand Down
33 changes: 31 additions & 2 deletions docs/04-mcpp-toml.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,10 @@ Autotools/CMake. On Linux, mcpp passes `-Wl,-soname,<name>` to the linker and
generates a `<name> -> lib<target>.so` alias in the output directory, so that
downstream programs can load the library via its standard ABI name through
`DT_NEEDED` or `dlopen()`. This field only applies to `kind = "shared"`, and the
value must be a filename basename.
value must be a filename basename. An ELF shared library that declares no
`soname` records its output file name as its SONAME (2026.9.14.2+), which is
the name its consumers already record in `DT_NEEDED`; bionic requires one from
API level 23.

Shared-library targets work on all three binary formats. ELF gets a `.so` with
its `soname` and a `$ORIGIN` search path; Mach-O gets a `.dylib` whose install
Expand Down Expand Up @@ -440,6 +443,7 @@ whose C library is linked statically — which is the **default for musl** —
| The package writes | mcpp reads it as |
|---|---|
| `[targets.<n>] kind = "shared"` | *must* be shared — something else in the process will `dlopen` it, so there may only be one copy (X11, a Vulkan loader) |
| `[target.<sel>.targets.<n>] kind = "shared"` *(2026.9.14.2+)* | *must* be shared on the rows the selector matches, and either form elsewhere ([22 — The Target Side](22-target-side.md)) |
| `ldflags` containing `-L` | *must* be static — the package ships prebuilt archives mcpp did not compile and cannot place inside a shared object it builds |
| a packaged library (`mcpp pack`) | whichever legs it actually ships, from `[[runtime.artifacts]] role` |
| anything else | either form |
Expand All @@ -448,6 +452,13 @@ whose C library is linked statically — which is the **default for musl** —
packages write it without choosing anything. Absence of a statement is not a
statement.

A request the constraint refuses is linked in the form the package allows,
with a warning that names the package's statement (`its manifest states
[targets.fw] kind = "shared", ...`); `--strict` turns the warning into an
error. `mcpp why deps` reports each dependency's form and the reason for it:
`default`, `requested`, `package-kind`, `row-kind`, `packaged`, `no-sources`,
`prebuilt-inputs`, `no-loader` or `static-libc` (2026.9.14.2+).

A per-dependency `linkage` is honoured **only in the root project's**
`[dependencies]`. A package deep in the graph does not get to decide how the
final program is laid out; one that genuinely must be a single shared copy says
Expand Down Expand Up @@ -1434,6 +1445,24 @@ do`.

Moved to [09 — Commands by Scenario](09-commands-by-scenario.md).

### 2.17 `[test]` — Where Test Programs Are

```toml
[test]
discover = ["tests/**/*.cpp"] # the default
```

| Key | Type | Meaning |
|---|---|---|
| `discover` | array of globs | every file a glob matches is one test program; a glob beginning with `!` removes the files it matches; `[]` discovers none |

The globs use the vocabulary of `[build] sources`. A test's name is its path
relative to the fixed directory of the first glob that matched it, without the
extension. Two files with one name are refused, naming both. A value that is not
an array of non-empty strings is an error; any other key in `[test]` is a
warning, and an error under `--strict`. [08 — Testing](08-testing.md) describes
the test model.


## 3. Worked Examples

Expand Down Expand Up @@ -1495,7 +1524,7 @@ kind = "bin"
| C standard | `c11` | `.c` files go through the C compiler automatically |
| Static stdlib | `true` | Portable binary |
| Headers | `include/` (if present) | Added to `-I` automatically |
| Tests | `tests/**/*.cpp` | Discovered automatically by `mcpp test` |
| Tests | `tests/**/*.cpp` | Discovered automatically by `mcpp test`; `[test] discover` replaces the set |
| Dependency namespace | `mcpplibs` (default) | A bare selector means only this exact namespace |

### 4.1 Legacy `[language]` Compatibility Layer
Expand Down
Loading
Loading