日期: 2026-06-01 状态: draft 范围: 让
standard = "c++26"这类语言标准配置贯通 manifest、fingerprint、编译 flags、import stdBMI cache、CDB 和文档。
这不是单个 -std=c++23 硬编码点的问题,而是 mcpp 同时存在四套没有闭环的语言标准表达:
- manifest 有
[package].standard和旧[language].standard两个入口。 - fingerprint 有
cppStandard字段,但构建命令没有真正消费同一个值。 - backend 仍把 C++ 标准当作全局 flags 字符串的一部分,并且把用户
cxxflags同时放进全局和 compile unit。 import stdBMI cache 只看产物是否存在,构建命令和 cache 元数据都没有记录实际 C++ 标准。
合理修法是把 C++ 标准提升为一个归一化后的 build graph 属性: manifest 只负责解析和兼容旧字段,prepare/build plan 只传递一个已归一的 active standard,所有 compiler flag、fingerprint、std module build、cache metadata 和文档都消费这个同一个值。
src/manifest.cppm:23-28:Package::standard默认是c++23。src/manifest.cppm:34-38:Language::standard也默认是c++23。src/manifest.cppm:319-331:[package].standard是新 home,旧[language].standard会镜像到 package。src/manifest.cppm:335-339: 统一后的校验仍只允许c++23和c++latest,错误信息仍说 MVP only supports c++23。
这说明 schema 已经有新字段,但合法值、归一化、调用侧消费还没有完成。
src/toolchain/fingerprint.cppm:23-29:FingerprintInputs::cppStandard默认c++23。src/toolchain/fingerprint.cppm:101: fingerprint 第 6 字段记录cppStandard。src/cli.cppm:2505-2512: build 准备阶段把m->language.standard写入 fingerprint。src/cli.cppm:584-604: canonical compile flags 字符串也拼了-std=<m.language.standard>。
问题是实际 C++ 编译命令不读这个值,fingerprint 记录和真实 compiler dialect 可以分裂。
src/build/flags.cppm:221-224: C 标准已经有build.c_standard模型。src/build/flags.cppm:254-256: C++ 规则仍硬编码-std=c++23,用户只能用build.cxxflags = ["-std=c++26"]追加覆盖。
这会产生两个问题:
- 标准配置没有一等入口,只能绕进附加 flags。
- 当
cxxflags后追加-std=c++26时,fingerprint 和 std module cache 仍可能以c++23的世界观工作。
src/toolchain/stdmod.cppm:46-49:ensure_built()只接收 toolchain、fingerprint 和 cache root。src/toolchain/stdmod.cppm:149-177: 复用判断只检查 BMI 和 object 是否存在。src/toolchain/gcc.cppm:105-120: GCC std module build command 固定-std=c++23。src/toolchain/clang.cppm:174-228: Clang std module build command 也固定-std=c++23。src/toolchain/clang.cppm:265-296:std.compatbuild command 同样固定-std=c++23。
所以项目源文件最终用 C++26 编译时,GCC/Clang 仍可能读到 C++23 预编译出的 std BMI。GCC 报 language dialect differs 'C++23', expected 'C++26/contracts' 是这个裂缝的直接结果。
src/modgraph/scanner.cppm:376-379: scanner 把每个 package 的cflags/cxxflags放到 compile unit。src/build/ninja_backend.cppm:475-476,src/build/ninja_backend.cppm:523-527,src/build/ninja_backend.cppm:570-574: Ninja 再把 per-unit flags 写入unit_cxxflags/unit_cflags。src/build/compile_commands.cppm:120-125: CDB 也追加 per-unit package flags。src/build/flags.cppm:213-214,src/build/flags.cppm:254-258: root manifest 的cxxflags/cflags仍被加入全局 baseline。
这导致主包 flags 可能在 cxxflags 和 unit_cxxflags 中各出现一次。对 -std= 这种有顺序语义的 flag 来说,重复不仅是噪音,还会掩盖标准模型缺失。
docs/05-mcpp-toml.md:40-48:[package]示例没有standard。docs/05-mcpp-toml.md:67-78:[build]只写了c_standard,没有 C++ 标准字段说明。docs/05-mcpp-toml.md:281-290: 默认值表只说标准默认c++23,没有说明怎么配置。
用户自然会把 C++ 标准塞进 cxxflags,因为文档没有给出一等配置路径。
standard = "c++26"是公开、稳定、文档化的配置,不再需要cxxflags = ["-std=..."]。- 同一次 build graph 中,所有 C++ compile unit、module scan、std BMI 构建、CDB 都使用同一个 active C++ standard。
- std module cache 复用前验证真实构建元数据,而不是只看
std.gcm/std.pcm是否存在。 - fingerprint 使用归一化标准值,避免
c++2c和c++26这类别名产生无意义缓存分裂。 cxxflags回归到附加编译参数语义,不承担语言标准选择职责。- 保持旧
[language].standard和 xpkg Lualanguage = ...兼容,但内部尽快归一到新模型。
- 不在本轮引入每个 package 独立 C++ 标准。C++ module BMI 和
import std的 dialect 兼容性要求太强,第一阶段应采用 graph-wide active standard。 - 不把
c++latest变成可复现语义。latest本质依赖 compiler 版本,fingerprint 必须包含 compiler 版本和实际 flag spelling。 - 不重写整个 flags/backend 架构。只建立标准模型和必要去重边界。
建议新增一个小的值类型,放在 src/manifest.cppm 或独立模块 src/build/language.cppm。如果要避免 manifest 过大,推荐独立模块:
struct CppStandard {
std::string canonical; // "c++23", "c++26", "gnu++26", "c++latest"
std::string compilerFlag; // "-std=c++23", "-std=c++2c", ...
int level = 23; // 23, 26, or max for latest
bool gnuDialect = false;
};
std::expected<CppStandard, StandardError>
normalize_cpp_standard(std::string_view raw,
const mcpp::toolchain::Toolchain* tc = nullptr);归一化规则:
| 输入 | canonical | 说明 |
|---|---|---|
c++23, c++2b |
c++23 |
c++2b 作为旧拼写兼容 |
gnu++23, gnu++2b |
gnu++23 |
保留 GNU dialect,因为它影响 compiler 行为和 BMI |
c++26, c++2c |
c++26 |
语义归一,flag spelling 可由工具链层决定 |
gnu++26, gnu++2c |
gnu++26 |
保留 GNU dialect |
c++latest |
c++latest |
允许,但提示其随 compiler 漂移 |
compilerFlag 不应简单等于 canonical。不同 GCC/Clang 版本可能接受 -std=c++2c 而不是 -std=c++26。更通用的方式是让 toolchain 层提供 flag spelling:
std::expected<std::string, ToolchainError>
cxx_standard_flag(const Toolchain& tc, const CppStandard& standard);第一阶段可以用映射表实现:
c++23->-std=c++23gnu++23->-std=gnu++23c++26-> 优先-std=c++26,必要时 fallback-std=c++2cgnu++26-> 优先-std=gnu++26,必要时 fallback-std=gnu++2c
如果要更稳,可以在 toolchain detect 阶段探测可接受的 -std= spelling,并把结果缓存进 Toolchain。
推荐数据流:
mcpp.toml / xpkg lua
-> manifest parse
-> normalize_cpp_standard()
-> BuildLanguageConfig
-> FingerprintInputs.cppStandard
-> BuildPlan.cppStandard
-> compute_flags()
-> stdmod::ensure_built()
-> ninja + compile_commands
关键点:
- manifest 解析后立即把
[package].standard和旧[language].standard归一成同一个值。 prepare_build()只读取归一后的 active standard,不再在package.standard与language.standard之间选择。BuildPlan增加cppStandard字段,backend 不再回读 manifest 字符串。canonical_compile_flags()和 fingerprint 使用cppStandard.canonical,不是用户原始输入。compute_flags()使用cppStandard.compilerFlag,不再硬编码-std=c++23。
这样 manifest -> fingerprint -> flags -> std BMI 形成闭环。
第一阶段建议采用 graph-wide active standard:
- root package 的
[package].standard选择 active standard,默认c++23。 - dependency package 的
standard表示最低需求,而不是独立 dialect。 - 如果 dependency 需要的标准高于 root active standard,直接报错:
dependency 'foo' requires c++26, but root package uses c++23.
Set [package].standard = "c++26" in the root manifest.
- 如果 root 是
c++26,dependency 默认c++23,依赖也用c++26编译,保证同一 build graph 的 module BMI dialect 一致。
这个策略比 per-package standard 更保守,但符合当前模块管线和 cache 设计。未来如果要支持 per-package dialect,必须先设计独立 BMI namespace、跨 dialect module import 规则和 cache key,这不应混进本轮修复。
ensure_built() 应改为:
std::expected<StdModule, StdModError> ensure_built(
const Toolchain& tc,
std::string_view fingerprint_hex,
const CppStandard& cppStandard,
const std::filesystem::path& cache_root = default_cache_root());std module build command 统一使用 cppStandard.compilerFlag:
- GCC:
g++ <std-flag> -fmodules ... bits/std.cc - Clang std:
clang++ <std-flag> --precompile std.cppm ... - Clang std object:
clang++ <std-flag> std.pcm -c ... - Clang std.compat:
clang++ <std-flag> -fmodule-file=std=...
cache dir 下新增元数据文件,建议名为 std-module.json:
{
"schema": 1,
"compiler": "gcc",
"compiler_version": "16.1.0",
"driver_identity": "hash-or-normalized-driver-ident",
"target_triple": "x86_64-linux-gnu",
"stdlib": "libstdc++",
"stdlib_version": "16.1.0",
"cpp_standard": "c++26",
"std_flag": "-std=c++2c",
"std_module_source": "/path/to/bits/std.cc",
"std_module_source_hash": "hex",
"build_command_hash": "hex",
"mcpp_version": "0.0.41"
}复用条件:
- BMI 文件存在。
- object 文件存在。
- metadata 存在。
- metadata 的 compiler、stdlib、target、standard、std flag、source hash、build command hash 与当前请求一致。
若 metadata 缺失,按 stale cache 处理,重建一次。这样可以兼容旧 cache 目录,又不会继续复用 C++23 生成的 std BMI。
当前 fingerprint 第 10 字段是 stdBmiHash,但 prepare 阶段先算 fingerprint 再用它作为 std module cache 目录名,存在 chicken-and-egg 注释。
本轮可以不重构这个顺序,只做两个约束:
- fingerprint 第 6 字段必须使用 active standard canonical 值。
- std module metadata 必须独立校验 actual std flag 和 build command。
原因:
- active standard 变了,fingerprint 目录自然变。
- 同一 fingerprint 目录内,如果旧版本 mcpp 产物或命令变化导致 metadata 不匹配,
ensure_built()会重建。
后续如果要把 std BMI content hash 真正放进 fingerprint,第 10 字段需要重新设计为两阶段 build 或独立 std module cache key,不适合和 C++26 修复绑在一起。
推荐采用更清晰方案:
- 全局
cxxflags只放 build graph baseline:- C++ standard flag。
- module flags。
- toolchain/sysroot/backend flags。
- project-wide include baseline。
- 所有 package-owned
cflags/cxxflags只通过 compile unit 追加:- root package 也是一个 package,不特殊放进全局。
- dependencies 与 root 行为一致。
build.cxxflags中出现-std=时给迁移诊断:
build.cxxflags must not set the C++ language standard.
Use [package].standard = "c++26" instead.
兼容策略可以分两步:
- P0: 如果
-std=与 active standard 冲突,报错。相同则警告。 - P1: 全部
-std=incxxflags报错,彻底收口语义。
这个策略能避免 cxxflags 继续绕过 std BMI dialect 和 fingerprint。
CDB 应继续从 BuildPlan + CompileFlags 生成,但需要满足两个不变量:
compile_commands.json与build.ninja中每个 compile unit 的标准 flag 完全一致。- 标准 flag 只出现一次。
建议新增一个共享 helper,避免 Ninja 和 CDB 各自拼接:
std::vector<std::string> cxx_args_for_unit(const BuildPlan& plan,
const CompileFlags& flags,
const CompileUnit& cu);Ninja 可以继续输出字符串变量,CDB 输出 arguments array,但二者应消费同一组分层 flag:
local includes -> baseline flags -> unit package flags -> -c source -o output
需要在两个位置给直接诊断。
检测到 build.cxxflags 含 -std=:
build.cxxflags contains '-std=c++26'.
C++ language standard is a first-class package setting.
Move it to:
[package]
standard = "c++26"
如果 metadata 与当前 active standard 不一致:
import std requires std module BMI built with the same C++ standard.
Current build uses c++26 (-std=c++2c), but cached std.gcm was built as c++23 (-std=c++23).
Rebuilding std module cache.
如果重建失败,再附加 compiler 原始输出。
docs/05-mcpp-toml.md 需要补三块:
推荐示例:
[package]
name = "myapp"
version = "0.1.0"
standard = "c++26"说明:
- 默认
c++23。 c++26可用于需要 C++26 语言特性的项目。c++2c作为别名兼容,但文档推荐写c++26。gnu++26表示 GNU dialect,会进入 fingerprint 和 std BMI cache key。
[language]
standard = "c++26"仍可读,但标记为 legacy compatibility。新项目使用 [package].standard。
明确写:
[build]
cxxflags = ["-Wall", "-Wextra"] # 附加 C++ flags,不用于 -std=语言标准只能通过 [package].standard 配置。
改动:
- 新增
normalize_cpp_standard()。 [package].standard和[language].standard都进入归一化。synthesize_from_xpkg_lua()中的language = ...同步填充归一后的 package standard。- 支持
c++26、c++2c、gnu++26、gnu++2c。
验证:
tests/unit/test_manifest.cpp:[package] standard = "c++26"通过。tests/unit/test_manifest.cpp:[language] standard = "c++2c"映射到c++26。tests/unit/test_manifest.cpp: 非法值给出包含 allowed values 的错误。
改动:
BuildPlan增加 activeCppStandard字段。prepare_build()填入 active standard。FingerprintInputs.cppStandard使用 canonical。compute_flags()使用cppStandard.compilerFlag。- 移除 C++ 标准硬编码
-std=c++23。
验证:
- unit test 生成
standard = "c++26"的 plan,build.ninja只含-std=c++26或工具链选出的-std=c++2c。 compile_commands.json与build.ninja标准 flag 一致。
改动:
ensure_built(tc, fp.hex, cppStandard)。- GCC/Clang/std.compat build command 都使用 active standard flag。
- cache metadata 写入并在复用前校验。
- metadata 缺失或不匹配时重建。
验证:
- unit test 或 command-generation test: C++26 下 GCC std module command 含 C++26 flag,不含 C++23。
- Clang std 和 std.compat command 同样覆盖。
- metadata mismatch 时不复用旧 BMI。
改动:
compute_flags()不再把 rootbuildConfig.cxxflags和cflags放进全局 baseline。- root 与 dependency 一样,只通过 compile unit
packageCxxflags/packageCflags进入 Ninja/CDB。 - 检测
-std=incxxflags,先报冲突错误或迁移 warning。
验证:
- 主包
-DROOT=1在每个 root compile unit 只出现一次。 - dependency flags 仍只作用于 dependency compile units。
-std=incxxflags给出迁移提示。
改动:
docs/05-mcpp-toml.md增加[package].standard字段。- 默认值表写明配置方式。
- 增加
[language]legacy note。 - 增加
cxxflags不承担语言标准的说明。
验证:
- 文档示例中不再推荐或暗示用
cxxflags配置-std=。
改动:
- std BMI metadata mismatch 输出清晰原因。
- 如果 compiler 不支持选定 standard,提示当前 toolchain 和建议 fallback。
验证:
- 人工构造 metadata mismatch,错误或 warning 能说明 cached/current standard。
| 层级 | 用例 | 目的 |
|---|---|---|
| Manifest | [package] standard = "c++26" |
新字段可用 |
| Manifest | [language] standard = "c++2c" |
旧字段兼容和别名归一 |
| Manifest | build.cxxflags = ["-std=c++26"] |
给迁移诊断 |
| Fingerprint | c++2c 与 c++26 |
canonical 相同,fingerprint 不分裂 |
| Fingerprint | c++23 与 c++26 |
fingerprint 必须不同 |
| Flags | standard = "c++26" |
Ninja/CDB 没有 -std=c++23 |
| Flags | root cxxflags = ["-DROOT=1"] |
不重复 |
| stdmod GCC | C++26 + import std |
std.gcm build command 使用同一 standard |
| stdmod Clang | C++26 + import std.compat |
std.pcm 和 std.compat.pcm 使用同一 standard |
| Cache | metadata 缺失 | 旧 cache 触发重建 |
| Cache | metadata standard mismatch | 不复用旧 BMI |
| E2E | C++26 非 import std 项目 | 编译命令贯通 |
| E2E | C++26 import std 项目 | std BMI dialect 一致 |
E2E 应按工具链能力 gate。不是所有 CI compiler 都一定支持 C++26 std module,不能让环境能力不足变成主线失败。
风险: 某些 compiler 支持 -std=c++2c 但不支持 -std=c++26。
取舍: 用户-facing canonical 推荐 c++26,compiler-facing flag 由 toolchain 层决定。fingerprint 记录 canonical 和实际 flag spelling,避免 cache 误用。
风险: gnu++26 和 c++26 不应混用同一 BMI cache。
取舍: canonical 保留 gnu++ 前缀,metadata 记录 std_flag。这会分出不同 cache,是正确行为。
风险: dependency 声明 c++23 但 root 用 c++26 编译,可能遇到语义变化。
取舍: 第一阶段把 dependency standard 当作 minimum requirement。模块 BMI 兼容性比逐包保留原 dialect 更重要。如果未来需要 per-package exact dialect,必须设计单独的模块 ABI 边界。
风险: 现有用户可能已经用 cxxflags = ["-std=c++26"] 绕过。
取舍: 支持一等 standard 后,应给明确迁移提示。短期可以 warning,长期必须禁止,否则 std BMI cache 和 fingerprint 仍会被绕过。
- 先打通
standard -> active standard -> compute_flags,让build.ninja不再硬编码 C++23。 - 同一 PR 内把
active standard传入stdmod::ensure_built(),否则import std仍会撞 BMI dialect。 - 写入 std module cache metadata,修复存在即复用的问题。
- 再整理 root
cxxflags重复和-std=迁移诊断。 - 最后补
docs/05-mcpp-toml.md和更友好的错误信息。
如果要切 PR,建议:
- PR 1: 一等 standard + stdmod 同步 + metadata。解决 C++26/import std 的正确性问题。
- PR 2: cxxflags 去重和
-std=迁移诊断。解决语义边界和输出一致性。 - PR 3: 文档与示例。也可以并入 PR 1,但单独做更容易 review。
- 把 C、C++ 标准都统一进
LanguageConfig,让c_standard也获得别名归一和工具链 capability probe。 - 把 Ninja 和 CDB 的 per-unit args 生成合并成共享 helper,减少未来 flags 顺序漂移。
- 将 std module cache key 从 output fingerprint 中独立出来,形成
std-module/<toolchain>/<standard>/<stdlib>/...的显式 cache namespace。 - 在
mcpp doctor中展示当前 active C++ standard、compiler flag spelling、std module cache metadata 和 std BMI 状态。