Skip to content

[Bug & RFC] 系统工具链 build.mcpp 执行失败缺陷、原生宿主模式与工作区继承规范提案 #527

Description

@elel-code

概述

本 Issue 与架构改进提案(RFC)包含以下内容:

  1. 致命缺陷 (Bug 1):在配置 MCPP_TOOLCHAIN=system 时,由于传入空编译器路径导致编译 build.mcpp 时崩溃(posix_spawnp('') failed)。
  2. 主要缺陷 (Bug 2):工作区(Workspace)根目录中的 [build] 配置项未透传给成员包(Member)。
  3. 架构提案 (RFC 1):引入原生宿主支持(toolchain = "system"sysroot = "system"),明确区分“第三方依赖包 RUNPATH”与“系统基础 Libc 寻址”。
  4. 架构提案 (RFC 2):将核心语言方言(如 -fno-exceptions)自动同步至 import std; 标准库 BMI 预编译流程。
  5. 架构提案 (RFC 3):提供完备的 [workspace.*] 继承规范(特别是统一声明 standard = 26 / [workspace.build]),彻底杜绝 C++ 模块化工程因标准版本不一致引发的跨包 BMI 解析崩溃。
  6. 架构提案 (RFC 4):结合现代 C++ Language Server(如 clice / clangd)的全局索引机制,为多包 Monorepo 工作区提供一键聚合导出 compile_commands.json--cdb-aggregate 选项。

🐛 第一部分:已核实的源码级缺陷报告 (Confirmed Bugs)

