Skip to content

Commit 88ed2e5

Browse files
committed
release: 2026.9.5.2
The version annotations in the manual said 2026.9.6 while the release lands today, so they name the version that actually carries them. `mcpp.toml` and `MCPP_VERSION` move together, as `01_help_and_version.sh` requires. The CHANGELOG's Unreleased section described the doctor's `nvcc --dryrun` check as a shipped feature. That check left the engine in this same batch — the reading was right and the place was wrong — so the entry is rewritten to say where it went rather than to announce something the release does not contain.
1 parent 1317625 commit 88ed2e5

10 files changed

Lines changed: 115 additions & 39 deletions

File tree

CHANGELOG.md

Lines changed: 92 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,98 @@
55
66
## [Unreleased]
77

8-
### `mcpp self doctor` 报出设备编译器够不到自己的后端
9-
10-
一个 CUDA 工具包可以安装完整、就在 `PATH` 上,而设备代码仍然在第一个阶段失败。
11-
nvcc 以裸名调用 `cicc``cudafe++``ptxas``fatbinary`,依赖的是它自己从紧邻
12-
其二进制的 `nvcc.profile` 前置进来的一条 `PATH`。Debian 系打包把那个 profile
13-
做成指向 `/etc` 的符号链接,于是任何替换了 `/etc` 的容器或沙箱都会移除它,
14-
nvcc 沿用环境原有的 `PATH` 并报出 `sh: 1: cicc: not found` —— 这条消息既不提
15-
nvcc 也不提 profile,而工具包一样不缺,所以显而易见的检查全部通过。
16-
17-
判据取自 nvcc 自己:`--dryrun` 打印它将要运行的各个阶段与它将要使用的 `PATH`,
18-
而不编译任何东西。mcpp 解析这份计划,逐个解析其中的裸名,报出第一个解析不到的。
19-
一次没有产生计划的 dryrun(没有 nvcc,或输出不是一份计划)不产生任何结论 ——
20-
与宿主编译器上界那条检查同一条原则:一个够不到答案的探测不应当发明一个。
21-
22-
这是本轮把发布物放进沙箱验证时暴露出来的:示例在宿主上跑通,在 `--sandbox`
23-
里失败,而唯一的差别就是那条 `PATH`
8+
## [2026.9.5.2] — 2026-09-05
9+
10+
### ⭐⭐ 在编译任何东西之前比较机器的下界
11+
12+
有些机器事实**限定**了能为它构建什么,而忽略它们时,失败到得很晚。本轮的样本:
13+
设备运行时不得新于它将运行其上的驱动;当它更新时,构建与链接都干净通过,程序在
14+
第一次分配处失败,消息里既不提工具包也不提驱动。
15+
16+
两个数字在编译任何东西之前都是可知的。mcpp 不去问厂商的工具要它们 ——
17+
`tests/unit/test_runtime_contract` 禁止 `src/` 里出现厂商探针,而且这条规则是对的:
18+
一个学会跑一家厂商探针的引擎会学会跑四家。所以数字以**声明**抵达:
19+
20+
```toml
21+
[[runtime.requirements]]
22+
kind = "version-floor"
23+
value = "cuda.driver >= 12.0"
24+
```
25+
26+
`mcpp.build.version_floor` 只做比较,**这个文件里不出现任何厂商名字**:
27+
`cuda.driver` 是流经的数据。第二种后端不需要改动它。
28+
29+
### ⭐ 探针通道:`mcpp::fact` / `mcpp::floor`(协议 v7)
30+
31+
构建程序陈述它**测得**的事实与它**需要**的下界,引擎比较并在不满足时给出两侧取值
32+
(`version-floor-unmet`)。这是让 CUDA 探针得以整体离开 `src/` 的那条通道 ——
33+
同样的读数现在由规则包产出,而它知道自己在跑哪个工具。
34+
35+
因此 **`mcpp self doctor` 的设备一节与 `mcpp.toolchain.devicehost` 一并删除**
36+
它读得对(载荷优先于宿主、`crt/host_config.h` 的宿主编译器上界、`nvcc --dryrun`
37+
的不可达阶段),但它不属于引擎。新增 `tests/unit/test_core_vendor_probes`
38+
在剥掉注释的源码上陈述这条性质,并自带分母:枚举到的文件太少即判失败。
39+
40+
### 逐 glob 的加速器约束
41+
42+
`[build] sources` 的条目可以带上它面向的加速器:
43+
44+
```toml
45+
sources = [
46+
"src/*.cppm",
47+
{ glob = "src/kernels/**/*.cu", accel = "cuda12.9+{sm_89}" },
48+
]
49+
```
50+
51+
构建按它收窄;`--no-accel` 整条排除;不覆盖它的 `--accel` 被拒并点名两侧
52+
(`accel-mismatch`);匹配为空的约束被拒并点名该 glob —— 空匹配是笔误或搬走了的
53+
目录,而不是空操作。
54+
55+
### `--accel` / `--no-accel` 现在也挂在 `run``test`
56+
57+
此前只有 `build` 有,实测后果是一个工程的 CPU-only 变体**能构建却不能运行**
58+
两个动词接受同样的两个开关,给出任一个都绕开各自的快路径 —— 缓存的产物是按上一次
59+
构建的轴产的。
60+
61+
### 第二个编译器需要的两个答案
62+
63+
`MCPP_TOOLCHAIN_SYSROOT``MCPP_TOOLCHAIN_BINUTILS_DIR` 陈述 mcpp 传给**它自己**
64+
那个编译器的 `--sysroot``-B`。规则包驱动一个 mcpp 并未解析的编译器时,那个
65+
编译器对环境一无所知:sub-OS 里 C 库不在 `/usr/include`,汇编器不在 `/usr/bin`,
66+
于是它遇到的第一个 `#include` 就失败。`hipcc``-fsycl-host-compiler`、任何会编译
67+
自己产物的生成器都有同一个缺口。
68+
69+
`gcc::binutils_prefix_dir``-B` 的守卫收敛到一处,此前有三份副本,其中一份的
70+
注释写着它是另一份的镜像。
71+
72+
### Linux 上的动态构建程序 helper 改用 `DT_RPATH`
73+
74+
RUNPATH 只对 helper **自己**的 needed 生效,于是一个在运行期打开宿主库的构建程序
75+
在下一跳失败:实测 `dlopen("<sentinel>/lib/libcuda.so.1")`
76+
`libdl.so.2: cannot open shared object file`,而持有它的目录就在 helper 的 RUNPATH 里。
77+
mcpp 链接的产物早就因为这个原因带 DT_RPATH。链接策略进 helper 的缓存身份,
78+
旧 helper 会被重建而不是被重放。
79+
80+
### `--offline` 跳过首次使用的沙箱引导
81+
82+
`--offline` 承诺不碰网络,而 `load_or_init` 在空 home 里克隆索引、经 xlings 装
83+
ninja 与 patchelf。实测:空 home 下 26 秒 / 126 MB → 0.3 秒。
84+
85+
### `examples/09-cuda-kernel` 走两条路线,并拒绝它不能配的对
86+
87+
主路线是 clang(`-x cuda`):工程自己的编译器编设备单元,没有第二个宿主编译器、
88+
没有宿主编译器上界、没有 CUDA 的宿主头挡路。nvcc 是备用路线,并按名拒绝两对:
89+
宿主编译器超出 `crt/host_config.h` 所述上界(实测 gcc 16 + nvcc 12.9 即便加了
90+
`-allow-unsupported-compiler` 也死在 gcc 自己的 `<type_traits>` 里),以及工具包
91+
旧于 C 库(12.9 的 `crt/math_functions.h` 为宿主重声明 C23 的
92+
`cospi`/`sinpi`/`rsqrt` 不带 `noexcept`,而 glibc 2.41+ 带)。
93+
94+
同一个缝下还有一份 CPU 实现,由 `cfg(not(accelerator = "cuda"))` 选中,于是
95+
`mcpp build --no-accel` 编它、`mcpp build``.cu`,两侧都不需要手写条件。
96+
97+
实测(RTX 4080,驱动 550.144.03 报 CUDA 12.4,LLVM 22.1.8):`mcpp run`
98+
`mcpp run --no-accel` 都打印 `12 24 36 48`,来自不同的产物目录,后者不含
99+
`cudaMalloc`
24100

25101
## [2026.9.5.1] — 2026-09-05
26102

docs/05-mcpp-toml.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ the package/feature boundary, not on an individual target.
215215
> mcpp 2026.8.18.1 the two were byte-identical, so there was no spelling for
216216
> "nothing" and any file left under `src/` was swept in.
217217
218-
> **A `sources` entry may carry the accelerator it is for** (2026.9.6+):
218+
> **A `sources` entry may carry the accelerator it is for** (2026.9.5.2+):
219219
> `{ glob = "src/kernels/**/*.cu", accel = "cuda12.9+{sm_89}" }`. The glob
220220
> joins the list like any other; the constraint decides whether it applies to a
221221
> given build. It must match at least one file (an empty match is refused: it

docs/07-build-mcpp.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ is ignored, so diagnostics may be logged freely.
5555
| `mcpp:runner=<token>` *(2026.8.19.2+)* | one argv token of the command that EXECUTES this build's artifact, when the host cannot. Emitted once per token, in order; the artifact path is appended (or substituted for `{}`). Reaches the **consumer**. ⚠️ Emit the executable as an ABSOLUTE path, and only **one** dependency may supply it |
5656
| `mcpp:link-script=<path>` *(2026.8.19+)* | link with this **linker script** (`-T`; relative resolves against the package root, and the emitted path is absolute because the link runs in the build directory). Reaches the **consumer**, unlike `include-dir` — a board's memory layout is the one thing a consumer cannot write for itself |
5757
| `mcpp:warning=<text>` *(2026.8.21.2+)* | say something to the user and **keep going**. The one directive that changes no compile line, no link line and no source set. Survives the build cache — see below |
58-
| `mcpp:fact=<name>=<version>` *(2026.9.6+)* | state something the program **established about the machine** (`cuda.driver=12.4`). Compared against floors before anything is compiled; see below |
59-
| `mcpp:floor=<name> >= <version>` *(2026.9.6+)* | state what this package **needs** of that quantity. Unmet ⇒ the build is refused with both values (`version-floor-unmet`); a floor nobody stated a fact for is silent |
58+
| `mcpp:fact=<name>=<version>` *(2026.9.5.2+)* | state something the program **established about the machine** (`cuda.driver=12.4`). Compared against floors before anything is compiled; see below |
59+
| `mcpp:floor=<name> >= <version>` *(2026.9.5.2+)* | state what this package **needs** of that quantity. Unmet ⇒ the build is refused with both values (`version-floor-unmet`); a floor nobody stated a fact for is silent |
6060
| `mcpp:rerun-if-changed=<path>` | re-run `build.mcpp` when this file changes |
6161
| `mcpp:rerun-if-env-changed=<VAR>` | re-run `build.mcpp` when this env var changes |
6262

@@ -152,7 +152,7 @@ there is nothing to do the build never reaches the `build.mcpp` stage — it als
152152
does not report which target it built or which sources it inferred. Touch a
153153
source and the advisory returns.
154154

155-
### The probe channel: `fact` / `floor` (2026.9.6+)
155+
### The probe channel: `fact` / `floor` (2026.9.5.2+)
156156

157157
A rule package is the thing that knows how to ask a machine what it has —
158158
which library to open, which function to call — and the engine is the thing
@@ -235,7 +235,7 @@ package whose content is implementation-neutral. Asking follows whatever
235235
the compiler payload or through the runtime binding, and nothing has to look
236236
for it.
237237

238-
### Driving a second compiler: `toolchain_sysroot` / `toolchain_binutils_dir` (2026.9.6+)
238+
### Driving a second compiler: `toolchain_sysroot` / `toolchain_binutils_dir` (2026.9.5.2+)
239239

240240
```cpp
241241
const char* sr = mcpp::toolchain_sysroot(); // the `--sysroot` mcpp passes, or ""
@@ -561,10 +561,10 @@ The running program receives the build context as `MCPP_*` variables
561561
| `MCPP_TARGET_ENV` *(0.0.100+)* | `mcpp::target_env()` | the target's env segment (`gnu`/`musl`/`msvc`); empty string when the triple has none (macOS) |
562562
| `MCPP_HOST` | `mcpp::host()` | the host triple |
563563
| `MCPP_PROFILE` | `mcpp::profile()` | effective profile name (`dev`/`release`/…) |
564-
| `MCPP_TOOLCHAIN_SYSROOT` *(2026.9.6+)* | `mcpp::toolchain_sysroot()` | the `--sysroot` mcpp passes to its own compiler; empty when it passes none. For a rule package that runs a **second** compiler — see "Driving a second compiler" above |
565-
| `MCPP_TOOLCHAIN_BINUTILS_DIR` *(2026.9.6+)* | `mcpp::toolchain_binutils_dir()` | the directory mcpp names with `-B`; empty when it names none (a musl or MinGW payload brings its own assembler and linker) |
566-
| `MCPP_ACCEL` *(2026.9.6+)* | `mcpp::accel()` | the device axis of this build, resolved — `--accel` / `--no-accel` over `[build] accel` — in the wire form `cuda12.9+{sm_89} ptx>=89`; empty when the build asks for no accelerator. A rule package derives its own flags (`-gencode`, `--offload-arch`) from it, so the architecture set is written once, in the manifest. The same value feeds the `cfg(accelerator = "…")` layer key |
567-
| `MCPP_DEVICE_SOURCES` *(2026.9.6+)* | `mcpp::device_sources()` | the device-kind sources (`.cu`, `.hip`, …) the package's effective `sources` match, package-root-relative, one per line; empty when there are none. The engine compiles none of them — the rule package this program imports turns each into an `mcpp::action`. Already narrowed: a `{ glob, accel }` entry the build does not cover contributes nothing, so `--no-accel` yields an empty list |
564+
| `MCPP_TOOLCHAIN_SYSROOT` *(2026.9.5.2+)* | `mcpp::toolchain_sysroot()` | the `--sysroot` mcpp passes to its own compiler; empty when it passes none. For a rule package that runs a **second** compiler — see "Driving a second compiler" above |
565+
| `MCPP_TOOLCHAIN_BINUTILS_DIR` *(2026.9.5.2+)* | `mcpp::toolchain_binutils_dir()` | the directory mcpp names with `-B`; empty when it names none (a musl or MinGW payload brings its own assembler and linker) |
566+
| `MCPP_ACCEL` *(2026.9.5.2+)* | `mcpp::accel()` | the device axis of this build, resolved — `--accel` / `--no-accel` over `[build] accel` — in the wire form `cuda12.9+{sm_89} ptx>=89`; empty when the build asks for no accelerator. A rule package derives its own flags (`-gencode`, `--offload-arch`) from it, so the architecture set is written once, in the manifest. The same value feeds the `cfg(accelerator = "…")` layer key |
567+
| `MCPP_DEVICE_SOURCES` *(2026.9.5.2+)* | `mcpp::device_sources()` | the device-kind sources (`.cu`, `.hip`, …) the package's effective `sources` match, package-root-relative, one per line; empty when there are none. The engine compiles none of them — the rule package this program imports turns each into an `mcpp::action`. Already narrowed: a `{ glob, accel }` entry the build does not cover contributes nothing, so `--no-accel` yields an empty list |
568568
| `MCPP_OUT_DIR` | `mcpp::out_dir()` | a writable scratch/output dir owned by mcpp |
569569
| `MCPP_MANIFEST_DIR` | `mcpp::manifest_dir()` | the package root (= CWD) |
570570
| `MCPP_FEATURE_<NAME>` | `mcpp::has_feature("name")` | set to `1` per active feature (same `<NAME>` sanitization as the `MCPP_FEATURE_` compile macro) |

docs/20-accelerators.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ combinators.
232232
## Two boundaries worth stating
233233

234234
**`--accel` and `--no-accel` are `build`, `run` and `test` options** (run and
235-
test from 2026.9.6), as `--target` and `--profile` are. `pack` reads
235+
test from 2026.9.5.2), as `--target` and `--profile` are. `pack` reads
236236
`[build] accel` from the manifest like every other build input. The flag was a
237237
`build`-only option at first, and the measured consequence was a project whose
238238
CPU-only variant could be built and not run: `mcpp build --no-accel` produced

docs/zh/05-mcpp-toml.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ mcpp 刻意不在一次构建里把同一个共享源编译成两份:一个源
201201
> `src/` 下剩下的任何文件都会被扫进来。
202202
203203

204-
> **`sources` 的条目可以带上它所面向的加速器**(2026.9.6+):
204+
> **`sources` 的条目可以带上它所面向的加速器**(2026.9.5.2+):
205205
> `{ glob = "src/kernels/**/*.cu", accel = "cuda12.9+{sm_89}" }`。glob 与其它条目一样
206206
> 进入列表;约束决定它是否适用于某一次构建。它必须至少匹配一个文件(空匹配会被拒绝:
207207
> 那会让这个设备无东西可编,而只在链接时才说话)。`--no-accel` 下该 glob 被排除,

docs/zh/07-build-mcpp.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ mcpp build # 编译 + 运行 build.mcpp,然后构建工程
5252
| `mcpp:runner=<token>` *(2026.8.19.2+)* | 执行本次构建产物的命令的**一个 argv token**(宿主跑不了它时)。一个 token 一次调用、按顺序;产物路径会被追加(或替换 `{}`)。**到达消费者**。⚠️ 可执行文件要发**绝对路径**,且**只能有一个**依赖提供它 |
5353
| `mcpp:link-script=<path>` *(2026.8.19+)* | 用这个**链接脚本**链接(`-T`;相对路径按包根解析,发出的是绝对路径,因为链接是在构建目录里跑的)。与 `include-dir` 不同,它**到达消费者** —— 板子的内存布局恰恰是消费者写不出来的那一项 |
5454
| `mcpp:warning=<text>` *(2026.8.21.2+)* | 对用户说一句话并**继续**。唯一一条不改变编译行、链接行与源码集的指令。它**穿过构建缓存** —— 见下 |
55-
| `mcpp:fact=<name>=<version>` *(2026.9.6+)* | 陈述程序**测得的机器事实**(`cuda.driver=12.4`)。在编译任何东西之前与 floor 比较;见下 |
56-
| `mcpp:floor=<name> >= <version>` *(2026.9.6+)* | 陈述本包对该量的**下界**。不满足 ⇒ 构建被拒并给出两侧取值(`version-floor-unmet`);没有人陈述事实的下界保持沉默 |
55+
| `mcpp:fact=<name>=<version>` *(2026.9.5.2+)* | 陈述程序**测得的机器事实**(`cuda.driver=12.4`)。在编译任何东西之前与 floor 比较;见下 |
56+
| `mcpp:floor=<name> >= <version>` *(2026.9.5.2+)* | 陈述本包对该量的**下界**。不满足 ⇒ 构建被拒并给出两侧取值(`version-floor-unmet`);没有人陈述事实的下界保持沉默 |
5757
| `mcpp:rerun-if-changed=<path>` | 该文件变化时重跑 `build.mcpp` |
5858
| `mcpp:rerun-if-env-changed=<VAR>` | 该环境变量变化时重跑 `build.mcpp` |
5959

@@ -139,7 +139,7 @@ mcpp 在每次命中时重放它。
139139
`build.mcpp` 阶段 —— 它同样不会报告构建了哪个目标、推断了哪些源码。touch 一下源码,提示
140140
就回来了。
141141

142-
### 探针通道:`fact` / `floor`(2026.9.6+)
142+
### 探针通道:`fact` / `floor`(2026.9.5.2+)
143143

144144
规则包是知道「怎么问机器它有什么」的那一方 —— 打开哪个库、调用哪个函数;
145145
引擎则是不该知道的那一方。于是由包来**测量**,由引擎来**比较**:
@@ -205,7 +205,7 @@ const char* sr = mcpp::sysroot_dir(); // 目标的 C 库根目录,没有则
205205

206206
宿主目标上 `sysroot_dir()` 为空:那里 C 库随编译器载荷或运行时绑定而来,没人需要找它。
207207

208-
### 驱动第二个编译器:`toolchain_sysroot` / `toolchain_binutils_dir`(2026.9.6+)
208+
### 驱动第二个编译器:`toolchain_sysroot` / `toolchain_binutils_dir`(2026.9.5.2+)
209209

210210
```cpp
211211
const char* sr = mcpp::toolchain_sysroot(); // mcpp 传的 `--sysroot`,没有则为 ""
@@ -487,10 +487,10 @@ mcpp 会把它自己构建时用的**同一份** std 模块暂存过来,缓存
487487
| `MCPP_TARGET_ENV` *(0.0.100+)* | `mcpp::target_env()` | 目标的 env 段(`gnu`/`musl`/`msvc`);三元组无 env 段(macOS)时为空串 |
488488
| `MCPP_HOST` | `mcpp::host()` | 宿主三元组 |
489489
| `MCPP_PROFILE` | `mcpp::profile()` | 生效 profile 名(`dev`/`release`/…) |
490-
| `MCPP_TOOLCHAIN_SYSROOT` *(2026.9.6+)* | `mcpp::toolchain_sysroot()` | mcpp 传给自己那个编译器的 `--sysroot`;不传时为空串。供运行**第二个**编译器的规则包使用 —— 见上文「驱动第二个编译器」 |
491-
| `MCPP_TOOLCHAIN_BINUTILS_DIR` *(2026.9.6+)* | `mcpp::toolchain_binutils_dir()` | mcpp 用 `-B` 指的目录;不指时为空串(musl 与 MinGW 载荷自带汇编器与链接器) |
492-
| `MCPP_ACCEL` *(2026.9.6+)* | `mcpp::accel()` | 本次构建的设备轴,已解析 —— `--accel` / `--no-accel` 优先于 `[build] accel` —— 线上形态 `cuda12.9+{sm_89} ptx>=89`;不要加速器时为空串。规则包从它推导自己的开关(`-gencode``--offload-arch`),架构集合因此只在 manifest 写一次。同一个值也喂给 `cfg(accelerator = "…")` 这个 layer 键 |
493-
| `MCPP_DEVICE_SOURCES` *(2026.9.6+)* | `mcpp::device_sources()` | 本包有效 `sources` 匹配到的设备类源文件(`.cu``.hip`…),相对包根,一行一个;没有时为空串。引擎一个都不编译 —— 由本程序引入的规则包把每一个变成一条 `mcpp::action`。已经过收窄:构建未覆盖的 `{ glob, accel }` 条目贡献为空,因此 `--no-accel` 得到空列表 |
490+
| `MCPP_TOOLCHAIN_SYSROOT` *(2026.9.5.2+)* | `mcpp::toolchain_sysroot()` | mcpp 传给自己那个编译器的 `--sysroot`;不传时为空串。供运行**第二个**编译器的规则包使用 —— 见上文「驱动第二个编译器」 |
491+
| `MCPP_TOOLCHAIN_BINUTILS_DIR` *(2026.9.5.2+)* | `mcpp::toolchain_binutils_dir()` | mcpp 用 `-B` 指的目录;不指时为空串(musl 与 MinGW 载荷自带汇编器与链接器) |
492+
| `MCPP_ACCEL` *(2026.9.5.2+)* | `mcpp::accel()` | 本次构建的设备轴,已解析 —— `--accel` / `--no-accel` 优先于 `[build] accel` —— 线上形态 `cuda12.9+{sm_89} ptx>=89`;不要加速器时为空串。规则包从它推导自己的开关(`-gencode``--offload-arch`),架构集合因此只在 manifest 写一次。同一个值也喂给 `cfg(accelerator = "…")` 这个 layer 键 |
493+
| `MCPP_DEVICE_SOURCES` *(2026.9.5.2+)* | `mcpp::device_sources()` | 本包有效 `sources` 匹配到的设备类源文件(`.cu``.hip`…),相对包根,一行一个;没有时为空串。引擎一个都不编译 —— 由本程序引入的规则包把每一个变成一条 `mcpp::action`。已经过收窄:构建未覆盖的 `{ glob, accel }` 条目贡献为空,因此 `--no-accel` 得到空列表 |
494494
| `MCPP_OUT_DIR` | `mcpp::out_dir()` | mcpp 提供的可写输出/暂存目录 |
495495
| `MCPP_MANIFEST_DIR` | `mcpp::manifest_dir()` | 包根(= CWD) |
496496
| `MCPP_FEATURE_<NAME>` | `mcpp::has_feature("name")` | 每个活跃 feature 置 `1`(`<NAME>` 消毒规则与 `MCPP_FEATURE_` 编译宏一致) |

0 commit comments

Comments
 (0)