Skip to content

Commit a965a05

Browse files
The action lists have no size limit, and ${mcpp.self} names the engine (2026.9.13.1) (#629)
* build.mcpp: an action's lists have no declared size limit The bundled mcpp module held an action's six list fields in fixed arrays (8192 bytes of serialised JSON for inputs and outputs, 16384 for command) and refused a declaration that did not fit. The bound was in bytes, so a consumer's checkout depth decided whether a list of 44 resource files was accepted (HuxerUI#130 measured the margin at 45 bytes), and outputs is the one list an author cannot shorten: an output the program does not name cannot be built, and there is no depfile for outputs. The arrays were fixed because one constraint was read as two. The module must not import std, and its exported interface must name no std type; neither forbids the heap, and <cstdlib> was already in the global module fragment. The six arrays become one growable std-free buffer over realloc. The exported surface and the protocol version are unchanged, and the payload of every action that fit before is byte-identical, so the cache key is untouched. The overflow marker keeps its wire form and now means allocation failure; the engine's message says so. e2e 659 declares 200 inputs and 200 outputs and a 19200-byte command and reads the action's edge out of build.ninja; under 2026.9.12.4 the same fixture is refused. * build.mcpp: ${mcpp.self} names the engine, and mcpp stage is the portable copy An action's command is an argv with no shell, so a build program had no portable way to copy a file: cp is absent on Windows, cmd /c copy is a shell and an 8191-character limit, and a copier carried by a package is a host-tool sub-build for one copy. The engine is the one program present wherever a build runs, and mcpp stage --verify content --output <dst> <src> is the copy every stage_file edge already performs. ${mcpp.self} joins the argv substitution family and is replaced by the engine's absolute path, as the check wrapper already bakes it in. mcpp stage's argument shape is a contract from here on; its help text now states the real default (content). e2e 660 copies the linked program through the token on every shard; under 2026.9.12.4 the token stays literal and the edge fails. * docs: the action lists' limit, the ${mcpp.self} row, and the design record for the four upstream asks * chore: bump version to 2026.9.13.1 * ninja: the command-length guard measures a literal command, not its edge line check_inline_command_lengths read each build line as a proxy for the command. That is right for a rule that expands $in and $out and wrong for an action rule, whose command is a literal argv and whose inputs and outputs exist only so that ninja can order and re-run it. e2e 659's first run on the Windows shard refused an action with 200 outputs and 200 inputs with the whole edge line counted as argv. The guard now measures a literal command's own text. The refusal names the edge by its first output and the count of the rest, instead of printing every output. e2e 659 declares 600 inputs and 600 outputs, so the edge line crosses the POSIX 128 KiB limit as well as Windows's 32767 and the guard is measured on every shard; its negative leg declares a 140800-byte literal command and expects the refusal to name the edge. * ci(openkal-cross): say what the Windows host provides for the mingw target The Windows job of this workflow was green with one sandbox lineage and red with the next on the same sources and the same runner image (PR #629: lld could not find -lntdll and its neighbours). On a Windows host openkal-windows's build program generates no import libraries, so where lld finds them is a property of the host, the sandbox or the payload, and nothing in the job said which. This step prints it. --------- Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>
1 parent baa89d2 commit a965a05

16 files changed

Lines changed: 1155 additions & 56 deletions

.agents/docs/2026-09-13-four-upstream-asks-from-a-ui-framework.md

Lines changed: 598 additions & 0 deletions
Large diffs are not rendered by default.

.agents/docs/README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,16 @@ superseded_by: 2026-09-07-....md # when status is superseded
1818
---
1919
```
2020

21-
282 records.
21+
283 records.
2222

2323
## By subject
2424

2525
Records that declare one. Everything else is listed by date below.
2626

27+
### build-program
28+
29+
- [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) — active
30+
2731
### docs
2832

2933
- [The documentation as a book: a chapter-by-chapter design](2026-09-08-the-documentation-as-a-book.md) — active
@@ -61,6 +65,7 @@ Records that declare one. Everything else is listed by date below.
6165

6266
### 2026-09
6367

68+
- [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) — active
6469
- [The engine gaps left open after the SDK batch](2026-09-12-engine-gaps-after-the-sdk-batch.md) — landed
6570
- [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
6671
- [Implementation plan: a UI framework on Android, iOS and Web (#622)](2026-09-12-622-implementation-plan.md) — landed

.github/workflows/openkal-cross.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,27 @@ jobs:
187187
"$MCPP_UNDER_TEST" toolchain install llvm 22.1.8
188188
"$MCPP_UNDER_TEST" toolchain default 'llvm@22.1.8'
189189
190+
# WHAT THIS RUNNER ACTUALLY PROVIDES FOR THE mingw TARGET. On a Windows
191+
# host `openkal-windows`'s build program generates no import libraries
192+
# ("the system's own are present"), so `-lntdll` and its neighbours are
193+
# found by lld only where the host, the sandbox or the payload puts
194+
# them. This job was green with one sandbox lineage and red with the
195+
# next (2026-09-13, PR #629, same sources, same image), which is the
196+
# signature of a dependency on cached state nobody declared. The lines
197+
# below say where the libraries come from, so the next such reading is
198+
# diagnosed from the log rather than from a bisect over caches.
199+
- name: What this host provides for x86_64-w64-windows-gnu
200+
if: matrix.host == 'windows'
201+
run: |
202+
echo "PATH=$PATH" | tr ':' '\n' | head -40
203+
ls "${MCPP_HOME:-$HOME/.mcpp}/registry/data/xpkgs" 2>/dev/null || echo "(no xpkgs dir)"
204+
CLANG=$(ls "${MCPP_HOME:-$HOME/.mcpp}"/registry/data/xpkgs/xim-x-llvm/22.1.8/bin/clang++.exe 2>/dev/null | head -1)
205+
echo "clang=$CLANG"
206+
[ -n "$CLANG" ] && "$CLANG" --target=x86_64-w64-windows-gnu -print-search-dirs
207+
[ -n "$CLANG" ] && "$CLANG" --target=x86_64-w64-windows-gnu -print-file-name=libntdll.a
208+
[ -n "$CLANG" ] && "$CLANG" --target=x86_64-w64-windows-gnu -print-file-name=libkernel32.a
209+
command -v x86_64-w64-mingw32-gcc gcc 2>/dev/null || true
210+
190211
- name: The program — one source, three targets
191212
run: |
192213
set -euo pipefail

CHANGELOG.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,35 @@
55

66
## [Unreleased]
77

8+
### `mcpp::action` 的列表不再有长度上限;`${mcpp.self}` 让 action 叫出引擎自己
9+
10+
内置的 `mcpp` 模块此前把一个 action 的 `inputs`、`outputs`、`command` 等六个列表
11+
放在定长数组里(`inputs` 与 `outputs` 各 8192 字节的序列化 JSON),放不下的声明
12+
被拒绝为「arguments did not fit」。上限按字节计,于是一个消费者的 checkout 深度
13+
决定了 44 个资源文件的列表能否通过(HuxerUI#130 量得的余量是 45 字节);而
14+
`outputs` 是作者无法缩短的那一个列表:没有点名的产物无法构建,也不存在面向
15+
outputs 的 depfile。六个数组换成模块内部基于 `realloc` 增长的 std-free 缓冲,
16+
导出接口、协议版本与原本放得下的 action 的序列化字节全部不变(缓存键因此不变)。
17+
`"overflow":true` 标记保留,含义改为分配失败,引擎侧消息随之重写。运行期的命令
18+
仍受操作系统 argv 上限约束,那是工具自己命令行的事。
19+
20+
`${mcpp.self}` 加入 action argv 的替换家族,替换为引擎自己的绝对路径。action 的
21+
命令是没有 shell 的 argv,构建程序此前没有可移植的拷贝手段;`${mcpp.self} stage
22+
--verify content --output <dst> <src>` 即每条 `stage_file` 边已在执行的那次拷贝。
23+
`mcpp stage` 的参数形状自此成为契约,其帮助文本改为陈述真实的默认值(content)。
24+
25+
顺带修正引擎的命令长度守卫:它此前把每条 `build` 行读成命令的代理,这对展开
26+
`$in`/`$out` 的规则成立,对命令是字面 argv 的 action 规则不成立 —— 一个 200 个
27+
输出的 action 在 Windows 分片上被整条边行当 argv 计数而拒绝。守卫现在对字面命令
28+
量它自己的文本。
29+
30+
- 判据:`tests/e2e/659`(600 个输入与 600 个输出的 action 整体进入 build.ninja,
31+
边行越过每个宿主的 argv 上限而不被守卫误拒,缓存回放后仍完整;在 2026.9.12.4
32+
上同一夹具被拒绝),`tests/e2e/660`
33+
(`${mcpp.self}` 在每个分片上完成一次拷贝,空转重建不再拷贝;在 2026.9.12.4
34+
上 token 原样落入 build.ninja)。
35+
- 设计记录:`.agents/docs/2026-09-13-four-upstream-asks-from-a-ui-framework.md`。
36+
837
### wasm 产物契约:启动器改名为 `.js`,`.wasm` 是隐式输出(#622 A5)
938

1039
`wasm32-emscripten` 行此前用的是宿主借来的裸名 —— `bin/<name>`(Linux 宿主)或

docs/30-build-mcpp.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,20 @@ and the module graph during prepare, so an output whose *name* is unknown
553553
cannot be built. Content may arrive later; names may not. A malformed action is
554554
a hard error, never a silent skip.
555555

556+
**The lists have no declared size limit** (2026.9.13.1+). `inputs`, `outputs`
557+
and `command` grow with what is declared; a generated tree of two hundred files
558+
is two hundred `output` calls. Until 2026.9.13.1 the bundled module held each
559+
list in a fixed array (8192 bytes of serialised JSON for `inputs` and
560+
`outputs`) and refused a declaration that did not fit, so a consumer's
561+
checkout depth decided whether a list of forty files was accepted. What
562+
remains bounded is the **command at run time**, by the operating system's
563+
limit on a process's arguments (128 KiB per argument on Linux, 32767
564+
characters for a Windows `CreateProcess`); that is a limit on the tool's own
565+
command line, and a tool that takes hundreds of files takes them through a
566+
response file or a directory argument of its own. The engine's own guard
567+
against it measures the command, not the edge: an action's inputs and
568+
outputs are graph edges, never argv.
569+
556570
For a generated **module interface**, declare its interface too:
557571

558572
```cpp
@@ -705,6 +719,7 @@ none to rely on), and the only interpolations are a closed set:
705719
| `${mcpp.compile_db}` | path to `compile_commands.json` (what clang-tidy's `-p` wants) |
706720
| `${mcpp.target_file:<name>}` | the built file of target `<name>` |
707721
| `${mcpp.stage_dir}` *(2026.9.11.1+)* | the tree `mcpp pack` staged, absolute. `artifact` role only, and only under `mcpp pack --format <name>` |
722+
| `${mcpp.self}` *(2026.9.13.1+)* | the engine's own executable, absolute. An action's command is an argv with no shell, so a build program has no portable way to copy a file; the engine is present wherever a build runs, and `${mcpp.self} stage --verify content --output <dst> <src>` copies one file, creates the destination's parent, and writes only when the bytes differ. That argument shape is a contract from 2026.9.13.1 on; a build program that names it declares that release as its floor |
708723

709724
The raw stdout protocol above remains the low-level substrate; `import mcpp;`
710725
is the typed layer over it.

docs/zh/30-build-mcpp.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,16 @@ mcpp 为那条边写出 `depfile =` 与 `deps = gcc`,ninja 读取该文件并把
470470
所以名字未知的产物无法构建。内容可以晚到,名字不行。畸形 action 是**硬错误**,
471471
绝不静默跳过。
472472

473+
**列表没有声明上的长度上限**(2026.9.13.1+)。`inputs``outputs``command`
474+
随声明增长;一棵两百个文件的生成树就是两百次 `output` 调用。2026.9.13.1 之前,
475+
内置模块把每个列表放在定长数组里(`inputs``outputs` 各 8192 字节的序列化
476+
JSON),放不下的声明会被拒绝,于是一个消费者的 checkout 深度决定了四十个文件的
477+
列表能否被接受。仍然有上限的是**运行期的命令**,由操作系统对进程参数的限制决定
478+
(Linux 上每个参数 128 KiB,Windows 的 `CreateProcess` 是 32767 个字符);那是
479+
工具自己命令行的上限,一个要接收几百个文件的工具用它自己的 response file 或目录
480+
参数来接收。引擎自己针对它的守卫量的是命令而不是边:action 的输入与输出是图上的
481+
边,从不进入 argv。
482+
473483
生成**模块接口**时,把它的接口也声明出来:
474484

475485
```cpp
@@ -606,6 +616,7 @@ mcpp 会写出 `<暂存树>.stage-manifest` —— 一个兄弟文件,永不是
606616
| `${mcpp.compile_db}` | `compile_commands.json` 的路径(clang-tidy 的 `-p` 要的就是它) |
607617
| `${mcpp.target_file:<name>}` | target `<name>` 构建出的文件 |
608618
| `${mcpp.stage_dir}` *(2026.9.11.1+)* | `mcpp pack` 暂存出的那棵树,绝对路径。仅 `artifact` role 可用,且仅在 `mcpp pack --format <name>` 下可用 |
619+
| `${mcpp.self}` *(2026.9.13.1+)* | 引擎自己的可执行文件,绝对路径。action 的命令是没有 shell 的 argv,构建程序因此没有可移植的拷贝手段;引擎在构建运行的每台机器上都在,`${mcpp.self} stage --verify content --output <dst> <src>` 拷贝一个文件、创建目标的父目录、只在字节不同时写入。这个参数形状自 2026.9.13.1 起是契约;写下它的构建程序即以该版本为下限 |
609620

610621
上面的裸 stdout 协议仍是底层基底;`import mcpp;` 是其上的类型化层。
611622

mcpp.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mcpp"
3-
version = "2026.9.12.4"
3+
version = "2026.9.13.1"
44
description = "Modern C++ build & package management tool"
55
license = "Apache-2.0"
66
authors = ["mcpp-community"]

modules/buildmcpp/src/directives.cppm

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,20 +1088,21 @@ std::string deploy_directive_error(const mcpp::manifest::Manifest& m, const Dire
10881088

10891089
std::string action_error(const Directives& d) {
10901090
for (auto const& payload : d.at(Slot::Actions)) {
1091-
// The typed API sets this when an argv did not fit its fixed buffer.
1092-
// Diagnosed separately because "malformed action" would send the
1093-
// author looking for a typo in something that was actually correct
1094-
// and merely too long.
1091+
// The typed API sets this when the build program could not allocate
1092+
// memory for one of the action's lists. Diagnosed separately because
1093+
// "malformed action" would send the author looking for a typo in a
1094+
// declaration that was correct and merely cut short. The lists have
1095+
// no declared size limit (they had one, 8192 bytes of serialised
1096+
// JSON, until 2026.9.13.1); the OS bounds the COMMAND's argv at run
1097+
// time, and that is a limit of the tool's own command line, which a
1098+
// response file or a directory argument shortens.
10951099
if (payload.find("\"overflow\":true") != std::string::npos) {
10961100
return std::format(
1097-
"build.mcpp declared an action whose arguments did not fit.\n"
1098-
" The typed `mcpp::action` builder uses fixed buffers "
1099-
"(the bundled module has to stay\n"
1100-
" buildable before a std module exists, so it cannot use "
1101-
"std::string).\n"
1102-
" Shorten the command — e.g. pass a response file, or a "
1103-
"directory instead of\n"
1104-
" enumerating its files.\n"
1101+
"build.mcpp declared an action whose lists could not be stored.\n"
1102+
" The build program ran out of memory while collecting the "
1103+
"action's inputs,\n"
1104+
" outputs or command, so the declaration is incomplete "
1105+
"and cannot be used.\n"
11051106
" payload: {}", payload);
11061107
}
11071108
if (decode_action(payload)) continue;

modules/versioning/src/version.cppm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,6 @@ import std;
3131

3232
export namespace mcpp {
3333

34-
inline constexpr std::string_view MCPP_VERSION = "2026.9.12.4";
34+
inline constexpr std::string_view MCPP_VERSION = "2026.9.13.1";
3535

3636
} // namespace mcpp

0 commit comments

Comments
 (0)