Skip to content

Commit 6ce62fd

Browse files
committed
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
1 parent e53204a commit 6ce62fd

49 files changed

Lines changed: 3477 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: 913 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

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

docs/07-build-mcpp.md

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -401,15 +401,49 @@ When nothing changed you'll see `build.mcpp up to date (cached)`; otherwise
401401
- **CWD is the project root**, so relative paths (`src/generated.cpp`) land where
402402
you expect.
403403
- A non-zero exit from `build.mcpp` aborts the build and prints its output.
404-
- **The run is bounded** (mcpp 2026.8.5.1+, **POSIX only**): a build program
405-
gets **600 s** by default, after which mcpp kills it and fails the build
406-
naming the package. Override with `MCPP_BUILD_PROGRAM_TIMEOUT=<seconds>`
407-
(`0` = no limit). **On Windows the bound is not enforced** — the process
408-
launcher has no kill-by-handle path yet (`mcpp.platform.process`), so a
409-
build program that hangs there still hangs the build. Same limitation as
410-
`mcpp test --timeout`; stated rather than papered over. The
411-
**compile** is deliberately *not* bounded — the same asymmetry `mcpp test`
404+
- **The run is bounded** (mcpp 2026.8.5.1+): a build program gets **600 s** by
405+
default, after which mcpp kills it and fails the build naming the package.
406+
Configure it per package:
407+
408+
```toml
409+
[build]
410+
build_program_timeout = 1800 # seconds; 0 = no limit
411+
```
412+
413+
Precedence, highest first — the same shape `macos_deployment_target` uses:
414+
415+
```
416+
MCPP_BUILD_PROGRAM_TIMEOUT=<seconds> this invocation only
417+
> [build] build_program_timeout the manifest of the package that OWNS the build.mcpp
418+
> 600 built-in default
419+
```
420+
421+
The value comes from the **owning package's** manifest, because its author is
422+
the one who knows how long the generator takes. When a dependency's build
423+
program times out, the error names the exact `mcpp.toml` to edit — editing
424+
your own would change nothing.
425+
426+
Omitting the key is not the same as `0`: unset means "use the default bound",
427+
`0` means "no bound at all".
428+
429+
**The bound is enforced on every platform** as of mcpp 2026.8.11.1. It used
430+
to be POSIX-only: the Windows launcher fell through to an unbounded path, so
431+
this knob — and `mcpp test --timeout`, and `--build-timeout` — silently did
432+
nothing there. Windows now runs the child in a Job object and closes it on
433+
expiry, which takes the whole process tree rather than just the direct child
434+
(a grandchild left holding the capture pipe would otherwise hang the drain
435+
after the kill).
436+
437+
The **compile** is deliberately *not* bounded — the same asymmetry `mcpp test`
412438
uses: a long compile is usually legitimate (a first-run `std` module build is
413439
minutes) and killing it produces a baffling failure, while a long-running
414440
build *program* is usually stuck, and an unbounded one hangs the whole build
415441
with no diagnostic at all.
442+
443+
> **Why not "ask the user instead of aborting"** ([#410](https://github.com/mcpp-community/mcpp/issues/410)):
444+
> the program's stdout is already dup2'd into a pipe that carries the `mcpp:`
445+
> directive protocol, so there is no interaction channel; most builds run
446+
> where nobody is watching (CI, a pipeline, ninja's child), and a build
447+
> blocked on a prompt is harder to diagnose than one that failed; and a build
448+
> whose outcome depends on a keystroke is not reproducible. The configurable
449+
> bound plus an error that names the file to edit answers the same need.

0 commit comments

Comments
 (0)