Skip to content

Commit 8ed92f6

Browse files
committed
docs(zh): 同步 host 工具、构建图节点、规则包三节
docs/zh/05 §2.14 与 docs/zh/07 的 dep_bin / action 两节 —— 与英文版一一对应。
1 parent beb25cd commit 8ed92f6

2 files changed

Lines changed: 156 additions & 0 deletions

File tree

docs/zh/05-mcpp-toml.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,81 @@ OPENBLAS_NUM_THREADS = "1"
724724
host 工具(`make`/`cmake`/`protoc`…)、按项目固定工具版本、或设构建期环境变量——无需手改
725725
`.xlings.json``[toolchain]`(§2.7)仍是编译器的便捷简写;`[xlings.workspace]` 是其通用形式。
726726

727+
### 2.14 依赖产出的 host 工具(mcpp 2026.8.5.1+)
728+
729+
一个包能构建出消费者在**构建期**需要的二进制 —— `protoc``grpc_cpp_plugin`
730+
`flatc``moc`、转译器。在依赖上声明:
731+
732+
```toml
733+
[dependencies]
734+
protobuf = { version = "35.1", tools = ["protoc"] }
735+
grpc = { version = "1.83.0", tools = ["grpc_cpp_plugin"] }
736+
```
737+
738+
每个名字必须是该包的一个 `kind = "bin"` target。mcpp 会**为构建机器**构建它,
739+
并把绝对路径以 `MCPP_DEP_<PKG>_BIN_<TOOL>` 交给 `build.mcpp` —— 用
740+
`mcpp::dep_bin("protobuf", "protoc")` 读取(见 [07 — build.mcpp](07-build-mcpp.md))。
741+
742+
四条值得知道的性质:
743+
744+
- **永远是 host 二进制。** 即使 `mcpp build --target <triple>`,工具依然为**本机**
745+
构建 —— 代码生成器必须在这里跑。它是一次独立的、面向 host 的子构建:工具包
746+
自己的 `[toolchain]`、自己的依赖解析生效,不需要与你的构建一致。之所以安全,
747+
是因为可执行文件与你的代码**零 ABI 接触**
748+
- **单一版本轴。** 工具的版本**就是**依赖的版本,所以「protoc 与其运行时不匹配」
749+
这种情况**不可表达**。(把工具单独打包正是会出这个问题,而且它在**运行期**才咬人,
750+
不是编译期。)
751+
- **默认关闭。** 没人要就什么都不构建,成本由消费者付。包用 `[features]` +
752+
`required_features` 给昂贵的部分加门(protobuf 的 `protoc` 需要 libprotoc 的
753+
~157 个额外 TU,只用运行时的人绝不该编译它)。
754+
- **全局缓存**,按 包版本 × host 工具链 × feature × 自身依赖闭包 键控 —— 每台机器
755+
构建一次,而不是每个工程一次。
756+
757+
#### `[tools.overrides]` —— 用你已经有的二进制
758+
759+
```toml
760+
[tools.overrides]
761+
"compat.protobuf:protoc" = "/usr/bin/protoc"
762+
```
763+
764+
或者不改 manifest(CI、发行版打包):
765+
766+
```bash
767+
MCPP_TOOL_PROTOBUF_PROTOC=/usr/bin/protoc mcpp build
768+
```
769+
770+
命中 override 会**完全跳过构建**。每个同类系统都提供这条逃生舱(vcpkg 的
771+
`VCPKG_HOST_TRIPLET`、CMake 的 `LLVM_NATIVE_TOOL_DIR`、Qt 的 `QT_HOST_PATH`),
772+
理由一样:一个在本机构建不出来的工具**不能是死路**。它**刻意不进** cache key ——
773+
逃生舱不是可复现输入。
774+
775+
#### `host-module = true` —— 可复用的构建规则以包分发
776+
777+
一条规则(比如「对这些 `.proto` 跑 protoc」)应该**写一次**,而不是复制进每个
778+
消费者的 `build.mcpp`。把它做成普通的 mcpp 库包再 import:
779+
780+
```toml
781+
[dependencies]
782+
"mcpp.rules.protobuf" = { version = "0.1.0", host-module = true }
783+
```
784+
785+
```cpp
786+
// build.mcpp
787+
import mcpp;
788+
import mcpp.rules.protobuf;
789+
int main() { mcpp::rules::protobuf::generate(/**/); }
790+
```
791+
792+
mcpp 会把该包的 lib 根模块**为 host 编译,且与 `build.mcpp` 在同一条命令里** ——
793+
这正是 BMI 能用的前提:一个模块接口只对「在 standard / dialect / 编译器身份上与
794+
它一致」的编译可导入。
795+
796+
于是规则**有版本、能测试、能通过你已有的包管理器分发**,而且是用 **C++** 写的
797+
—— 不引入第二门语言,这正是 `build.mcpp` 存在的理由。
798+
799+
*限制:* 规则接口是单独编译的,因此可以 import `std` 与内置 `mcpp` 模块,
800+
但不能 import 第三个包。规则包按构造是叶子。
801+
727802
## 附录 A. Schema 所有权原则(新字段准入标准)
728803
729804
> **语法封闭,词汇开放**:谁拥有解析语义谁定义键;谁拥有领域知识谁定义值。

docs/zh/07-build-mcpp.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,87 @@ int main() {
9090
| `mcpp::source(p)` | `mcpp:source=` |
9191
| `mcpp::include_dir(d)` / `mcpp::include_dir_after(d)` | `mcpp:include-dir=` / `mcpp:include-dir-after=` |
9292
| `mcpp::rerun_if_changed(p)` / `mcpp::rerun_if_env_changed(v)` | 对应的 `rerun-*` 指令 |
93+
| `mcpp::dep_bin(pkg, tool)` *(2026.8.5.1+)* |`MCPP_DEP_<PKG>_BIN_<TOOL>` —— 依赖构建出的 **host 工具**的绝对路径(见下) |
94+
| `mcpp::action{…}.submit()` *(2026.8.5.1+)* | `mcpp:action=` —— **声明一个构建图节点**,而不是在这里把活干了(见下) |
95+
96+
### 依赖产出的 host 工具(2026.8.5.1+)
97+
98+
`mcpp.toml` 里声明需求,然后调用它:
99+
100+
```toml
101+
[dependencies]
102+
protobuf = { version = "35.1", tools = ["protoc"] }
103+
```
104+
105+
```cpp
106+
// build.mcpp
107+
import mcpp;
108+
int main() {
109+
const char* protoc = mcpp::dep_bin("protobuf", "protoc");
110+
// … 调用它,然后声明它产出了什么 …
111+
}
112+
```
113+
114+
mcpp 会**为构建机器**构建那个 `kind = "bin"` target(即使在 `--target` 下),
115+
全局缓存,并把路径交给你。这个请求写在 `mcpp.toml` 而不是这里,理由和依赖本身
116+
一样:向依赖图索取一个额外产物是**图级别**的请求,而图必须保持可静态分析。
117+
完整契约(含 `[tools.overrides]`)见 [05 §2.14](05-mcpp-toml.md)
118+
119+
### 声明工作而不是干活:`mcpp::action`(2026.8.5.1+)
120+
121+
**这里**直接把源码写出来是省事的路,超过一定规模就是错的:它每次 prepare 跑
122+
一遍、全量、串行,失败还只报「build.mcpp exited 1」。**声明**这份工作,它就成为
123+
构建图里的一条边 —— 增量、并行,失败能归因到具体那条边。
124+
125+
```cpp
126+
import mcpp;
127+
int main() {
128+
const std::string out = std::string(mcpp::out_dir()) + "/foo.pb.cc";
129+
mcpp::action a;
130+
a.id = "protoc:foo";
131+
a.role = "source"; // "source" | "check" | "artifact"
132+
a.arg(mcpp::dep_bin("protobuf", "protoc"))
133+
.arg("--cpp_out=...").arg("proto/foo.proto")
134+
.input("proto/foo.proto")
135+
.output(out.c_str())
136+
.submit();
137+
}
138+
```
139+
140+
三种 role,一个原语 —— `role` 只决定这条边的输出接到哪:
141+
142+
| `role` | 输出 | 顺序 | 典型 |
143+
|---|---|---|---|
144+
| `source` | 进编译集 | 编译边消费它们 | protoc、转译器 |
145+
| `check` | 一个 stamp 文件 | **与编译并行**(`blocking = true` 才前置) | clang-tidy、格式/ABI 检查 |
146+
| `artifact` | 一个新文件 | 它的**输入**是链接产物,所以在链接之后跑 | 签名、打包、size budget |
147+
148+
全程不涉及任何 phase 机制:顺序由 ninja 自己的文件依赖决定 —— 这也是为什么
149+
`artifact` 不会像朴素的「post 构建钩子」那样把自己重复施加一遍。
150+
151+
**必须写出输出文件名。** mcpp 在 prepare 期就定死源码集、fingerprint 与模块图,
152+
所以名字未知的产物无法构建。内容可以晚到,名字不行。畸形 action 是**硬错误**,
153+
绝不静默跳过。
154+
155+
生成**模块接口**时,把它的接口也声明出来:
156+
157+
```cpp
158+
a.output(gen.c_str()).provides("my.generated").imports("std").submit();
159+
```
160+
161+
mcpp 会播下一个带着该声明的占位文件,使 prepare 期的扫描与你的生成器将要产出的
162+
内容一致 —— 与 `[modules].scan_overrides` 同一条「声明 + 验证」的取舍,build 期由
163+
编译器自己的 P1689 输出复核。
164+
165+
命令是 **argv 而不是 shell 字符串**(不假设存在 shell —— Windows 没有能依赖的那个),
166+
插值只有封闭的一组:
167+
168+
| 变量 | 含义 |
169+
|---|---|
170+
| `${mcpp.out_dir}` | 构建输出目录 |
171+
| `${mcpp.bin_dir}` | 产出的二进制所在目录 |
172+
| `${mcpp.compile_db}` | `compile_commands.json` 的路径(clang-tidy 的 `-p` 要的就是它) |
173+
| `${mcpp.target_file:<name>}` | target `<name>` 构建出的文件 |
93174

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

0 commit comments

Comments
 (0)