fix: support .ccm/.cxxm/.ixx module interface extensions - #272
Closed
ZheFeng7110 wants to merge 2 commits into
Closed
fix: support .ccm/.cxxm/.ixx module interface extensions#272ZheFeng7110 wants to merge 2 commits into
ZheFeng7110 wants to merge 2 commits into
Conversation
Member
|
这几个后缀比较小众, 先保留PR 后面看是否有很多相关项目真实使用 到时候 再评估是否再合入, 目前先以推荐 cppm / cpp 为推荐 看是否可以形成共识 简化表达 |
Member
Author
|
好的。 但我还是认为最好能支持这些后缀,因为 C++ 社区的代码风格就是比较多样化:甚至有模块接口单元就是普通的 并且目前的这个设计也还存在缺陷:我尝试过使用 .ccm ,最终在构建的时候 ninja 提示“模块无法找到”。最后让 AI 查看了源代码才发现是 mcpp 不支持使用 .ccm 作为后缀,最后通过代码生成绕过了这个问题。既然希望让使用“cpp/cppm”形成共识,那应该直接提示用户“必须使用 .cppm 作为模块后缀名”,而不是编译失败提示“找不到模块”。 附上 AI 分析的原因: 根据对 plan.cppm 源码的分析,问题出在 plan.cppm 中两处硬编码的 .cppm 扩展名判断导致 .ccm 文件的对象文件永远不会被链接:
根本原因:plan.cppm 的链接单元组装逻辑
plan.cppm:make_plan() 为每个 link unit 添加对象文件时有两个循环:
1. 模块接口对象循环(.ccm 被遗漏)
// plan.cppm — 添加模块接口单元的对象
for (auto& cu : plan.compileUnits) {
if (sharedDepPackages.contains(cu.packageName)) continue;
if (cu.source.extension() == ".cppm") { // ← 只匹配 .cppm
lu.objects.push_back(cu.object);
}
}
2. 实现源文件循环(.ccm 也不在列表中)
bool is_implementation_source(const std::filesystem::path& source) {
auto ext = source.extension();
return ext == ".cpp" || ext == ".cc" || ext == ".cxx" || ext == ".c" || ext == ".m"
|| ext == ".S" || ext == ".s" || ext == ".asm";
// ↑ 没有 .ccm
}
后果
扫描器能正确识别 .ccm 中的 export module 声明 → 它会被加入 plan.compileUnits
Ninja 会为它生成编译规则(生成 .o)→ 编译阶段不报错
但 .ccm 的 .o 永远不会被加入任何 linkUnit.objects → 链接阶段缺少该模块的符号
最终二进制中找不到模块,触发 "module not found" 错误
解决方案
方案一:把 .ccm 文件重命名为 .cppm(mcpp 唯一官方模块扩展名)
方案二:修改 plan.cppm,将扩展名判断改为语义判断:
把 cu.source.extension() == ".cppm" 改为 cu.providesModule.has_value()(基于内容而非扩展名)
把 is_implementation_source() 改为白名单取反(providesModule 为空且不是 C/汇编)
方案三:手动在 [build].sources 中把 .ccm 加入 glob,并在 mcpp 仓库提 PR 修复这两处硬编码。 |
Member
|
目前可以先使用 build.mcpp 进行 rename 后缀, 后面进行评估 真实按理备注 |
This was referenced Aug 8, 2026
Sunrisepeak
added a commit
that referenced
this pull request
Aug 11, 2026
* feat: 源文件角色表 与 build.mcpp 运行上限 —— 两个硬编码变成两条声明 (#272, #410) (2026.8.11.1) 把「哪个扩展名是模块接口」和「build.mcpp 能跑多久」这两个决策从代码里拿出来, 变成 mcpp.toml 的两条声明,并顺手把它们背后的架构债与跨平台缺口补上。 新增 [build] module_extensions:additive 到内置 .cppm。声明一个扩展名会同时 让默认 sources glob 找到它、让它走模块规则(产 BMI、.o 无条件进链接)、并让 新鲜度快路径扫描它 —— 一个键而不是三处配置。拒绝已代表其他角色的扩展名。 新增 [build] build_program_timeout:优先级 env > 该包自己的 manifest > 内置 600s。超时报错点名要改的那份 mcpp.toml —— 依赖超时时改自己的那份不会有效果。 optional 承重:int 的话「没写」与「写 0」不可区分,而 0 意为不限。 架构:「扩展名 → 角色」原本在 9 个文件 20 处推导、8 份互不一致的清单。现在 分类只发生一次(SourceUnit::kind → CompileUnit::kind),下游读字段。#272 修了 链接侧却漏了 pick_rule —— 边上声明 BMI 而命令行丢了 -fmodule-output=。 实测(GCC 16.1 / Clang 22.1):Clang 根本不认 .ixx,把它当链接输入、退出码 0、 不产 BMI。而显式旗标在已识别后缀上幂等(Clang .cppm 的 BMI 逐字节相同)。 ⇒ 不维护「谁认哪个后缀」这张会过期且错了静默的表,永远显式告诉编译器。 跨平台:capture_exec_deadline 此前只在 POSIX 生效,Windows 直接回落无界路径, 于是 mcpp test --timeout / --build-timeout / 这个新键在那里全是空操作。现在 两侧各有实现(Windows 用 Job object 杀整棵树,否则孙进程攥着捕获管道会让 杀掉之后的读取挂住),process.cppm 单点 if constexpr 分派。 src/platform/ 拆成 unix/ windows/ linux/ macos/。 可观察性:mcpp self doctor 报告生效的扩展名表、超时值及其来源、deadline 是否 真的强制;module_extensions 零命中的条目告警。 顺带修掉三个发现的缺陷:isModuleInterface/isImplementation 是写而不读的死字段 (5 写 0 读、3 份不一致推导)⇒ 删除;is_implementation_source 漏 .mm 导致 Objective-C++ 对象永不进链接;stage 兜底 glob 漏全部三种汇编扩展名。 兼容:未配置时构建图零差分(同样的文件、BMI、链接对象、指纹目录)。 module_extensions 进指纹(改图形态),build_program_timeout 不进(不改任何边)。 设计:.agents/docs/2026-08-11-source-kind-table-and-build-program-timeout.md * fix(platform): 未捕获的有界运行必须继承 stdio,而不是先缓冲后回放 自审发现的真回归。`run_exec_deadline` 是 `mcpp test` 非 JSON 模式跑测试二进制 的路径,原本继承调用方的 stdio —— 输出实时出现,且子进程的 stdout 是终端。 把它改成「捕获后在结束时一次性回放」有两个后果: 1. 长测试的输出全部憋到退出才出现,恰好抵消了 `mcpp test` 可观察性那一整 轮工作(「只有子进程输出、mcpp 一行没有」正是缓冲问题的指纹); 2. 子进程的 stdout 变成管道而非终端,gtest 之类会静默关掉彩色输出。 两侧启动器现在共用一条契约:`sink == nullptr` 表示「不捕获」,子进程直接继承 调用方的 stdio,但**仍然有界**。POSIX 侧不建管道也不设 dup2 file action; Windows 侧不设 STARTF_USESTDHANDLES,也不加 CREATE_NO_WINDOW(未捕获的运行 本来就是要给人看的)。`dispatch_bounded` 多一个 capture 形参把这个选择传下去。 * fix(cache): module_extensions 进依赖缓存键的 E 轴 指纹管的是 target/<triple>/<fp>/,全局依赖缓存是另一套键。一个声明了 module_extensions 的依赖产出不同的 .o/BMI,它的缓存键必须体现这一点。 今天不可达 —— 默认 glob 会跟着变,sourceGlobs 已经动了;索引包描述符按版本 冻结,version 在 D 轴。但这正是本 PR 在消灭的形状(同一决策漏一处),一行补上 比留着等它以后变成一次错误的缓存命中便宜。 不 bump epoch:老条目命令行里没有 -x c++,而该旗标在已识别后缀上幂等(产物逐 字节相同),沿用安全,不必让全网缓存作废。 * fix(platform/windows): 捕获路径必须把 stdin 封成 NUL 自审发现。被替换掉的 Windows 捕获路径经 _popen 走 shell,命令行尾部带 '< NUL' —— 这个文件的同伴 mcpp.platform.process 头注释就点名了原因:xlings / xim / curl / git 子进程在 bootstrap 期间阻塞在终端 stdin 上,逼用户反复敲回车。 新实现把父进程的 console stdin 直接透传给了被捕获的子进程,会静默把那个挂起 带回来。改为从 NUL 打开;打不开时退回 console 句柄而不是交一个无效句柄 ——「完全没有 stdin」的失败长得一点也不像「stdin 没被封」。 未捕获的子进程保持继承真实 stdin,与 run_exec 一致:mcpp run 就是要把终端交 给程序。 * refactor(prepare): 扩展名表按包建一次,不再每个生成物重建一次 * fix(manifest): 自动推断的 lib 备注要说出实际找到的那个扩展名 macOS e2e 抓到的:我把备注从「lib from .cppm in src/」改成了「lib from module interface in src/」,25_convention_mode.sh 断言的是前者。 这条我判断为文案改得不够好,而不是测试过时。备注该说的是**实际找到了什么**: .cppm 工程输出与从前逐字不变(那条断言原样通过),而声明了 module_extensions 的工程会看到「lib from .ixx in src/」—— 说 .cppm 才是名不副实。 顺带 CHANGELOG 补 2026.8.11.1 条目。 * fix(e2e): 218 的指纹断言不能靠 find|head -1 推目录 自审 + 全量套件抓到的:第 2 部分之后 target/ 下有两个输出目录, `find target -name build.ninja | head -1` 取到的是 find 恰好先走到的那个 —— 单跑绿、进套件红。改成直接问 mcpp 要指纹值(--print-fingerprint), 断言那个值本身,而不是一个目录列举的副作用。 (这正是「指纹目录随版本变,ls|head -1 会自查到旧产物」那条老坑的同一形状, 在一个专门用来抓这类问题的测试里又踩了一次。) * docs: 补实施记录 —— CI 19/19、本机 12 条失败的逐条对照结论、以及我在自己测试里重踩 head -1 的教训 --------- Co-authored-by: speak-agent <248744407+speak-agent@users.noreply.github.com>
Member
|
由于一些特殊后缀是编译器特有的, 所以没有做成内置的。而是 做成了 开发者 可配置功能。根据自己项目情况 进行配置 支持的 后缀。
可以用最新版本尝试, 如果有问题可以创建相关issue或相关的PR。该PR暂时先关闭 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
plan.cppmhardcoded.cppmextension checks, causing non-.cppmmodule interface files (.ccm,.cxxm,.ixx) to have their object files excluded from link units, resulting in "module not found" linker errors.Changes
src/build/plan.cppm: Link object collection now usescu.providesModulesemantic check instead of extension check;object_filename_fortreats all 4 module extensions equally with.mprefixsrc/build/execute.cppm: Freshness check now recognizes.ccm/.cxxm/.ixxtests/unit/test_module_extensions.cpp(new): Unit tests forobject_filename_fordisambiguationtests/e2e/152_module_extensions.sh(new): End-to-end build→link→run test for all 4 extensionsTest plan
mcpp buildself-host passes