Bug 1: MCPP_TOOLCHAIN=system 时无法启动 build.mcpp(Exit 127: posix_spawnp('')

  • 严重程度Critical / Blocker(致命阻断)
  • 影响组件mcpp.build.prepare, mcpp.build.build_program
  • 复现环境:Linux / macOS,配置 [toolchain] default = "system" 或环境变量 MCPP_TOOLCHAIN=system,且工程中包含 build.mcpp 构建脚本。

最小复现步骤

  1. 创建一个包含 build.mcpp 的最小工程:
    # mcpp.toml
    [package]
    name = "demo"
    version = "0.1.0"
    standard = 26
    
    [toolchain]
    linux = "system"
    // build.mcpp
    #include <iostream>
    int main() { return 0; }
  2. 执行构建命令:mcpp build

实际报错

在编译主程序前构建立即中断:

mcpp module compile failed (exit 127): posix_spawnp('') failed (error 2)
build.mcpp failed to compile (exit 127)

源码调用链与根因溯源

  1. 未赋值的 explicit_compiler
    src/build/prepare.cppm:1705-1706 中,当 *tcSpec == "system" 时,进入了空分支:

    // src/build/prepare.cppm:1705-1706
    } else if (tcSpec.has_value() && *tcSpec == "system") {
        // Explicit user opt-in to system PATH compiler — kept as escape hatch.
    }

    局部变量 std::filesystem::path explicit_compiler 保持为空字符串 ""
    随后在 src/build/prepare.cppm:1882 调用 mcpp::toolchain::detect(explicit_compiler, ...),探测函数成功从系统 PATH 找到了编译器并将真实绝对路径存入 tc->binaryPath(如 /usr/bin/g++)。

  2. Host 工具链闭包返回空路径
    src/build/prepare.cppm:2205-2208 中构造 Host 工具链闭包时,非交叉编译分支直接返回了仍为空的 explicit_compiler

    // src/build/prepare.cppm:2205-2208
    auto host_tc_for_build_program = [&]() -> std::expected<
            std::pair<std::filesystem::path, mcpp::toolchain::Toolchain>, std::string> {
        if (overrides.target_triple.empty())
            return std::pair{explicit_compiler, *tc}; // ⚠️ 致命 Bug:explicit_compiler 为空!
  3. posix_spawnp 执行空路径
    该空路径作为 hostCompiler 传递给 src/build/build_program.cppm:573-580。底层 posix_spawnp("") 尝试执行空文件名,操作系统立即返回 ENOENT(error 2),子进程以 127 退出码异常终止。

建议修复补丁 (Proposed Patch)

--- a/src/build/prepare.cppm
+++ b/src/build/prepare.cppm
@@ -2205,8 +2205,10 @@ export std::expected<BuildContext, std::string> prepare_build(
     auto host_tc_for_build_program = [&]() -> std::expected<
             std::pair<std::filesystem::path, mcpp::toolchain::Toolchain>, std::string> {
         if (overrides.target_triple.empty())
-            return std::pair{explicit_compiler, *tc};
+            return std::pair{explicit_compiler.empty() ? tc->binaryPath : explicit_compiler, *tc};

Bug 2: Workspace 根目录 [build] 表项无法透传给成员包

  • 严重程度Major(主要缺陷)
  • 现象描述
    在 Workspace 根目录的 mcpp.toml 中配置了全局构建参数(例如 [build] cxxflags = ["-fno-exceptions"]bmi_schedule = "on"),但在执行 mcpp buildmcpp build -p <member> 时,各子成员包完全无法继承这些构建标志,仍按默认参数编译。
  • 源码定位
    src/build/prepare.cppm:965-980 中,Workspace 根仅向成员包合并了 toolchaintargetOverridesindices,遗漏了 buildConfig

🚀 第二部分:架构改进与功能提案 (RFC / Feature Proposals)

RFC 1: 引入原生宿主支持(toolchain = "system"sysroot = "system"

问题背景

对于系统级软件(如 Wayland 合成器、显示服务、系统守护进程、与 Linux 内核特性及 Mesa/Vulkan/GBM 显卡驱动深度交互的工程),mcpp 默认的“全沙盒托管机制(Managed Sandbox)”会引发严重的运行时冲突:

  1. 驱动与动态链接器撕裂:改写 PT_INTERP 或注入沙盒 glibc 的 DT_RUNPATH,会导致程序加载宿主驱动(如 /usr/lib64/dri/*_dri.so)或 Vulkan ICD 时发生动态链接器符号版本冲突或段错误。
  2. 现存 sysroot 语法的局限src/build/prepare.cppm:2005 强制要求 sysroot 必须是 xim: 包或 ""(裸机零 libc),无法表达“直接使用宿主操作系统的 libc 与加载器”。

架构分析:依赖库 RUNPATH 与 Libc 寻址的分层解耦

动态链接中必须清晰区分两类不同的动态库及其寻址诉求:

  • 第三方业务依赖动态库(Package .so:由 mcpp 托管下载或本地编译输出。必须注入 DT_RUNPATH(本地开发期注入缓存绝对路径,发布期通过 mcpp pack 重写为 $ORIGIN/../lib)。
  • 基础 C 运行时(glibc / libc.so.6 / 加载器 ld.so
    • 托管沙盒模式(Managed):适合独立 CLI / 跨发行版免安装工具,保持现有的 xim:glibc 并注入沙盒 RUNPATH;
    • 原生宿主模式(Host-Native):直接使用系统 /lib64/ld-linux-x86-64.so.2/usr/lib64绝不向产物注入任何指向 toolchain payload 的 -Wl,-rpath
graph TD
    A[可执行文件 Binary] --> B[基础 C 运行时 (glibc / ld-linux)]
    A --> C[第三方/远程依赖库 (Package .so)]
    
    B -->|原生宿主模式: sysroot='system'| D[系统 /lib64/ld-linux 与 /etc/ld.so.cache]
    B -->|托管沙盒模式: sysroot='xim:glibc'| E[沙盒 ld-linux 与 沙盒 glibc RUNPATH]
    
    C -->|开发期寻址| F[mcpp 缓存/产物目录 RUNPATH]
    C -->|打包发布期 (mcpp pack)| G[相对路径 $ORIGIN/../lib]
Loading

提案配置语法

mcpp.toml 中支持显式声明宿主原生模式:

[target.x86_64-linux-gnu]
toolchain = "system"   # 使用宿主 /usr/bin/g++ 或 clang++
sysroot   = "system"   # 使用宿主 glibc、头文件与 /lib64/ld-linux (不注入 libc RUNPATH)

RFC 2: 核心语言方言(如 -fno-exceptions)自动同步至标准库 BMI

问题背景

当项目在 cxxflags 中关闭异常(-fno-exceptions)时,mcpp 目前不会将该标志同步至 src/toolchain/stdmod.cppm 预编译的标准库 std.gcm / std.pcm,导致源文件在 import std; 时编译器报方言不匹配错误。

建议方案

在生成标准库 BMI 时,自动将关键语言方言(-fno-exceptions-fno-rtti-fcoroutines)提升并同步至 BMI 预编译参数中,无需用户寻找隐晦的配置字段。


RFC 3: 完备的多包 Workspace 配置继承(特别是 C++ 标准与全局构建项)

问题背景:C++ 模块化体系对标准一致性的强制要求

在 C++20/C++23/C++26 模块化(C++ Modules)体系下,标准版本的一致性是强制性的
若 Workspace 内部成员包 A 使用 -std=c++26 编译,成员包 B 使用 -std=c++23 编译,包 B 在尝试 import 包 A 导出的 C++26 模块(BMI)时,GCC/Clang 编译器会直接报错拒绝(标准版本与 AST 签名不兼容)。

在包含众多成员包的 Monorepo 中,逐一手写 standard = 26[build][target] 极易发生配置漂移与冗余。

提案继承模型

  1. Workspace 根目录 (mcpp.toml) 统一声明

    [workspace]
    members = ["apps/*", "packages/*"]
    
    # ① 全局 C++ 语言版本与包元信息继承 (统一 C++26,杜绝模块 BMI 标准漂移)
    [workspace.package]
    standard = 26               # 统一 C++26,所有成员包自动继承
    edition  = "2026"
    version  = "0.1.0"
    license  = "Apache-2.0"
    
    # ② 全局构建与方言选项继承 (彻底解决 Bug 2)
    [workspace.build]
    cxxflags         = ["-fno-exceptions", "-Wall"]
    dialect_cxxflags = ["-fno-exceptions"]
    bmi_schedule     = "on"
    jobs             = "auto"
    
    # ③ 全局目标平台与原生系统模式继承
    [workspace.target.x86_64-linux-gnu]
    toolchain = "system"
    sysroot   = "system"
    
    # ④ 统一依赖版本表
    [workspace.dependencies]
    wayland-client = { version = "1.23.0" }
  2. 成员包 (apps/compositor/mcpp.toml) 轻量继承

    [package]
    name             = "compositor"
    standard.workspace = true    # 自动继承全局 C++26 (或未显式声明时默认继承)
    version.workspace  = true
    
    [build]
    inherit.workspace  = true    # 自动继承根目录 [workspace.build]
    
    [dependencies]
    wayland-client.workspace = true

RFC 4: 结合现代语言服务器(clice / clangd)支持多包 Workspace 聚合导出 CDB (compile_commands.json)

问题背景与 Language Server 架构诉求

在现代 C++ 开发生态中,新兴的高性能语言服务器(如基于 LLVM/Clang 构建、深度支持 C++20 模块编译图与模板智能推导的 clice,以及 clangd)在初始化时均以工程的 workspace_root 作为根上下文:

  • clice 的工作区探测机制:在 clice 的工作区状态管理(Workspace::discover_compile_commands)中,语言服务器首先在 workspace_root 根目录下查找 compile_commands.json,并以此构建整个工作区跨 Translation Unit、跨 C++20 模块依赖的有向编译图(CompileGraph)与全局符号索引。
  • 当前 mcpp 的痛点:目前 mcpp build --workspace --configure-only 会为每个 Member 在其子目录中分散生成各自局部的 compile_commands.json。这导致当开发者在 Workspace 根目录打开 VS Code / Neovim / CLion 时,clice / clangd 无法在根目录探测到统一的全局编译数据库,导致跨包模块跳转、全局语义高亮与后台跨包索引全部失效。

建议方案

为 CLI 增加 --cdb-aggregate 开关:

mcpp build --workspace --configure-only --cdb-aggregate

在 Workspace 根目录下输出一份汇聚了所有 Member 编译单元的完整全局 compile_commands.json,使 cliceclangd 等现代 LSP 能够无缝接入并完成全工作区的跨包符号分析与模块导航。

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingenhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions