Skip to content

Commit 5468cc3

Browse files
committed
docs(design): §13 — the second round, and what it turned up
Records the outcome of §12's five items under the single-PR decision (O1 not split, the rest done), plus the two real defects the work forced out: the kind="shared" guard was inert on native builds — the very case it was written for, since targetTriple is empty there — and `--target` accepted a target this host cannot produce, resolving the native g++ and delivering an ELF as a Windows build. Also the shape they share: mingw's ld tolerates linking a .dll directly, so "a PE shared library is two files" was never exposed. The tolerant case was hiding the broken one.
1 parent fe62962 commit 5468cc3

1 file changed

Lines changed: 125 additions & 0 deletions

File tree

.agents/docs/2026-08-17-library-distribution-design.md

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -782,3 +782,128 @@ clang 构建的二进制,而 clang 构建的 mcpp 在本机会段错误)。
782782
| **O3** | MSVC 的 `/REMOVE:` 路径**未测试**(mcpp 的 Windows CI 用 clang 的 llvm-ar,走 GNU 形式) | 失败会带命令原文报错,不会静默;要真测需要 `msvc@system` 的 job |
783783
| **O4** | Windows 上的胖包(msvc + mingw)未覆盖 | 见 §12.1;要在 Windows e2e job 装 `xim:mingw-gcc` |
784784
| **O5** | 设计 §2.2 承诺的「`--target` 集合与 `[package].platforms` 覆盖比对告警」**没有实现** | 我漏了。它便宜(一次集合比对 + 一条 warning),但属于「发布纪律」而不是正确性,可以随后补 |
785+
786+
787+
---
788+
789+
## 13. 第二轮:把 §12 的五项做完(2026-08-18,同一个 PR #451)
790+
791+
裁决是「统一在单 PR 451 上实现最大兼容和支持度」,所以 O1(拆 PR)**不做**,
792+
其余四项全部落地,并顺带补上了两个在做的过程中被逼出来的真缺陷。
793+
794+
### 13.1 §12 五项的结局
795+
796+
| # || 结局 |
797+
|---|---|---|
798+
| O1 | 把扫描器改动拆成独立 PR | **不做** —— 单 PR 是裁决。代价照旧:revert 粒度与库分发绑在一起 |
799+
| O2 | `scan_overrides` 声明的实现分区被误判成接口 | **已修,而且没加 manifest 键**(见 §13.2) |
800+
| O3 | MSVC `/REMOVE:` 未测试 | **两端都测了**:`archive_remove_command` 抽出成缝 + 单测钉命令,e2e 255 用真 `lib.exe` |
801+
| O4 | Windows 胖包未覆盖 | **e2e 256**(msvc 腿 + mingw 腿同处一包),Windows e2e job 增加 `xim:mingw-gcc` 安装步与 `mingw` 能力 |
802+
| O5 | `[package].platforms` 覆盖告警未实现 | **已实现**(见 §13.3) |
803+
804+
### 13.2 O2 的解法:三态,而且零新键
805+
806+
`SourceUnit::providesInterface``bool` 改成 `std::optional<bool>`
807+
三条建图路径各说自己真的知道的事:
808+
809+
| 路径 | 它知道什么 |
810+
|---|---|
811+
| 文本扫描器 | 读到了关键字 ⇒ 显式 true / false |
812+
| P1689 读取器 | 编译器的答案,**包括它的沉默**(`is-interface` 是可选键) |
813+
| `scan_overrides` | **什么都不知道** ⇒ 留空。schema 里没有地方说 export |
814+
815+
**判据:`true` 是那个「不产生任何警告」的值。** 字段原来的注释把默认 `true`
816+
叫「保守方向,因为这个标志只会产生一条警告」—— 说反了,所以用
817+
`[scan_overrides]` 声明的实现分区被一声不响地发布,而那正是整个闭包设计要防的事。
818+
819+
两个细节让告警可用:
820+
- **只问分区。** `module M;` 不 provide 任何东西,所以能 provide 裸 `M` 的只有
821+
`export module M;` —— 在那里问会对每个用了 override 的包的每个主接口都告警;
822+
- **未知与已知说不同的话。** 一条是「你正在发布你的实现」,另一条是
823+
「mcpp 判不出你是不是在发布」。
824+
825+
**P1689 那一半是真修复而不是变通**:编译器早就报了 `is-interface`,
826+
mcpp 解析进了一个从来没人读的字段。
827+
828+
e2e 253 **两侧都钉**:同一个 fixture 打两次(扫描 / override),两次必须给出
829+
**不同的句子** —— 只断言 override 会告警,分不清「mcpp 建模了三态」与
830+
「mcpp 对每个发布的分区都告警」。**控制探针验证过**:把 override 那一处恢复成
831+
`true`(其余修复保留),253 在正好那条断言上失败。
832+
833+
### 13.3 O5 的解法:判据是 `host_can_serve`,难点是「别嚷嚷」
834+
835+
四种比较只有两种能打印:
836+
837+
| 情况 | 处理 |
838+
|---|---|
839+
| 打了却没声明 | 永远可行动 —— manifest 否认了一个包明明能服务的平台 |
840+
| 声明了却没打,**且本宿主能构建它** | 可行动 |
841+
| 声明了却没打,本宿主构建不了 | **不说话** |
842+
843+
第三行才是这个检查可用的原因:正常流程是 CI 每平台各跑一次 `mcpp pack`,
844+
Linux runner 不产 macOS 腿不是遗漏、是每一次。**永远触发的告警会把真正该看的那条盖掉。**
845+
判据用 `host_can_serve` —— 与 `--target` 接受什么是同一个函数,所以告警只可能
846+
点出作者此刻做得到的事。e2e 254 **把沉默也断言了**
847+
848+
### 13.4 顺带被逼出来的两个真缺陷
849+
850+
**(a) `kind = "shared"` 的守卫在最要紧的情形下失效。**
851+
它写的是 `!targetTriple.empty() && os != "linux"`,而**原生构建的 targetTriple 是空的**
852+
⇒ 它拦住了一个交叉到 macOS 的构建(那本来就不可服务、不可达),
853+
却让**原生 macOS / 原生 Windows** 直接走进它本该拦住的未验证路径。
854+
855+
修完守卫之后,真正缺的东西各不相同,而且**都不是 flag 拼写**:
856+
857+
| 格式 | 缺什么 | 现在 |
858+
|---|---|---|
859+
| PE | **导入库** —— 只写了 `.dll`,消费者直接链 `.dll`,mingw 的 ld 容忍、别人都不容忍 | 链接边把导入库声明成**隐式输出**;包里两个都带;`-static` 会让 ld 拒绝导入库(报 `have you installed the static version…`,既没点 DLL 也没点 `-static`),所以那条 `-l` 前先 `-Wl,-Bdynamic` |
860+
| Mach-O | **install name** —— 只在声明了 `soname` 时才发,于是其他每个 `.dylib` 都把构建目录烙了进去 | 无条件 `@rpath/<file>`;而且改成**按 target 判定**(原来是宿主的 `#if defined(__APPLE__)`) |
861+
| PE/MSVC | **符号导出**(不是链接器 —— `link /DLL /IMPLIB:` 一直都在) | 仍然拒绝,但换成真理由,并点名 MinGW 这条路 |
862+
863+
⚠️ **「能用的那种情况把坏掉的那种遮住了」是这一整块的形状。**
864+
mingw 的 ld 容忍链 `.dll`,所以「PE 共享库是两个文件」这件事从来没被暴露。
865+
866+
**(b) `--target` 接受了这台机器产不出来的 target,并悄悄按宿主构建。**
867+
实测(Linux):
868+
869+
```
870+
$ mcpp build --target x86_64-windows-msvc
871+
Resolved gcc@16.1.0 → x86_64-windows-msvc → …/xim-x-gcc/16.1.0/bin/g++
872+
Finished dev [unoptimized + debuginfo] in 0.07s
873+
$ ls target/
874+
x86_64-linux-gnu/ ← 一个 ELF,被当成 Windows 构建交付
875+
```
876+
877+
**词表的 tier 说的是「mcpp 支持这个 target」,从来没说「这台机器能产出它」。**
878+
`host_can_serve` 此前**只用于 `toolchain list` 的展示**,构建路径根本没问它。
879+
现在 `prepare.cppm` 在紧邻那条「typo 绝不能悄悄回落到宿主工具链(最坏的失败模式)」
880+
的检查之后问它,并把**这台宿主能构建的清单**列进错误里。逃生口保留:
881+
显式 `[target.X] toolchain = "…"` 表示交叉链是作者自己提供的。
882+
883+
**(c) 消费一个 `shared` 的分发包直接失败**:`ninja: multiple rules generate
884+
bin/libmathkit.dll` —— 依赖循环为一个**已经构建好**的库创建了 link unit。
885+
就算不撞名也是错的:分发包的 `sources` 只有它发布的接口,重链会静默产出一个
886+
**缺掉发布方保留的每个实现单元**的库。
887+
888+
### 13.5 覆盖矩阵(第二轮之后)
889+
890+
| e2e | linux | macOS | windows | 备注 |
891+
|---|---|---|---|---|
892+
| 242/243/244/246/247/249/250/251/253/254 |||| 无能力门 |
893+
| 245 胖包(gnu+musl) || 结构性不可能 || `# requires: gcc musl`(补上了 musl) |
894+
| 256 胖包(msvc+mingw) || 结构性不可能 || `# requires: msvc mingw`;job 里装 `xim:mingw-gcc` |
895+
| 248 跨 OS 胖包(PE 腿) |||| mingw job |
896+
| 255 `lib.exe /REMOVE:` |||| `# requires: msvc` |
897+
| 257 PE 共享库(产出+打包+跑) | ✅ wine ||| `# requires: mingw-cross wine` |
898+
| 258 MSVC 拒绝 `shared` |||| `# requires: msvc`;在 Linux 上**看不到**(target 门先答) |
899+
| 259 Mach-O 共享库可重定位 |||| `# requires: macos`;**先删掉发布方的构建树**再消费 |
900+
901+
**macOS 那两格是产品事实不是测试缺口**:它只能服务一个 target。
902+
903+
### 13.6 第二轮仍然开着的
904+
905+
| # || 说明 |
906+
|---|---|---|
907+
| **P1** | 打包库**不能被原生 `cl.exe` 消费** | 生成的 manifest 用 `-L`/`-l`(GNU 拼写)。**改成写文件路径也不行**:ninja 的链接命令 cwd 是输出目录,只有 include 家族前缀会被相对包根绝对化,无前缀 token 会去错地方找(`ld: cannot find lib/…/libmathkit.a`),而绝对路径就不再可重定位。要补齐需让条件通道承载 `link_library_dirs`/`libraries` —— mcpp 已能按方言渲染,只是只在顶层读 |
908+
| **P2** | MSVC 的 `kind = "shared"` | 要生成 `.def`(对对象做符号扫描)—— 那是一个构建图节点,不是 flag |
909+
| **P3** | 扫描器改动的 revert 粒度 | O1 的代价,按裁决接受 |

0 commit comments

Comments
 (0)