Skip to content

fix(build.mcpp): 规则包能用 import std; 与 import mcpp; (2026.8.5.2) - #357

Merged
speak-agent merged 3 commits into
mainfrom
fix/host-module-rules-usable
Aug 5, 2026
Merged

fix(build.mcpp): 规则包能用 import std; 与 import mcpp; (2026.8.5.2)#357
speak-agent merged 3 commits into
mainfrom
fix/host-module-rules-usable

Conversation

@speak-agent

Copy link
Copy Markdown
Member

问题

host-module = true 是 2026.8.5.1 加的:一条构建规则以普通 mcpp 包分发,消费者在 build.mcppimport 它。机制成立,但两个缺陷叠在一起,使它只能承载「手工 printf 指令」的玩具规则 —— 规则一旦去用它本该用的那套 API 就编不过。

第一个真实使用者(grpc-m 的 protoc/gRPC codegen 规则)在第一分钟同时撞上了两个。

e2e 189 一直是绿的,因为它的规则只 #include <cstdio> 再 printf —— 恰好把两个缺陷都绕开了。这条 PR 里它被扩到覆盖真实用法。

1. 规则里的 import std;

build_program.cppm 先编 host module、后建 std 模块,规则拿到的 stdFlags 是空的:

fatal error: module 'std' not found

而且「是否需要 std」只扫 build.mcpp 的源码 —— 一条只有规则import std; 的构建根本不会去建 std 模块。

修法:扫描把规则接口一并计入(usesStd / usesStdCompat / usesModule 三者取并集),std 那一段整体挪到编译 host module 之前,stdFlags 传进 build_host_moduleextraUseFlags

顺序是承重的,不是风格问题:BMI 必须先存在、stdFlags 必须先指向它,规则才可能 import 它。

2. 规则里的 import mcpp;

host-module = true 只做了「注册这个模块」。prepare.cppm 从头到尾只 spec.hostModule,从没把这个包移出消费者的普通依赖图 —— 同一个 .cppm 又被当成消费者的一个普通库编了一遍,而那次编译里内置 mcpp 模块并不存在:

failed: obj/grpcgen/src/grpcgen.m.o
fatal error: module 'mcpp' not found

修法:一条构建规则是纯构建期的东西,本来就不该进消费者的二进制 —— Cargo 用 [build-dependencies] 划的是同一条界线。只经由 root 的 host-module 边到达的包会被清空源码集(与 feature-gated sources 的丢弃用同一套机制),不再参与编译与链接。解析不动,因为 lib 根要从磁盘上读。

有护栏:同一个包完全可以既是规则、又是别处(或 root 另一种拼法)的普通库。只在「指向它的边全部来自 root」时才清空 —— 否则会变成一个离现场很远的 undefined reference。

效果

消费者的 build.mcpp 从约 60 行降到 3 行,而那 60 行搬进了一个有版本、能测试、能发布的包:

import mcpp;
import grpcgen;
int main() { return grpcgen::generate({"helloworld"}) ? 0 : 1; }

