Skip to content

Commit 1bc6074

Browse files
feat: 源文件角色表 与 build.mcpp 运行上限 —— 两个硬编码变成两条声明 (#272, #410) (#411)
* feat: 源文件角色表 与 build.mcpp 运行上限 —— 两个硬编码变成两条声明 (#272, #410) (2026.8.11.1) 把「哪个扩展名是模块接口」和「build.mcpp 能跑多久」这两个决策从代码里拿出来, 变成 mcpp.toml 的两条声明,并顺手把它们背后的架构债与跨平台缺口补上。 新增 [build] module_extensions:additive 到内置 .cppm。声明一个扩展名会同时 让默认 sources glob 找到它、让它走模块规则(产 BMI、.o 无条件进链接)、并让 新鲜度快路径扫描它 —— 一个键而不是三处配置。拒绝已代表其他角色的扩展名。 新增 [build] build_program_timeout:优先级 env > 该包自己的 manifest > 内置 600s。超时报错点名要改的那份 mcpp.toml —— 依赖超时时改自己的那份不会有效果。 optional 承重:int 的话「没写」与「写 0」不可区分,而 0 意为不限。 架构:「扩展名 → 角色」原本在 9 个文件 20 处推导、8 份互不一致的清单。现在 分类只发生一次(SourceUnit::kind → CompileUnit::kind),下游读字段。#272 修了 链接侧却漏了 pick_rule —— 边上声明 BMI 而命令行丢了 -fmodule-output=。 实测(GCC 16.1 / Clang 22.1):Clang 根本不认 .ixx,把它当链接输入、退出码 0、 不产 BMI。而显式旗标在已识别后缀上幂等(Clang .cppm 的 BMI 逐字节相同)。 ⇒ 不维护「谁认哪个后缀」这张会过期且错了静默的表,永远显式告诉编译器。 跨平台:capture_exec_deadline 此前只在 POSIX 生效,Windows 直接回落无界路径, 于是 mcpp test --timeout / --build-timeout / 这个新键在那里全是空操作。现在 两侧各有实现(Windows 用 Job object 杀整棵树,否则孙进程攥着捕获管道会让 杀掉之后的读取挂住),process.cppm 单点 if constexpr 分派。 src/platform/ 拆成 unix/ windows/ linux/ macos/。 可观察性:mcpp self doctor 报告生效的扩展名表、超时值及其来源、deadline 是否 真的强制;module_extensions 零命中的条目告警。 顺带修掉三个发现的缺陷:isModuleInterface/isImplementation 是写而不读的死字段 (5 写 0 读、3 份不一致推导)⇒ 删除;is_implementation_source 漏 .mm 导致 Objective-C++ 对象永不进链接;stage 兜底 glob 漏全部三种汇编扩展名。 兼容:未配置时构建图零差分(同样的文件、BMI、链接对象、指纹目录)。 module_extensions 进指纹(改图形态),build_program_timeout 不进(不改任何边)。 设计:.agents/docs/2026-08-11-source-kind-table-and-build-program-timeout.md * fix(platform): 未捕获的有界运行必须继承 stdio,而不是先缓冲后回放 自审发现的真回归。`run_exec_deadline` 是 `mcpp test` 非 JSON 模式跑测试二进制 的路径,原本继承调用方的 stdio —— 输出实时出现,且子进程的 stdout 是终端。 把它改成「捕获后在结束时一次性回放」有两个后果: 1. 长测试的输出全部憋到退出才出现,恰好抵消了 `mcpp test` 可观察性那一整 轮工作(「只有子进程输出、mcpp 一行没有」正是缓冲问题的指纹); 2. 子进程的 stdout 变成管道而非终端,gtest 之类会静默关掉彩色输出。 两侧启动器现在共用一条契约:`sink == nullptr` 表示「不捕获」,子进程直接继承 调用方的 stdio,但**仍然有界**。POSIX 侧不建管道也不设 dup2 file action; Windows 侧不设 STARTF_USESTDHANDLES,也不加 CREATE_NO_WINDOW(未捕获的运行 本来就是要给人看的)。`dispatch_bounded` 多一个 capture 形参把这个选择传下去。 * fix(cache): module_extensions 进依赖缓存键的 E 轴 指纹管的是 target/<triple>/<fp>/,全局依赖缓存是另一套键。一个声明了 module_extensions 的依赖产出不同的 .o/BMI,它的缓存键必须体现这一点。 今天不可达 —— 默认 glob 会跟着变,sourceGlobs 已经动了;索引包描述符按版本 冻结,version 在 D 轴。但这正是本 PR 在消灭的形状(同一决策漏一处),一行补上 比留着等它以后变成一次错误的缓存命中便宜。 不 bump epoch:老条目命令行里没有 -x c++,而该旗标在已识别后缀上幂等(产物逐 字节相同),沿用安全,不必让全网缓存作废。 * fix(platform/windows): 捕获路径必须把 stdin 封成 NUL 自审发现。被替换掉的 Windows 捕获路径经 _popen 走 shell,命令行尾部带 '< NUL' —— 这个文件的同伴 mcpp.platform.process 头注释就点名了原因:xlings / xim / curl / git 子进程在 bootstrap 期间阻塞在终端 stdin 上,逼用户反复敲回车。 新实现把父进程的 console stdin 直接透传给了被捕获的子进程,会静默把那个挂起 带回来。改为从 NUL 打开;打不开时退回 console 句柄而不是交一个无效句柄 ——「完全没有 stdin」的失败长得一点也不像「stdin 没被封」。 未捕获的子进程保持继承真实 stdin,与 run_exec 一致:mcpp run 就是要把终端交 给程序。 * refactor(prepare): 扩展名表按包建一次,不再每个生成物重建一次 * fix(manifest): 自动推断的 lib 备注要说出实际找到的那个扩展名 macOS e2e 抓到的:我把备注从「lib from .cppm in src/」改成了「lib from module interface in src/」,25_convention_mode.sh 断言的是前者。 这条我判断为文案改得不够好,而不是测试过时。备注该说的是**实际找到了什么**: .cppm 工程输出与从前逐字不变(那条断言原样通过),而声明了 module_extensions 的工程会看到「lib from .ixx in src/」—— 说 .cppm 才是名不副实。 顺带 CHANGELOG 补 2026.8.11.1 条目。 * fix(e2e): 218 的指纹断言不能靠 find|head -1 推目录 自审 + 全量套件抓到的:第 2 部分之后 target/ 下有两个输出目录, `find target -name build.ninja | head -1` 取到的是 find 恰好先走到的那个 —— 单跑绿、进套件红。改成直接问 mcpp 要指纹值(--print-fingerprint), 断言那个值本身,而不是一个目录列举的副作用。 (这正是「指纹目录随版本变,ls|head -1 会自查到旧产物」那条老坑的同一形状, 在一个专门用来抓这类问题的测试里又踩了一次。) * docs: 补实施记录 —— CI 19/19、本机 12 条失败的逐条对照结论、以及我在自己测试里重踩 head -1 的教训 --------- Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>
1 parent e53204a commit 1bc6074

51 files changed

Lines changed: 3705 additions & 361 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/docs/2026-08-11-source-kind-table-and-build-program-timeout.md

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

.github/actions/bootstrap-mcpp/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ inputs:
2525
# `package.name`, so one of the two was simply unreachable — and which one
2626
# depended on the machine, which is why CI failed on `compat:lua` on
2727
# Windows and `mcpplibs.capi:lua` on Linux. Never pin below that.
28-
default: '2026.8.10.4'
28+
default: '2026.8.11.1'
2929
cache-target:
3030
description: also restore/save target/ (build artifacts + BMIs)
3131
required: false

.github/actions/setup-macos-llvm/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ inputs:
1515
# Floor imposed by the index, not a routine bump — see
1616
# .github/actions/bootstrap-mcpp/action.yml for why 0.4.69 is required
1717
# (two packages named `lua` in one repo need openxlings/xlings#381).
18-
default: '2026.8.10.4'
18+
default: '2026.8.11.1'
1919

2020
runs:
2121
using: composite

.github/workflows/bootstrap-macos.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
# Dormant (workflow_dispatch only), but kept in step with the rest —
1818
# check_version_pins.sh holds it there. Floor: 0.4.69, below which the
1919
# index cannot resolve two packages that share a short name.
20-
XLINGS_VERSION: '2026.8.10.4'
20+
XLINGS_VERSION: '2026.8.11.1'
2121
steps:
2222
- uses: actions/checkout@v4
2323

.github/workflows/ci-fresh-install.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ jobs:
152152
env:
153153
XLINGS_NON_INTERACTIVE: '1'
154154
run: |
155-
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.10.4
155+
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.11.1
156156
echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH"
157157
158158
- name: Install mcpp and config mirror
@@ -293,7 +293,7 @@ jobs:
293293

294294
- name: Install xlings + mcpp
295295
run: |
296-
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.10.4
296+
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.11.1
297297
# Deliberately NOT writing to $GITHUB_PATH here. On container
298298
# images that declare no PATH in their config (opensuse/
299299
# tumbleweed), appending a single dir to GITHUB_PATH makes the
@@ -364,7 +364,7 @@ jobs:
364364
# (older ones carry minos=15 and refuse to start).
365365
# v0.4.51+: in-process sha256 — this image has no sha256sum
366366
# binary, so pinned fetches failed before it.
367-
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.10.4
367+
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.11.1
368368
echo "$HOME/.xlings/subos/current/bin" >> "$GITHUB_PATH"
369369
370370
- name: Install mcpp and config mirror

.github/workflows/ci-linux-e2e.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ jobs:
133133
134134
- name: Bootstrap xlings + released mcpp
135135
run: |
136-
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.10.4
136+
curl -fsSL https://raw.githubusercontent.com/openxlings/xlings/main/tools/other/quick_install.sh | bash -s v2026.8.11.1
137137
export PATH="$HOME/.xlings/subos/current/bin:$PATH"
138138
xlings update
139139
xlings install mcpp -y -g

.github/workflows/cross-build-test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ jobs:
118118
# release assets were uploaded in a broken state (records present,
119119
# blobs missing → 404 on GET); re-uploaded clean. The stale-INDEX
120120
# half is handled by the marker-clear below.
121-
XLINGS_VERSION: '2026.8.10.4'
121+
XLINGS_VERSION: '2026.8.11.1'
122122
run: |
123123
tarball="xlings-${XLINGS_VERSION}-linux-x86_64.tar.gz"
124124
curl -fsSL -o "/tmp/${tarball}" \
@@ -255,7 +255,7 @@ jobs:
255255
- name: Bootstrap mcpp via xlings
256256
env:
257257
XLINGS_NON_INTERACTIVE: '1'
258-
XLINGS_VERSION: '2026.8.10.4'
258+
XLINGS_VERSION: '2026.8.11.1'
259259
run: |
260260
tarball="xlings-${XLINGS_VERSION}-linux-x86_64.tar.gz"
261261
curl -fsSL -o "/tmp/${tarball}" \

.github/workflows/release.yml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ jobs:
9696
# Pin xlings to a known-good version. The upstream install
9797
# script always grabs `latest` (no version override), so we
9898
# download + self-install manually to avoid broken releases.
99-
XLINGS_VERSION: '2026.8.10.4'
99+
XLINGS_VERSION: '2026.8.11.1'
100100
run: |
101101
if [ ! -x "$HOME/.xlings/subos/default/bin/xlings" ]; then
102102
tarball="xlings-${XLINGS_VERSION}-linux-x86_64.tar.gz"
@@ -288,7 +288,7 @@ jobs:
288288
- name: Bootstrap mcpp via xlings
289289
env:
290290
XLINGS_NON_INTERACTIVE: '1'
291-
XLINGS_VERSION: '2026.8.10.4'
291+
XLINGS_VERSION: '2026.8.11.1'
292292
run: |
293293
tarball="xlings-${XLINGS_VERSION}-linux-x86_64.tar.gz"
294294
curl -fsSL -o "/tmp/${tarball}" \
@@ -358,11 +358,11 @@ jobs:
358358
# below are pinned to the same version as XLINGS_VERSION; they are
359359
# NOT interpolated from it, so check_version_pins.sh scans for them
360360
# explicitly (they were absent from the old lock-step comment).
361-
XLA="xlings-2026.8.10.4-linux-aarch64.tar.gz"
361+
XLA="xlings-2026.8.11.1-linux-aarch64.tar.gz"
362362
if curl -fsSL -o "/tmp/$XLA" \
363-
"https://github.com/openxlings/xlings/releases/download/v2026.8.10.4/$XLA"; then
363+
"https://github.com/openxlings/xlings/releases/download/v2026.8.11.1/$XLA"; then
364364
tar -xzf "/tmp/$XLA" -C /tmp
365-
XLBIN=$(find /tmp/xlings-2026.8.10.4-linux-aarch64 -path '*/bin/xlings' -type f | head -1)
365+
XLBIN=$(find /tmp/xlings-2026.8.11.1-linux-aarch64 -path '*/bin/xlings' -type f | head -1)
366366
if [ -n "$XLBIN" ]; then
367367
mkdir -p "$STAGING/$WRAPPER/registry/bin"
368368
cp "$XLBIN" "$STAGING/$WRAPPER/registry/bin/xlings"
@@ -440,7 +440,7 @@ jobs:
440440
- name: Bootstrap mcpp via xlings
441441
env:
442442
XLINGS_NON_INTERACTIVE: '1'
443-
XLINGS_VERSION: '2026.8.10.4'
443+
XLINGS_VERSION: '2026.8.11.1'
444444
run: |
445445
if [ ! -x "$HOME/.xlings/subos/default/bin/xlings" ]; then
446446
WORK=$(mktemp -d)
@@ -622,7 +622,7 @@ jobs:
622622
shell: bash
623623
env:
624624
XLINGS_NON_INTERACTIVE: '1'
625-
XLINGS_VERSION: '2026.8.10.4'
625+
XLINGS_VERSION: '2026.8.11.1'
626626
run: |
627627
# Captured before the `cd` below, in POSIX form: this step never
628628
# returns to the workspace, and GITHUB_WORKSPACE is a backslash

CHANGELOG.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,85 @@
33
> 本文件追踪 `mcpp-community/mcpp` 公开仓的版本演进。
44
> 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)
55
6+
## [2026.8.11.1] — 2026-08-11
7+
8+
### 新增
9+
10+
- **`[build] module_extensions` —— 哪些扩展名是模块接口,由工程声明。**
11+
12+
```toml
13+
[build]
14+
module_extensions = [".ixx", ".ccm"]
15+
```
16+
17+
追加到内置的 `.cppm`。声明一个扩展名会**同时**做三件事:默认 sources glob 跟着
18+
变宽(文件才能被**找到**)、这些单元走**模块**规则(产 BMI、`.o` 无条件进链接)、
19+
新鲜度快路径**扫描**它们(加 `import` 会让构建图作废)。一个键而不是三处配置。
20+
21+
任何扩展名都接受,唯独拒绝已代表其他角色的(`.cpp` `.c` `.h` `.S` …)——
22+
manifest **错误**而非警告,因为宣称 `.c` 是模块接口会把 C 文件送进 C++ 模块规则,
23+
最终失败在一个既不提文件也不提这个键的地方。
24+
25+
⚠️ `.ccm`/`.cxxm`/`.ixx` **不进内置默认**。进了的话默认 glob 会跟着变宽,
26+
于是 `src/` 下躺着 vendored MSVC-only `.ixx`**已发布包会在一次 mcpp 升级后
27+
突然开始编译它** —— 而包作者改不了已经发出去的 tarball。
28+
29+
- **`[build] build_program_timeout` —— `build.mcpp` 的运行上限可配置。**
30+
31+
优先级 `MCPP_BUILD_PROGRAM_TIMEOUT` > **该包自己的** manifest > 内置 600s,
32+
`macos_deployment_target` 同构。超时报错**点名要改的那份 `mcpp.toml`** ——
33+
依赖超时时改自己的那份不会有任何效果,这正是 [#410](https://github.com/mcpp-community/mcpp/issues/410)
34+
从外面看到的样子。不写这个键与写 `0` 不是一回事:不写=用默认上限,`0`=不设上限。
35+
36+
- **`mcpp self doctor` 报告构建策略。** 生效的模块接口扩展名表、生效的超时值
37+
**及其来源**、以及本平台的 deadline 是否**真的**强制执行。
38+
另外 `module_extensions` 里零命中的条目会告警 —— 否则打字错误(`.ixxx`)与
39+
「这个工程还没有」无法区分。
40+
41+
### 修复
42+
43+
- **超时上限在 Windows 上从来就是空操作。** `capture_exec_deadline` 只在 POSIX
44+
生效,其余平台直接回落到无界启动器 —— 于是 `mcpp test --timeout`
45+
`--build-timeout`、以及这个新键在 Windows 上**设了等于没设**。现在两侧各有实现:
46+
Windows 把子进程放进 **Job 对象**并在到期时关闭它,杀掉的是**整棵进程树**而不只是
47+
直接子进程(否则一个还攥着捕获管道的孙进程会让杀掉之后的读取一直挂住)。
48+
49+
- **`.mm`(Objective-C++)的对象编了但永远不进链接** —— `is_implementation_source`
50+
的清单漏了它。
51+
52+
- **stage 一个含汇编的依赖会静默丢掉那些源文件** —— 兜底 glob 漏了全部三种汇编扩展名。
53+
54+
### 架构
55+
56+
- **「扩展名 → 角色」此前在 9 个文件 20 处推导,分成 8 份互不一致的清单**
57+
(三份「什么算实现单元」、四份「什么算源文件」)。
58+
[#272](https://github.com/mcpp-community/mcpp/pull/272) 修了链接侧,却漏了
59+
`pick_rule` —— 边上**声明**了 BMI 产物(那行读 `providesModule`),命令行却丢了
60+
`-fmodule-output=`
61+
62+
收敛的形状不是「大家都调同一个函数」,而是**分类只发生一次**(文件进图时),
63+
之后当数据传递(`SourceUnit::kind``CompileUnit::kind`)。扫描器手里本来就有
64+
所属包的 manifest,所以这一步没有新增任何管道。
65+
66+
- **mcpp 现在每次都显式告诉编译器某个单元是模块接口**(`-x c++` / `-x c++-module` /
67+
`/interface /TP`),而不是去维护「哪个驱动认哪个后缀」。实测(GCC 16.1 / Clang 22.1):
68+
**Clang 根本不认 `.ixx`** —— 把它当链接输入、警告、**退出码 0 且不产 BMI**;
69+
而显式旗标在已识别后缀上**幂等**(Clang 的 `.cppm` BMI 逐字节相同)。
70+
一张会过期、错了还静默的表不该存在。
71+
72+
- `src/platform/` 拆成 `unix/ windows/ linux/ macos/`;`mcpp.platform.process`
73+
对有界运行**单点 `if constexpr` 分派**,取代此前散落的平台分支。
74+
75+
### 兼容性
76+
77+
- 未配置时**构建图零差分**:同样的文件被编、同样的 BMI、同样的对象进链接、
78+
同样的指纹目录。
79+
- `module_extensions` ****指纹(它改图的形态);`build_program_timeout` **不进**
80+
(它不改任何一条边 —— 进了会让「抬高超时」重建全世界)。
81+
- ⚠️ 旧版 mcpp 遇到 `module_extensions` 会警告+忽略,然后把那些文件当普通翻译单元
82+
编译 —— **错误的构建**而不是干净的失败。发布用了这个键的包必须声明 mcpp 版本下限
83+
(见 `docs/10-publishing-a-library.md`)。
84+
685
## [2026.8.10.3] — 2026-08-10
786

887
### 修复

docs/05-mcpp-toml.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ the package/feature boundary, not on an individual target.
156156
```toml
157157
[build]
158158
sources = ["src/**/*.cppm", "src/**/*.cpp"] # Source globs (default: src/**/*.{cppm,cpp,cc,c,S,s,asm})
159+
module_extensions = [".ixx"] # Extra extensions your module INTERFACES use (§ below)
160+
build_program_timeout = 1800 # Seconds a build.mcpp may run; 0 = no limit (§ below)
159161
include_dirs = ["include", "third_party/include"] # Header search paths
160162
include_dirs_after = ["*"] # Header dirs searched AFTER system dirs (-idirafter)
161163
c_standard = "c11" # Standard for C source files (default c11)
@@ -194,6 +196,83 @@ baseline, and 14.0 is the floor of LLVM's official static libraries themselves).
194196
This value enters the BMI fingerprint, so switching targets automatically rebuilds
195197
the module cache.
196198

199+
### Module interface extensions (`module_extensions`)
200+
201+
mcpp treats `.cppm` as a module interface unit. The C++ ecosystem has not
202+
converged on one spelling — Clang also recognizes `.ccm` and `.cxxm`, MSVC uses
203+
`.ixx` — so a project whose interfaces use another extension declares it:
204+
205+
```toml
206+
[build]
207+
module_extensions = [".ixx", ".ccm"]
208+
```
209+
210+
The list is **additive**: `.cppm` is always a module interface and cannot be
211+
removed. To stop a particular file from being built, `!`-exclude it in
212+
`sources`; that is what `sources` is for.
213+
214+
Declaring an extension does three things at once, which is the point of having
215+
one key rather than several:
216+
217+
1. the convention default for `sources` grows to match, so the files are
218+
**found** (`src/**/*.ixx` joins the default glob);
219+
2. those units compile with the **module** rule — they emit a BMI and their
220+
objects are linked unconditionally;
221+
3. the freshness fast path watches them, so adding an `import` to one
222+
invalidates the build graph instead of silently reusing a stale one.
223+
224+
Any extension is accepted **except** ones that already name a non-module role
225+
(`.cpp` `.cc` `.cxx` `.c` `.m` `.mm` `.h` `.hpp` `.hh` `.hxx` `.S` `.s`
226+
`.asm`); claiming one of those is a manifest error rather than a warning,
227+
because it would route (say) C files to the C++ module rule and fail somewhere
228+
that names neither the file nor this key.
229+
230+
Extensions are matched **literally, without case folding**`.S` and `.s` are
231+
different languages in this domain, so case is never ignored.
232+
233+
mcpp always tells the compiler explicitly that a module interface unit is one
234+
(`-x c++-module` on Clang, `-x c++` on GCC, `/interface /TP` on MSVC), so an
235+
extension the compiler driver has never heard of works anyway. This is why any
236+
extension is allowed: mcpp does not need the compiler to recognize it.
237+
238+
> **Publishing note.** An older mcpp does not know this key: it warns, ignores
239+
> it, and then compiles those files as ordinary translation units — a wrong
240+
> build rather than a clean failure. If you publish a package that uses
241+
> `module_extensions`, declare an mcpp version floor in its index descriptor.
242+
243+
### Build-program timeout (`build_program_timeout`)
244+
245+
A `build.mcpp` gets **600 seconds** by default, after which mcpp kills it and
246+
fails the build naming the package. A project whose build program legitimately
247+
runs longer (a large code-generation step) raises its own bound:
248+
249+
```toml
250+
[build]
251+
build_program_timeout = 1800 # seconds; 0 = no limit
252+
```
253+
254+
The value is read from **the manifest of the package that owns the
255+
`build.mcpp`** — a dependency's generator is bounded by the dependency's own
256+
declaration, because its author is the one who knows how long it takes. The
257+
precedence follows the same shape as `macos_deployment_target`:
258+
259+
```
260+
MCPP_BUILD_PROGRAM_TIMEOUT=<seconds> (this invocation; highest)
261+
> [build] build_program_timeout (that package's manifest)
262+
> 600 (built-in default)
263+
```
264+
265+
Leaving the key out is not the same as setting `0`: unset means "use the
266+
default bound", `0` means "no bound at all".
267+
268+
This value is deliberately **not** part of the build fingerprint — it changes
269+
no edge in the graph, and folding it in would mean that raising a timeout
270+
rebuilt the whole project, which is the opposite of what someone raising a
271+
timeout wants.
272+
273+
The **compile** phase is not bounded, only the build *program*. See
274+
[07-build-mcpp.md](07-build-mcpp.md) for why that asymmetry is deliberate.
275+
197276
### The C++ runtime contract (`cxx_runtime`)
198277

199278
`cxx_runtime` states what the produced artifact promises about the machine that

0 commit comments

Comments
 (0)