diff --git a/.agents/docs/2026-05-21-linux-sysroot-missing-kernel-headers.md b/.agents/docs/2026-05-21-linux-sysroot-missing-kernel-headers.md new file mode 100644 index 0000000..01f5391 --- /dev/null +++ b/.agents/docs/2026-05-21-linux-sysroot-missing-kernel-headers.md @@ -0,0 +1,370 @@ +# Linux sysroot 缺少内核头文件导致 std module 预编译失败 + +## 现象 + +在用户机器上(非 CI),使用 LLVM 或 GCC 工具链执行 `mcpp run` / `mcpp build` 时, +std module 预编译失败: + +``` +/home/speak/.mcpp/registry/subos/default/usr/include/bits/local_lim.h:38:10: +fatal error: 'linux/limits.h' file not found +``` + +GCC 和 Clang 均受影响,问题是系统性的。 + +## 根因 + +### 直接原因:M5.5 sysroot 覆盖逻辑 + +`cli.cppm:1192-1203` 中的 M5.5 逻辑,将 `tc->sysroot` 强制覆盖为 mcpp 自己的 +subos(`~/.mcpp/registry/subos/default`): + +```cpp +if (!isMuslTc) { + if (auto cfg = get_cfg(); cfg) { + auto mcppSubos = (*cfg)->xlingsHome() / "subos" / "default"; + if (std::filesystem::exists(mcppSubos / "usr" / "include")) { + tc->sysroot = mcppSubos; + } + } +} +``` + +### 触发 commit + +**`063fb6f`** — 将 MCPP_HOME 从 xpkgs 包目录改为 `~/.mcpp/`,使 M5.5 +找到一个存在但不完整的 subos。 + +### CI/e2e 未发现的原因 + +CI 的 subos 由 `xlings self install` 完整初始化(含内核头文件),e2e 测试 +通过 `_inherit_toolchain.sh` 继承宿主完整 subos。 + +--- + +## 设计分析:当前问题的本质 + +### 当前架构的矛盾 + +mcpp 的工具链管理存在一个架构层面的矛盾: + +``` + ┌─────────────────────────┐ + │ xlings 下载 payload │ + │ ~/.xlings/data/xpkgs/ │ + │ (cfg/specs 路径正确) │ + └──────────┬──────────────┘ + │ copy (因 XLINGS_HOME 传播不可靠) + ▼ + ┌─────────────────────────┐ + │ mcpp sandbox 副本 │ + │ ~/.mcpp/registry/xpkgs/ │ + │ (cfg/specs 路径 stale!) │ + └──────────┬──────────────┘ + │ + ┌──────────────┼──────────────┐ + ▼ ▼ ▼ + GCC: specs Clang macOS: Clang Linux: + fixup 修复 --no-default 没有修复 ← BUG + 路径 ✅ -config ✅ 用 subos 补救 ✗ +``` + +**三个平台/工具链的 stale path 问题,用了三种不同的解法:** + +| 工具链 | stale path 来源 | 当前解法 | 状态 | +|--------|-----------------|---------|------| +| GCC (Linux) | specs 文件 | `fixup_gcc_specs()` 重写 specs | ✅ 正确但只修了 specs,没修 `-print-sysroot` | +| Clang (macOS) | clang++.cfg | `--no-default-config` + xcrun | ✅ 但只在 macOS | +| Clang (Linux) | clang++.cfg | M5.5 subos 覆盖 | ❌ subos 不完整 | + +**根本问题:mcpp 复制了 payload 但没有统一处理 stale path。** + +### mcpp 对 xlings 的依赖边界不清 + +当前 mcpp 对 xlings 有三层依赖: + +| 层次 | 内容 | 应该依赖? | +|------|------|-----------| +| 包下载 | xlings 作为包索引 + 下载工具 | ✅ 是 | +| payload 路径 | xpkgs 下具体包的文件结构 | ✅ 是 | +| subos | xlings 的沙箱 sysroot | ❌ 不应该 | + +M5.5 的问题就在于跨越了第三层边界——用 xlings 的内部实现细节(subos) +来补救 mcpp 自身的路径问题。 + +--- + +## 设计方案 + +### 核心原则 + +**mcpp 只把 xlings 当包索引 + 下载工具。工具链的编译环境由 payload 自描述, +mcpp 忠实读取,不替换、不覆盖。** + +### 方案:Payload-first + 统一 stale path 处理 + +#### 1. 工具链 sysroot 来源:只从 payload 获取 + +``` +┌─────────────────────────────────────────────────┐ +│ sysroot 解析优先级 │ +│ │ +│ 1. compiler -print-sysroot (GCC 原生支持) │ +│ → 路径存在则使用 │ +│ │ +│ 2. payload cfg 文件解析 (Clang clang++.cfg) │ +│ → 解析 --sysroot= 行,路径存在则使用 │ +│ │ +│ 3. macOS: xcrun --show-sdk-path │ +│ │ +│ 4. 空 (不传 --sysroot,让编译器用自身默认值) │ +│ │ +│ ✗ 不再有 subos fallback │ +└─────────────────────────────────────────────────┘ +``` + +**改动**: +- 删除 `cli.cppm` M5.5 subos 覆盖代码 +- `probe.cppm` 增加 cfg 文件解析作为第 2 优先级 + +#### 2. 复制 payload 时统一修复 stale path + +当前只有 GCC 做了 specs fixup,Clang 只在 macOS 做了 `--no-default-config`。 +应该统一为:**凡是复制了 payload,就修复其中的路径配置。** + +``` + copy payload 后 + │ + ┌────────┼────────┐ + ▼ ▼ ▼ + GCC specs Clang cfg 其他配置 + │ │ + ▼ ▼ + rewrite_gcc rewrite_cfg + _specs() _paths() + │ │ + ▼ ▼ + 新 sysroot 新 sysroot + 新 rpath 新 -isystem + 新 -L/-rpath +``` + +**具体做法**:在 `package_fetcher.cppm` 复制 payload 后(或在 `cli.cppm` +toolchain install 后),对 Clang cfg 做类似 `fixup_gcc_specs` 的路径重写: + +```cpp +void fixup_clang_cfg(const std::filesystem::path& payloadRoot, + const std::filesystem::path& newSysroot) { + auto cfgPath = payloadRoot / "bin" / "clang++.cfg"; + if (!std::filesystem::exists(cfgPath)) return; + + // 读取 cfg,将旧 sysroot/isystem/rpath 路径替换为 payload 实际位置 + // ... +} +``` + +**但这里有一个关键设计选择:新路径指向哪里?** + +#### 3. 关于 sysroot 本身从哪来 + +工具链需要 C 库头文件(glibc headers + linux kernel headers)。来源有三种: + +| 来源 | 说明 | 优劣 | +|------|------|------| +| 系统 `/usr/include` | 宿主机自带 | 简单,但不可控,不同发行版不同 | +| xlings subos | xlings 管理的沙箱 sysroot | 可控,但 mcpp 需依赖 xlings 内部结构 | +| payload 自带 | 工具链包自带 sysroot(如 musl-gcc) | 最干净,但需要上游包支持 | + +**推荐策略**: + +- **短期**:信任 payload 自身配置的 sysroot 路径。xlings 安装 GCC/LLVM 时 + 已经配置好了 sysroot(指向 xlings 自己的 subos),mcpp 只需忠实读取。 + 如果路径存在且有效,就用它。如果路径无效,不传 `--sysroot`,让编译器 + 用系统默认路径。 + +- **中期**:推动 xlings 上游让 LLVM/GCC 包的 cfg/specs 使用相对路径或 + 可配置路径,避免硬编码绝对路径。这从源头消除 stale path 问题。 + +- **长期**:mcpp 自带轻量 sysroot 管理(类似 Zig 的做法:打包 libc headers + 作为 mcpp 自身的资源),彻底不依赖宿主系统或 xlings 的 sysroot。但这是 + 大工程,不急。 + +--- + +## 修复方案(基于以上设计) + +### Phase 1:修复当前 bug(最小改动) + +#### P1-1:删除 M5.5 subos 覆盖 + +**文件**:`src/cli.cppm:1192-1203` + +**删除**整个代码块。工具链的 sysroot 由 payload 决定,mcpp 不介入。 + +同时删除 `cli.cppm:1001` 的 subos 注释和 `cli.cppm:1178` 的 "glibc subos" 注释。 + +#### P1-2:`probe_sysroot` 增加 cfg 解析 + +**文件**:`src/toolchain/probe.cppm:254-272` + +`-print-sysroot` 失败后(Clang 不支持),解析 payload 中 `clang++.cfg` +的 `--sysroot=` 行: + +```cpp +std::filesystem::path +probe_sysroot(const std::filesystem::path& compilerBin, + const std::string& envPrefix) { + // 1. -print-sysroot (GCC) + auto r = run_capture(std::format("{}{} -print-sysroot {}", + envPrefix, + mcpp::xlings::shq(compilerBin.string()), + mcpp::platform::null_redirect)); + if (r) { + auto s = trim_line(*r); + if (!s.empty() && std::filesystem::exists(s)) return s; + } + + // 2. Parse payload cfg (Clang) + auto cfgPath = compilerBin.parent_path() + / (compilerBin.stem().string() + ".cfg"); + if (std::filesystem::exists(cfgPath)) { + std::ifstream ifs(cfgPath); + std::string line; + while (std::getline(ifs, line)) { + constexpr std::string_view prefix = "--sysroot="; + if (line.starts_with(prefix)) { + auto val = trim_line(std::string(line.substr(prefix.size()))); + if (!val.empty() && std::filesystem::exists(val)) + return val; + } + } + } + + // 3. macOS: xcrun SDK + if (auto sdk = mcpp::platform::macos::sdk_path()) + return *sdk; + return {}; +} +``` + +**Phase 1 效果**: +- Clang:cfg 中的 `--sysroot=~/.xlings/subos/default` 被正确读取, + `tc->sysroot` 不再为空。stdmod.cppm 和 flags.cppm 传递正确的 sysroot。 +- GCC:`-print-sysroot` 正常工作(如果路径存在);若不存在则 sysroot 为空, + GCC 用默认系统路径(`/usr/include`)。 +- 不再依赖 subos。 + +### Phase 2:统一 Clang stale cfg 处理(消除隐患) + +#### P2-1:`stdmod.cppm` — 所有有 cfg 的 Clang 都走 `--no-default-config` + +**文件**:`src/toolchain/stdmod.cppm:103-116` + +将 macOS 特有的 `--no-default-config` 逻辑泛化为"有 cfg 文件的 Clang": + +```cpp +std::string sysroot_flag; +if (is_clang(tc)) { + auto cfgPath = tc.binaryPath.parent_path() + / (tc.binaryPath.stem().string() + ".cfg"); + if (std::filesystem::exists(cfgPath)) { + // Bypass cfg (may have stale paths after payload copy). + // Provide correct flags from payload structure directly. + auto llvmRoot = tc.binaryPath.parent_path().parent_path(); + auto libcxxInclude = llvmRoot / "include" / "c++" / "v1"; + sysroot_flag = " --no-default-config"; + sysroot_flag += std::format(" -isystem'{}'", libcxxInclude.string()); + if (!tc.sysroot.empty()) + sysroot_flag += std::format(" --sysroot='{}'", tc.sysroot.string()); + else if (auto sdk = mcpp::platform::macos::sdk_path()) + sysroot_flag += std::format(" --sysroot='{}'", sdk->string()); + } else if (!tc.sysroot.empty()) { + sysroot_flag = std::format(" --sysroot='{}'", tc.sysroot.string()); + } +} else if (!tc.sysroot.empty()) { + sysroot_flag = std::format(" --sysroot='{}'", tc.sysroot.string()); +} +``` + +#### P2-2:`flags.cppm` — 同步修改 + +**文件**:`src/build/flags.cppm:96-111` + +同步 P2-1 的逻辑:将 `is_macos_clang` 条件改为"检测到 cfg 文件存在"。 + +**Phase 2 效果**: +- Linux 和 macOS Clang 走统一路径 +- 不再依赖 cfg 中的路径碰巧有效 +- mcpp 从 payload 结构推导出正确的 `-isystem` 和 `--sysroot` + +### Phase 3(未来):复制 payload 时重写 cfg 路径 + +在 `package_fetcher.cppm` 或 `cli.cppm` toolchain install 后,添加 +`fixup_clang_cfg()`,类似 `fixup_gcc_specs()` 的做法: + +```cpp +void fixup_clang_cfg(const std::filesystem::path& payloadRoot, + const std::filesystem::path& oldXlingsHome, + const std::filesystem::path& newRegistryHome) { + // 重写 clang++.cfg 中的路径: + // --sysroot= → --sysroot= + // -isystem → -isystem + // -L → -L + // -rpath, → -rpath, +} +``` + +这样即使不用 `--no-default-config`,cfg 路径也是正确的。 +但需要 mcpp 管理自己的 sysroot 内容(确保完整性),所以这是更远期的方向。 + +--- + +## 修改总结 + +| Phase | 修改 | 文件 | 效果 | +|-------|------|------|------| +| P1-1 | 删除 M5.5 | cli.cppm | 去除 subos 依赖 | +| P1-2 | cfg 解析 sysroot | probe.cppm | Clang 获取正确 sysroot | +| P2-1 | 统一 --no-default-config | stdmod.cppm | 消除 stale cfg 隐患 | +| P2-2 | 同步 P2-1 | flags.cppm | 常规编译也用正确路径 | +| P3 | cfg 路径重写 | package_fetcher/cli | 从根源修复 stale path | + +**Phase 1 修复 bug,Phase 2 消除隐患,Phase 3 完善架构。** + +--- + +## 测试补充 + +### 新增 e2e 测试:无 subos 下的 import std + +```bash +#!/usr/bin/env bash +# requires: import-std +# Test that import std works without mcpp's subos sysroot. +# Regression test: M5.5 subos override must not be required. +set -euo pipefail + +TMP=$(mktemp -d) +trap "rm -rf $TMP" EXIT + +export MCPP_HOME="$TMP/mcpp-home" +export MCPP_INHERIT_SUBOS=0 +source "$(dirname "$0")/_inherit_toolchain.sh" + +mkdir -p "$TMP/proj/src" +cd "$TMP/proj" + +cat > mcpp.toml <<'EOF' +[package] +name = "sysroot_test" +version = "0.1.0" +EOF + +cat > src/main.cpp <<'EOF' +import std; +int main() { std::println("sysroot ok"); } +EOF + +"$MCPP" build +"$MCPP" run | grep -q "sysroot ok" +``` diff --git a/src/build/flags.cppm b/src/build/flags.cppm index e7278c7..b894d0f 100644 --- a/src/build/flags.cppm +++ b/src/build/flags.cppm @@ -88,23 +88,41 @@ CompileFlags compute_flags(const BuildPlan& plan) { include_flags += " -I" + escape_path(abs); } - // Sysroot + config override for macOS. - // On macOS, xlings LLVM's clang++.cfg contains hardcoded --sysroot and - // -isystem paths from the original install location. When the package is - // copied to mcpp's sandbox, these paths become stale. We pass - // --no-default-config to ignore the cfg and provide correct paths. + // Sysroot + config override. + // + // On macOS, xlings LLVM's clang++.cfg has hardcoded paths that become + // stale after copying. Use --no-default-config + explicit flags. + // + // On Linux, the cfg contains essential linker flags (-fuse-ld=lld, + // --rtlib=compiler-rt). Let the cfg apply normally; pass --sysroot + // explicitly to override any stale sysroot value in the cfg. std::string sysroot_flag; bool is_macos_clang = mcpp::toolchain::is_clang(plan.toolchain) && (plan.toolchain.targetTriple.find("apple") != std::string::npos || plan.toolchain.targetTriple.find("darwin") != std::string::npos); if (is_macos_clang) { - auto llvmRoot = plan.toolchain.binaryPath.parent_path().parent_path(); - auto libcxxInclude = llvmRoot / "include" / "c++" / "v1"; - sysroot_flag = " --no-default-config"; - sysroot_flag += " -isystem" + escape_path(libcxxInclude); - if (auto sdk = mcpp::platform::macos::sdk_path()) - sysroot_flag += " --sysroot=" + escape_path(*sdk); - f.sysroot = sysroot_flag; + auto cfgPath = plan.toolchain.binaryPath.parent_path() + / (plan.toolchain.binaryPath.stem().string() + ".cfg"); + if (std::filesystem::exists(cfgPath)) { + auto llvmRoot = plan.toolchain.binaryPath.parent_path().parent_path(); + auto libcxxInclude = llvmRoot / "include" / "c++" / "v1"; + sysroot_flag = " --no-default-config"; + sysroot_flag += " -isystem" + escape_path(libcxxInclude); + if (!plan.toolchain.targetTriple.empty()) { + auto targetInclude = llvmRoot / "include" + / plan.toolchain.targetTriple / "c++" / "v1"; + if (std::filesystem::exists(targetInclude)) + sysroot_flag += " -isystem" + escape_path(targetInclude); + } + if (auto sdk = mcpp::platform::macos::sdk_path()) + sysroot_flag += " --sysroot=" + escape_path(*sdk); + else if (!plan.toolchain.sysroot.empty()) + sysroot_flag += " --sysroot=" + escape_path(plan.toolchain.sysroot); + f.sysroot = sysroot_flag; + } else if (!plan.toolchain.sysroot.empty()) { + sysroot_flag = " --sysroot=" + escape_path(plan.toolchain.sysroot); + f.sysroot = sysroot_flag; + } } else if (!plan.toolchain.sysroot.empty()) { sysroot_flag = " --sysroot=" + escape_path(plan.toolchain.sysroot); f.sysroot = sysroot_flag; diff --git a/src/cli.cppm b/src/cli.cppm index 172e124..5c00bcd 100644 --- a/src/cli.cppm +++ b/src/cli.cppm @@ -998,9 +998,8 @@ prepare_build(bool print_fingerprint, // ─── Toolchain resolution (docs/21) ──────────────────────────────── // Priority chain: // 1. mcpp.toml [toolchain]. → resolve_xpkg_path → abs path - // 2. $MCPP_HOME/registry/subos//bin/g++ (xlings sandbox subos) - // 3. $CXX env var - // 4. PATH g++ (with warning) + // 2. $CXX env var + // 3. PATH g++ (with warning) std::filesystem::path explicit_compiler; std::optional cfg_opt; auto get_cfg = [&]() -> std::expected { @@ -1174,10 +1173,8 @@ prepare_build(bool print_fingerprint, if (!tc) return std::unexpected(tc.error().message); // For musl-gcc the toolchain is fully self-contained - // (`/x86_64-linux-musl/{include,lib}` is its own sysroot), and - // pointing it at mcpp's glibc subos breaks compilation. Skip the - // sysroot injection in that case — musl-gcc's `-dumpmachine` reports - // `x86_64-linux-musl`, which is also the marker we use elsewhere. + // (`/x86_64-linux-musl/{include,lib}` is its own sysroot). + // musl-gcc's `-dumpmachine` reports `x86_64-linux-musl`. bool isMuslTc = tc->targetTriple.find("-musl") != std::string::npos; // A musl toolchain only really makes sense with static linkage — @@ -1189,18 +1186,9 @@ prepare_build(bool print_fingerprint, m->buildConfig.linkage = "static"; } - // M5.5: prefer mcpp's xlings-managed subos as sysroot — it has glibc - // headers + libs in the conventional layout that GCC expects. The - // -print-sysroot output from a freshly-built GCC often points at - // some build-time path that doesn't exist on the user's machine. - if (!isMuslTc) { - if (auto cfg = get_cfg(); cfg) { - auto mcppSubos = (*cfg)->xlingsHome() / "subos" / "default"; - if (std::filesystem::exists(mcppSubos / "usr" / "include")) { - tc->sysroot = mcppSubos; - } - } - } + // Sysroot comes from the toolchain payload itself (GCC -print-sysroot, + // Clang clang++.cfg). mcpp does not override it — the payload is + // self-describing. See docs: 2026-05-21-linux-sysroot-missing-kernel-headers.md // Resolve dependencies: walk the **transitive** graph from the main // manifest, BFS-style. Each unique `(namespace, shortName)` is fetched diff --git a/src/toolchain/probe.cppm b/src/toolchain/probe.cppm index ef25032..0cac4f2 100644 --- a/src/toolchain/probe.cppm +++ b/src/toolchain/probe.cppm @@ -254,6 +254,7 @@ probe_target_triple(const std::filesystem::path& compilerBin, std::filesystem::path probe_sysroot(const std::filesystem::path& compilerBin, const std::string& envPrefix) { + // 1. Ask the compiler directly (works for GCC; Clang often doesn't support it). auto r = run_capture(std::format("{}{} -print-sysroot {}", envPrefix, mcpp::xlings::shq(compilerBin.string()), @@ -261,11 +262,44 @@ probe_sysroot(const std::filesystem::path& compilerBin, if (r) { auto s = trim_line(*r); if (!s.empty() && std::filesystem::exists(s)) return s; + + // GCC bakes the build-time sysroot into the binary. For xlings-built + // GCC this is a path like /.xlings/subos/default that + // doesn't exist on the user's machine. If the reported path ends + // with subos/default, look for the equivalent sysroot relative to + // the compiler's own xpkgs directory (payload-derived). + if (!s.empty() && s.ends_with("subos/default")) { + if (auto xpkgs = mcpp::xlings::paths::xpkgs_from_compiler(compilerBin)) { + // xpkgs is /data/xpkgs → registry = xpkgs/../.. + auto registrySysroot = xpkgs->parent_path().parent_path() + / "subos" / "default"; + if (std::filesystem::exists(registrySysroot / "usr" / "include")) + return registrySysroot; + } + } } - // macOS fallback: use xcrun to discover the SDK path. - // The sysroot is used for regular compilation flags (flags.cppm) but - // skipped for std module precompilation on macOS (stdmod.cppm) to - // avoid breaking SDK internal header dependencies. + + // 2. Parse the compiler driver config file (Clang .cfg). + // xlings-installed Clang ships a clang++.cfg alongside the binary + // with --sysroot pointing to the payload's associated sysroot. + { + auto stem = compilerBin.stem().string(); + auto cfgPath = compilerBin.parent_path() / (stem + ".cfg"); + if (std::filesystem::exists(cfgPath)) { + std::ifstream ifs(cfgPath); + std::string line; + while (std::getline(ifs, line)) { + constexpr std::string_view prefix = "--sysroot="; + if (line.starts_with(prefix)) { + auto val = trim_line(std::string(line.substr(prefix.size()))); + if (!val.empty() && std::filesystem::exists(val)) + return val; + } + } + } + } + + // 3. macOS fallback: use xcrun to discover the SDK path. if (auto sdk = mcpp::platform::macos::sdk_path()) return *sdk; return {}; diff --git a/src/toolchain/stdmod.cppm b/src/toolchain/stdmod.cppm index 0220d75..bd1b67b 100644 --- a/src/toolchain/stdmod.cppm +++ b/src/toolchain/stdmod.cppm @@ -93,24 +93,42 @@ std::expected ensure_built( sm.objectPath = sm.cacheDir / "std.o"; // Build sysroot + include flags for std module precompilation. + // // On macOS, xlings LLVM's clang++.cfg contains hardcoded --sysroot and - // -isystem paths from the original install location. When the LLVM package - // is copied to mcpp's sandbox, these cfg paths become stale (still point - // to the original xlings directory). We override both: - // --sysroot → current active SDK (from xcrun) - // --no-default-config → ignore stale cfg entirely - // -isystem → correct libc++ headers in the sandbox copy + // -isystem paths that become stale when the package is copied to mcpp's + // sandbox. We use --no-default-config to bypass the cfg and provide + // correct flags derived from the payload's actual location + xcrun SDK. + // + // On Linux, the cfg also contains linker flags (-fuse-ld=lld, + // --rtlib=compiler-rt, --unwindlib=libunwind) that are essential. Using + // --no-default-config would strip these, causing link failures. Instead, + // we let the cfg apply normally and only pass --sysroot to override the + // sysroot if needed (command-line --sysroot takes precedence over cfg). std::string sysroot_flag; bool is_macos = tc.targetTriple.find("apple") != std::string::npos || tc.targetTriple.find("darwin") != std::string::npos; if (is_macos && is_clang(tc)) { - // Ignore the stale clang++.cfg and provide correct flags directly. - auto llvmRoot = tc.binaryPath.parent_path().parent_path(); - auto libcxxInclude = llvmRoot / "include" / "c++" / "v1"; - sysroot_flag = " --no-default-config"; - sysroot_flag += std::format(" -isystem'{}'", libcxxInclude.string()); - if (auto sdk = mcpp::platform::macos::sdk_path()) - sysroot_flag += std::format(" --sysroot='{}'", sdk->string()); + auto cfgPath = tc.binaryPath.parent_path() + / (tc.binaryPath.stem().string() + ".cfg"); + if (std::filesystem::exists(cfgPath)) { + // Bypass stale macOS cfg; provide correct flags directly. + auto llvmRoot = tc.binaryPath.parent_path().parent_path(); + auto libcxxInclude = llvmRoot / "include" / "c++" / "v1"; + sysroot_flag = " --no-default-config"; + sysroot_flag += std::format(" -isystem'{}'", libcxxInclude.string()); + if (!tc.targetTriple.empty()) { + auto targetInclude = llvmRoot / "include" + / tc.targetTriple / "c++" / "v1"; + if (std::filesystem::exists(targetInclude)) + sysroot_flag += std::format(" -isystem'{}'", targetInclude.string()); + } + if (auto sdk = mcpp::platform::macos::sdk_path()) + sysroot_flag += std::format(" --sysroot='{}'", sdk->string()); + else if (!tc.sysroot.empty()) + sysroot_flag += std::format(" --sysroot='{}'", tc.sysroot.string()); + } else if (!tc.sysroot.empty()) { + sysroot_flag = std::format(" --sysroot='{}'", tc.sysroot.string()); + } } else if (!tc.sysroot.empty()) { sysroot_flag = std::format(" --sysroot='{}'", tc.sysroot.string()); }