验证

  • e2e 189 扩了两条断言,并且先验证过它们在 2026.8.5.1 上是红的(不是空断言):
    • 一条同时 import std;import mcpp; 的规则必须能建,指令必须落地;
    • obj/rules/**.o 必须不存在 —— 规则包被当普通库编译过就会留下它。
  • 本地 e2e 全量:除 03_multi_module / 07_static_library / 09_path_dependency 三条既有的环境性失败外全绿(已对 released 2026.8.4.1 核对过,与本改动无关)。
  • 真实回归:grpc-m 的 grpcgen 规则包在打了本 PR 的二进制上跑通,生成→编译→链接→真实 RPC 全链路。

文档

docs/05-mcpp-toml.md 的示例写的是 import mcpp.rules.protobuf;,做不到:mcpp 用依赖的裸 package.name 注册 host 模块,而 SPEC-001 要求 name 是单一原子段。顺带写明一条会咬人的约束:规则包的名字必须是合法 C++ 模块名(grpcgen 可以,grpc-rules 不行),否则报的是 module 'grpc_rules' not found,不会提示你名字有问题。中英文档同步。

版本

2026.8.5.2bootstrap pin 不动(仍 2026.8.4.1)—— 它是自举起点,不随发布走。

`host-module = true` 是 2026.8.5.1 加的:一条构建规则以普通 mcpp 包分发,消费者
在 build.mcpp 里 `import` 它。机制本身成立,但两个缺陷叠在一起,使它**只能承载
手工 printf 指令的玩具规则** —— 规则一旦去用它本该用的那套 API 就编不过。

第一个真实使用者(grpc-m 的 protoc/gRPC codegen 规则)在第一分钟同时撞上两个。
e2e 189 之所以一直是绿的,是因为它的规则只 `#include <cstdio>` 再 printf,恰好
把两个缺陷都绕开了。

── 1. 规则里的 `import std;` ────────────────────────────────────────────────
`build_program.cppm` 先编 host module、后建 std 模块,规则拿到的 `stdFlags` 是
空的,报 `module 'std' not found`。而且「是否需要 std」只扫 build.mcpp 的源码,
所以一条**只有规则**用 `import std;` 的构建根本不会去建 std 模块。

两处都修:扫描把规则接口一并计入(usesStd / usesStdCompat / usesModule 三者都
是并集),std 那一段整体挪到编译 host module **之前**,并把 stdFlags 传进
build_host_module 的 extraUseFlags。顺序是承重的,不是风格:BMI 必须先存在、
stdFlags 必须先指向它,规则才可能 import 它。

── 2. 规则里的 `import mcpp;` ───────────────────────────────────────────────
`host-module = true` 只做了「注册这个模块」。`prepare.cppm` 从头到尾只**读**
`spec.hostModule`,从没把这个包移出消费者的**普通依赖图** —— 于是同一个 `.cppm`
又被当成消费者的一个普通库编了一遍,而那次编译里内置 `mcpp` 模块并不存在:

    failed: obj/grpcgen/src/grpcgen.m.o
    fatal error: module 'mcpp' not found

一条构建规则是纯构建期的东西,本来就不该进消费者的二进制 —— Cargo 用
`[build-dependencies]` 划的是同一条界线。现在只经由 root 的 host-module 边到达
的包会被清空源码集(与 feature-gated sources 的丢弃用同一套机制),不再参与编译
与链接;解析不动,因为 lib 根要从磁盘上读。

**有护栏**:同一个包完全可以既是规则、又是别处(或 root 另一种拼法)的普通库。
只在「指向它的边全部来自 root」时才清空,否则会变成一个离现场很远的 undefined
reference。

── 测试 ─────────────────────────────────────────────────────────────────────
e2e 189 扩了两条断言,并且**先验证过它们在 2026.8.5.1 上是红的**:
- 一条同时 `import std;` 和 `import mcpp;` 的规则必须能建、指令必须落地;
- `obj/rules/**.o` 必须不存在 —— 规则包被当普通库编译过就会留下它。

本地 e2e 全量:除 03/07/09 三条既有的环境性失败外全绿(已对released 2026.8.4.1
核对过,与本改动无关)。

── 文档 ─────────────────────────────────────────────────────────────────────
`docs/05-mcpp-toml.md` 的示例写的是 `import mcpp.rules.protobuf;`,**做不到**:
mcpp 用依赖的裸 `package.name` 注册 host 模块,而 SPEC-001 要求 `name` 是单一
原子段。顺带写明一条会咬人的约束:规则包的名字必须是合法 C++ 模块名(`grpcgen`
可以,`grpc-rules` 不行),否则报的是 `module 'grpc_rules' not found`,不会提示
你名字有问题。中英文档同步。

bootstrap pin 不动(仍 2026.8.4.1):它是自举起点,不随发布走。
aarch64 fresh-install job 在 clone 出 mcpp 源码后直接死在:

    [error] xlings: version '2026.8.4.1' not found for 'mcpp'
    [error]   available: 2026.8.5.1

`.xlings.json` 的 bootstrap pin 停在 2026.8.4.1,而 **xim-pkgindex 只保留一个
mcpp 版本**。2026.8.5.1 一发布,2026.8.4.1 就不再可安装 —— 于是「从已发布的
mcpp 自举」这条路当场断掉。

pin 挪到 2026.8.5.1(= 当前最新已发布版本)。

顺带把这条不变式写进 check_version_pins.sh:此前那段注释只讲了**上界**
(「绝不能跑在被构建版本前面」,那是它自己踩过的坑),没讲**下界**。两边合起来
才是完整规则:**发布 N 之后,发布收尾的那个 commit 把 pin 设成 N** —— 不能超前
(check (c) 机器校验),也不能落后超过一个版本(校验不了,它需要索引,所以只会以
上面那条错误现身;注释里写清楚了下次去哪儿看)。
「Self-host — build mcpp + xlings from source」这一步固定克隆上游 main。于是
这道关卡从来没有验证过 PR 本身:一个会破坏「从源码自举」的改动能顺利通过它,
只在合入之后才炸。

这次是以最刺眼的方式暴露的 —— 上一个 commit 修的正是本仓库的 bootstrap pin,
而那个修复**无法被验证**:修复在分支上,克隆的却是 main,于是 job 继续报
`version '2026.8.4.1' not found`。

PR 上改为自举**被评审的那份代码**;非 PR 场景没有 head ref,main 本来就是对的。
用 fetch-by-sha 而不是 `clone --depth 1`(后者不接受 sha)。
@speak-agent
speak-agent merged commit 172408a into main Aug 5, 2026
18 of 19 checks passed
@speak-agent
speak-agent deleted the fix/host-module-rules-usable branch August 5, 2026 09:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant