Skip to content

Commit bc77d50

Browse files
committed
fix(toolchain,build): an absent SubOS description must not stop the build; link C-only units with the C driver (2026.8.15.2)
三条互不相关的缺陷,共同点是「一个事实的缺失被当成了矛盾」。 #427 —— `mcpp build` 与 `mcpp toolchain install` 在 Linux 上硬失败 `ensure_post_install_fixup` 在调用方没给出运行时身份时,自己去读硬编码的 `<xlings home>/subos/default`,并把「读不到」变成 `std::unexpected`。触发条件与 沙箱无关:默认 SubOS 由早于 `subos_info` 块的 xlings 创建即可(实测现场是一个 16 字节、只有 `{"workspace":{}}` 的清单)。`allow_host_libs` 救不了 —— 该判定 发生在 hermeticity 策略之前;`mcpp toolchain install` 同样死。已发布三周。 真因是没做完的迁移。调用点的注释写着「fixup 是 RuntimeBinding 的消费者」, `runtimeId` 参数也早已加上并从四处传入,旧的自行推导没有删。而被它保护的 `gcc_post_install_fixup` 本来就正确处理空值(warning 降级)—— 那个 `else` 从未执行过。 * 删掉第二处推导。身份只能来自调用方。 * 未知降级,矛盾仍然失败:身份为空 ⇒ 跳过并说明;身份声明了却兑现不了 ⇒ 报错。 * 严重程度归调用方:`build` 以 info 级说明一次(按载荷去重), `toolchain install` 以 warning 级说明并给出 `xlings self update`。 * 降级不写 marker,以免「什么都没做」被读成「已经做过」。 * `toolchain_install` 自己解析一次 RuntimeBinding —— 缺了这一步,单删兜底会让 它永远跳过 fixup,把硬失败换成静默的坏安装。 #426 —— 纯 C 的共享库依赖 libstdc++ 所有链接一律走 `$cxx`。同一个对象、同一份 ldflags,只换驱动的实测对照: g++ 给出 `libstdc++.so.6` `libm.so.6` `libgcc_s.so.1` `libc.so.6`,gcc 只给出 `libc.so.6` —— 三个 NEEDED 全部由驱动带入,零真实引用。 * 按内容选驱动,谓词照 `unit_needs_std` 的形状写;⚠️ 查不到的对象(action 产物) 保守判为 C++,与那个谓词相反。 * 只换驱动不够:`-lstdc++exp` 是显式命名的,`-stdlib=libc++` 与 macOS 的 `libc++.a` 路径在契约表里。故 `CompileFlags` 增加 `ldC`(与 `ld` 同一表达式 产生,`ld` 由构造保证不变),契约表增加 `unitFlagsC`(`-static` 与 `-static-libgcc` 属于 libc/编译器运行时,保留)。 * 顺带补上 `std.compat.o` 缺失的 `unit_needs_std` 收窄 —— #416 只修了 `std.o`。 main 的 bench pin 守卫 —— 断言本身是错的 发版收尾 bump `.xlings.json` 后三平台 e2e 全红。该守卫要求 `reference_mcpp` 等于 bootstrap pin,理由是「参照臂就是 CI 装的那个」;两半都不成立(标准集不在任何 workflow 里;bootstrap pin 是自举下界,可合理滞后),而它声称防止的危险已由 `run-standard.sh` 自己防住(按精确版本解析 + 要求二进制自述版本)。 * 删掉跨文件相等断言,保留编译器 pin 那条真耦合。 * 报告表头直接写出实测版本(engine 键本来就带),运行时把请求与实际解析到的 路径写进 `meta.json`。 文档 * `edit-body` 改写为三种情形的表(`.cppm` 移动行号 / `.cppm` 原地等长 / 独立 `.cpp`),不再读作否定级联抑制的价值。 * ⚠️ 订正 SPEC.md 中已被实测证否的解释:决定因素是行号移动,不是「成员函数体 进 BMI」——`Version::str()` 原地修改后 BMI 逐字节相同。 * 记录一个具名缺口:没有「原地等长修改」的场景,而那正是日常改动。 测试 * `tests/unit/test_post_install.cpp` +4:门的四个分支。⚠️ 必须是单测 —— 任何低成本 e2e 都用符号链接继承载荷,而 fixup 对继承载荷提前返回, 被测代码一行都执行不到(与 221 是同一种假绿)。 * `tests/e2e/237` 用户可见契约;`tests/e2e/238` 驱动选择,两个方向都钉。
1 parent bb53e81 commit bc77d50

23 files changed

Lines changed: 1449 additions & 81 deletions

.agents/docs/2026-08-15-issues-426-427-analysis.md

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

README.md

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -334,13 +334,21 @@ data: [`standard-20260814-linux-x86_64`](bench/results/standard-20260814-linux-x
334334
skips the cascade when the interface is unchanged. This is default behaviour
335335
and requires no configuration. The `mcpp (old)` column measures the previous
336336
release at 81.72s, level with cmake, so the effect is new in this revision.
337-
* **`edit-body` is the control case**, and it is the row where the cascade is
338-
genuinely owed: mcpp is 1.1x rather than 200x, and an engine faster here would
339-
be omitting work. `+opt` does not omit it either — it performs the same work
340-
2.9x faster. Worth stating precisely: the perturbation **inserts a line**, and
341-
under GCC that shifts the recorded source location of every declaration after
342-
it, which changes the BMI. The cascade follows from the changed BMI, not from
343-
the edited body — measured in
337+
* **`edit-body` measures the case where the cascade is genuinely owed** — and
338+
whether an edit owes one depends on where the body lives:
339+
340+
| the function body is in… | editing it | this row |
341+
|---|---|---|
342+
| a `.cppm`, and the edit **moves lines** | GCC records declaration positions, so the BMI changes → cascade owed | **what is measured: 1.1x, and 2.9x with `+opt`** |
343+
| a `.cppm`, edited **in place** (same line count) | GCC does not serialise non-template bodies → BMI unchanged → no cascade | ~200x, like `touch-hub` |
344+
| a separate `.cpp` implementation unit | that file has no BMI at all → no cascade, on every compiler | ~200x |
345+
346+
The perturbation here inserts a statement, so it takes the first row: every
347+
engine has to rebuild the importers, and one that did not would be skipping
348+
work. `+opt` does not skip it either — it does the same work 2.9x faster.
349+
Splitting interface from implementation is the sturdiest of the three, because
350+
it does not depend on GCC's body handling or on avoiding line shifts.
351+
Measured in
344352
[`.agents/docs/2026-08-15-module-edit-granularity.md`](.agents/docs/2026-08-15-module-edit-granularity.md).
345353
* **`bmi_schedule` is opt-in and disabled by default** (`auto` resolves to off).
346354
It moves code generation off the critical path, so it helps only where a

README.zh-CN.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -327,10 +327,18 @@ cmake 4.4.2 / xmake 3.1.0 · `-` 表示未测,本表没有 ·
327327
cmake 与 xmake 按时间戳判断,重编全部下游单元;mcpp 将编译器刚产出的 BMI 与上
328328
一份比较,接口未变则不触发级联。这是默认行为,无需任何配置。`mcpp (旧版)` 一列
329329
测得上一个发布版为 81.72s,与 cmake 同量级,因此该效果在本版本中才生效。
330-
* **`edit-body` 为对照组**,也是级联确实欠着的那一行:mcpp 为 1.1x 而非 200x,
331-
在这一行更快的引擎意味着省略了应做的工作。`+优化` 同样不省略,只是把同一份工作
332-
加快 2.9 倍。有一点需要说准:该扰动是**插入一行**,而 GCC 会因此移动其后所有声明
333-
的源码位置记录,BMI 随之改变。级联来自变化后的 BMI,而非被编辑的函数体 —— 实测见
330+
* **`edit-body` 量的是级联确实欠着的那种改动** —— 一次改动欠不欠级联,取决于函数体
331+
写在哪里:
332+
333+
| 函数体所在 | 改动它 | 对应本行 |
334+
|---|---|---|
335+
| `.cppm`,且改动**移动了行号** | GCC 在 BMI 里记录声明位置,BMI 随之改变 → 欠级联 | **本行所测:1.1x,`+优化` 2.9x** |
336+
| `.cppm`,**原地等长**修改 | GCC 不序列化非模板函数体 → BMI 不变 → 不级联 | 约 200x,与 `touch-hub` 同档 |
337+
| 独立的 `.cpp` 实现单元 | 该文件根本不产生 BMI → 不级联,且跨编译器成立 | 约 200x |
338+
339+
这里的扰动插入一条语句,因此落在第一行:所有引擎都必须重建导入者,更快只能意味着
340+
漏做。`+优化` 也不漏做,只是把同一份工作加快 2.9 倍。三者中**接口与实现分离最稳**,
341+
因为它既不依赖 GCC 对函数体的处理方式,也不依赖你避免行号移动。实测见
334342
[`.agents/docs/2026-08-15-module-edit-granularity.md`](.agents/docs/2026-08-15-module-edit-granularity.md)
335343
* **`bmi_schedule` 为 opt-in,默认关闭**(`auto` 解析为 off)。它将代码生成移出关键
336344
路径,因此仅在级联必需时有效:`cold` 86.69s → 35.73s、`edit-body` 80.87s →

bench/README.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ than what it said.
213213
| bazel | **9.2.0** | `matrix.json``tools` |
214214
| gcc | **16.1.0** | `bench/src/toolchain.cppm` |
215215
| clang / libc++ | **22.1.8** (Windows: 20.1.7) | `bench/src/toolchain.cppm` |
216-
| reference mcpp | **2026.8.11.3** | `matrix.json``reference_mcpp` |
216+
| reference mcpp | **2026.8.11.3** | `matrix.json``reference_mcpp`; the run records which release it actually resolved in `meta.json`, and the report names it in the column header |
217217
| mcpp (the workload) | **2026.8.11.3**`a749e9f` | submodule `projects/mcpp/mcpp-2026.8.11.3` |
218218
| xlings (combined style) | **2026.8.11.2**`b1563fe` | submodule `projects/xlings/xlings-2026.8.11.2` |
219219
| xlings (split style) | **2026.8.13.1**`f072075` | submodule `projects/xlings/xlings-2026.8.13.1` |
@@ -325,9 +325,12 @@ Four things this says, and the fixture can say none of them:
325325
touching a hub interface costs cmake and xmake a full 83-second rebuild
326326
because they decide by timestamp, and 0.40s for an engine that compares the
327327
BMI it just produced against the previous one.
328-
4. **`edit-body` is the control.** mcpp is deliberately *not* fast there (0.89x):
329-
the interface genuinely changed, so the cascade is owed. An engine that were
330-
fast on that row would have skipped work it owed.
328+
4. **`edit-body` is the control.** mcpp is deliberately *not* fast there
329+
(0.89x): the perturbation inserts a statement into an interface unit, which
330+
moves the source position GCC records for every declaration after it, so the
331+
BMI changes and the cascade is owed. An engine that were fast on that row
332+
would have skipped work it owed. A body edit that does NOT move lines, or one
333+
in a separate `.cpp`, owes no cascade and mcpp skips it — see §7.
331334

332335
> **The xmake column is from a SEPARATE run.** Its numbers in the original
333336
> five-arm run were invalid — xmake normalises `--buildir` to a path relative to

bench/README.zh-CN.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ __format/format_functions.h:99:30: error: call to implicitly-deleted default
203203
| bazel | **9.2.0** | `matrix.json``tools` |
204204
| gcc | **16.1.0** | `bench/src/toolchain.cppm` |
205205
| clang / libc++ | **22.1.8**(Windows:20.1.7) | `bench/src/toolchain.cppm` |
206-
| 参照 mcpp | **2026.8.11.3** | `matrix.json``reference_mcpp` |
206+
| 参照 mcpp | **2026.8.11.3** | `matrix.json``reference_mcpp`;每次跑把实际解析到的版本写进 `meta.json`,报告的表头直接写出它 |
207207
| mcpp(被测工作负载) | **2026.8.11.3**`a749e9f` | 子模块 `projects/mcpp/mcpp-2026.8.11.3` |
208208
| xlings(合并风格) | **2026.8.11.2**`b1563fe` | 子模块 `projects/xlings/xlings-2026.8.11.2` |
209209
| xlings(分离风格) | **2026.8.13.1**`f072075` | 子模块 `projects/xlings/xlings-2026.8.13.1` |
@@ -278,12 +278,15 @@ SNAPSHOT」。
278278
| `noop` | 什么都不动 | 「已经是最新」有多便宜 |
279279
| `touch-hub` | 给被大量 import 的单元改 mtime,**内容不变** | 引擎能不能证明接口没变? |
280280
| `edit-comment` | 往同一个单元里插一条注释 | 字节**确实**变了但接口没变 —— 只有比较产出 BMI 的引擎能止住级联 |
281-
| `edit-body` | 函数体内部一处真实语义修改 | 日常循环。接口单元里的内联函数体,BMI 合理地变了,级联是**对的** |
281+
| `edit-body` | 函数体内部一处真实语义修改(插入一条语句) | 日常循环。改动移动了行号,接口单元的 BMI 因此改变,级联是**对的** |
282282
| `touch-leaf` | 给没人 import 的单元改 mtime | 重编 1 个 + 链接 |
283283

284284
`edit-comment``edit-body`**刻意分开**的:不分开的话,一个能跳过纯注释重建
285285
的引擎就可以宣传成「改代码快 12 倍」,而那实际上是一句关于注释的话。
286286
`edit-body` 是反方向的对照 —— 那里没有引擎应该快,快了就是漏了该做的活。
287+
一次函数体修改欠不欠级联,取决于**函数体写在哪里**、以及改动是否移动行号:
288+
`.cppm` 里移动行号 ⇒ 欠;`.cppm` 里原地等长修改 ⇒ 不欠;独立 `.cpp` ⇒ 不欠,
289+
且跨编译器成立。详见 `SPEC.md`
287290

288291
---
289292

bench/SPEC.md

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,15 @@ table that was measuring something other than what it said:
6262
| the workloads | git submodules under `bench/projects/` | xlings was cloned from its default branch at run time (`--hub src/xlings.cppm` named a file that had stopped existing); **mcpp's own sources were the checkout**, so every commit on a branch changed the thing being measured |
6363
| the reference mcpp | `matrix.json.reference_mcpp` | a report said how fast this branch is, never whether it got faster |
6464

65+
> The reference pin is **not** required to equal the `.xlings.json` workspace
66+
> pin. A guard once required that, on the theory that the reference arm is
67+
> whatever CI bootstraps; neither half holds (the standard set runs on a
68+
> developer box, and the bootstrap pin is a self-hosting floor that may lag a
69+
> release), and bumping the pin after a release turned every e2e shard red on
70+
> `main`. `run-standard.sh` resolves the arm by exact version and requires the
71+
> binary to report that version itself, so a mismatch drops the column with a
72+
> note instead of measuring the wrong release.
73+
6574
`--compiler payload:gcc` / `payload:clang` is the spelling that delivers the
6675
second row: it resolves to the driver **inside mcpp's own registry**, so every
6776
engine is handed the same binary. That is the suite's fairness rule
@@ -233,7 +242,7 @@ job: the cell still runs, and its note says what to distrust.
233242
| `noop` | nothing | how cheap is "already up to date" |
234243
| `touch-hub` | mtime bump on a widely-imported unit, **content unchanged** | can the engine prove the interface did not change? |
235244
| `edit-comment` | a comment inserted into that same unit | the bytes *did* change but the interface did not — only an engine that compares the produced BMI avoids the cascade |
236-
| `edit-body` | a real semantic edit inside a function body | the everyday loop — and whether a cascade is owed depends on **where the body lives**, not on the edit. See below. |
245+
| `edit-body` | a real semantic edit inside a function body | the everyday loop — and whether a cascade is owed depends on **where the body lives and whether the edit moves lines**, not on what the body now does. See below. |
237246
| `touch-leaf` | mtime bump on a unit nobody imports | recompile 1 + link |
238247

239248
#### ⚠️ `edit-body` perturbs a DIFFERENT FILE in each variant, and the two ask
@@ -301,25 +310,52 @@ Measured directly, GCC 16.1, comparing the BMI before and after:
301310

302311
| what is edited | BMI | cascade |
303312
|---|---|---|
304-
| a free exported function's body, in the `.cppm` | **byte-identical** | not owed |
305-
| a **member function of an exported class**, inline in the `.cppm` | **differs** | **owed** |
306-
| a body in a separate `.cpp` implementation unit | **byte-identical** | **not owed** |
307-
308-
A class's member function bodies are part of the class definition, which every
309-
importer has to see, so they are serialised into the BMI. A free function's body
310-
is not, and nothing in an implementation unit is.
311-
312-
So "editing one function rebuilt forty modules" is not inherent to named modules
313-
— it is a consequence of where the body was written. `mcpp`'s own
314-
`src/version_req.cppm` is the first case (the perturbation lands in
315-
`Version::str()`, a member of an exported class), which is why its `edit-body`
316-
row is a near-full rebuild and why that is correct.
313+
| a body in a `.cppm`, edit **moves lines** (inserts or deletes one) | **differs** | **owed** |
314+
| a body in a `.cppm`, edited **in place** (same line count) | **byte-identical** | not owed |
315+
| a body in a separate `.cpp` implementation unit | **no BMI exists** | not owed |
316+
317+
GCC 16.1 does not serialise non-template function bodies, so changing what a
318+
body *does* is invisible to importers. What it does serialise is the source
319+
position of each declaration — so inserting a line moves every declaration
320+
after it and the BMI changes for that reason alone.
321+
322+
⚠️ **An earlier version of this section said the deciding factor was whether the
323+
body belonged to an exported class.** That was reasoning, and the measurement
324+
refuted it: editing `Version::str()` — a member of an exported class — in place
325+
rebuilt its object and left the BMI byte-identical, so no importer was touched.
326+
The deciding factor is line movement, not class membership.
327+
328+
Two consequences:
329+
330+
* "editing one function rebuilt forty modules" is not inherent to named modules.
331+
It follows from the edit moving lines in an interface unit.
332+
* the third row is the sturdiest, because it holds for **every** compiler and
333+
for every edit: a `.cpp` implementation unit produces no BMI, so nothing
334+
downstream can depend on its contents. Clang, whose BMI carries more than
335+
GCC's, cascades on an in-place body edit in a `.cppm` but not on a `.cpp`.
317336

318337
**This is what the two xlings pins measure.** Moving the implementations out of
319338
the interface units takes `edit-body` from 88.33s to **1.77s** on the same
320339
project — ~50x, the largest single effect anywhere in this suite, and a code
321340
style rather than an engine feature.
322341

342+
#### KNOWN GAP: there is no scenario for an in-place body edit
343+
344+
The three rows in the table above are not equally covered. `edit-body` inserts a
345+
statement, so only the **first** row is ever measured; the second — a semantic
346+
edit that keeps the line count — has no scenario at all.
347+
348+
That is the everyday case, and it is the only one that would show cascade
349+
suppression on a *real code change* rather than on a timestamp (`touch-hub`) or
350+
a comment (`edit-comment`). Its absence makes the published tables read as
351+
though the effect applies only when the code does not change, which understates
352+
it.
353+
354+
Closing it is a `replace_in_first_body` beside `insert_into_first_body` (an
355+
equal-length substitution, e.g. one integer literal for another of the same
356+
width) plus a scenario token — and a re-run of the standard set, which is why it
357+
is recorded here rather than half-added with no data behind it.
358+
323359
Real projects run five of the six: `touch-leaf` needs a unit nobody imports
324360
*and* a stable name for it, which a generated fixture has by construction and a
325361
real tree does not.

bench/run-standard.sh

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,32 @@ echo "standard set: $(printf '%s\n' "$PLAN" | wc -l) cells, ${RUNS} run(s) each,
255255
echo "output : ${OUT#"$ROOT"/}"
256256
echo
257257

258+
# WHICH RELEASE THE `mcpp (old)` COLUMN ACTUALLY MEASURED, recorded rather than
259+
# asserted. `matrix.json` states a REQUEST; this file states the OUTCOME — the
260+
# path that was resolved and the version that binary reported about ITSELF (the
261+
# loop above refuses any binary whose `--version` disagrees).
262+
#
263+
# This replaces a guard that required `reference_mcpp` to equal the `.xlings.json`
264+
# bootstrap pin. Making the report self-describing removes the drift instead of
265+
# policing it: a reader never has to hold two files in their head to know what
266+
# the "old" column is, and the two pins are free to be what they each are.
267+
mkdir -p "$OUT"
268+
python3 - "$OUT/meta.json" "$REFERENCE_MCPP" "$REF_BIN" "$UNDER_TEST" <<'PY'
269+
import json, sys
270+
out, requested, path, under_test = sys.argv[1:5]
271+
json.dump({
272+
"schema": 1,
273+
"reference_mcpp": {
274+
"requested": requested or None,
275+
# null ⇒ not resolved on this machine; the old-vs-new column is absent
276+
# from the report rather than silently filled by another release.
277+
"measured": (requested if path else None),
278+
"binary": path or None,
279+
},
280+
"under_test": under_test or None,
281+
}, open(out, "w", encoding="utf-8"), indent=2)
282+
PY
283+
258284
# ⚠️ NOT `printf ... | while`. A pipeline runs its right-hand side in a SUBSHELL,
259285
# so a failure counter incremented inside the loop does not survive it and the
260286
# script exits 0 no matter what happened. Verified with a stub engine that exits

bench/tools/report.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,15 @@ def short_name(engine, newest, lang="en"):
234234
return "mcpp +优化" if lang == "zh" else "mcpp +opt"
235235
if base == newest:
236236
return "mcpp"
237-
return "mcpp (旧版)" if lang == "zh" else "mcpp (old)"
237+
# ⚠️ NAME THE RELEASE, do not write "old". The version is already in the
238+
# engine key — it is the version that binary reported about ITSELF — so a
239+
# header that hides it is throwing away the one fact that makes the column
240+
# checkable. A guard used to enforce this from outside, by requiring
241+
# `matrix.json`'s `reference_mcpp` to equal the `.xlings.json` bootstrap
242+
# pin; that coupled a bench knob to the release pipeline and turned `main`
243+
# red when the pin moved. Printing what was measured removes the drift
244+
# instead of policing it.
245+
return "mcpp " + base[len("mcpp@"):]
238246

239247

240248
def columns_legend(short, engines, lang):

docs/05-mcpp-toml.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,13 @@ The **compile** phase is not bounded, only the build *program*. See
330330
runs it. It is a **distribution** property, not a build one — it describes the
331331
runtime dependency set, and the flags that deliver it differ per platform.
332332

333+
> **A target with no C++ in it has no C++ runtime contract to honour.** mcpp
334+
> links such a target with the C driver and leaves the C++ runtime flags off the
335+
> line entirely, so a pure-C shared library does not acquire a dependency on
336+
> `libstdc++`/`libc++` it has no use for. One C++ translation unit anywhere in
337+
> the target puts the whole target back on the C++ driver. This is derived from
338+
> the sources — there is no key for it.
339+
333340
```toml
334341
[build]
335342
cxx_runtime = "self-contained" # applies to every target (the default)

docs/zh/05-mcpp-toml.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,11 @@ MCPP_BUILD_PROGRAM_TIMEOUT=<秒> 本次调用(最高)
292292
`cxx_runtime` 声明的是**产物对运行它的机器做出的承诺**。它是**分发**属性而非
293293
构建属性 —— 它描述的是运行期依赖集,而兑现它的 flag 逐平台不同。
294294

295+
> **不含 C++ 的目标没有 C++ 运行时契约需要兑现。** mcpp 用 C 驱动链接它,
296+
> 并且完全不发 C++ 运行时相关的 flag,因此一个纯 C 的共享库不会平白拿到
297+
> `libstdc++` / `libc++` 依赖。目标里只要有一个 C++ 翻译单元,整个目标就回到
298+
> C++ 驱动。这一判定由源码推导,没有对应的配置键。
299+
295300
```toml
296301
[build]
297302
cxx_runtime = "self-contained" # 作用于所有目标(默认值)

0 commit comments

Comments
 (0)