diff --git a/.agents/docs/llvm-install-failure-analysis.md b/.agents/docs/llvm-install-failure-analysis.md new file mode 100644 index 0000000..55dfb6b --- /dev/null +++ b/.agents/docs/llvm-install-failure-analysis.md @@ -0,0 +1,129 @@ +# LLVM 工具链安装失败分析 + +## 现象 + +`mcpp toolchain install llvm` 依赖包(libxml2, zlib, glibc 等)安装成功,但 LLVM 本体(800MB)缺失: + +``` +~/.mcpp/registry/data/xpkgs/ +├── xim-x-libxml2/ ✓ 安装成功 +├── xim-x-zlib/ ✓ 安装成功 +├── xim-x-glibc/ ✓ 安装成功 +├── xim-x-llvm/ ✗ 不存在 +``` + +## 根因分析 + +### 问题 1:`/dev/null` 吞掉所有错误信息 + +`xlings.cppm:432-434` 构建的命令: + +```bash +cd ~/.mcpp && ... xlings interface install_packages --args '...' 2>/dev/null std::expected { -#if defined(_WIN32) - // Workaround: xlings on Windows may extract large packages (e.g. LLVM) - // into its global data dir instead of the mcpp sandbox, because the - // extraction subprocess doesn't inherit XLINGS_HOME. Detect this and - // copy the payload into the sandbox so mcpp remains self-contained. + // Workaround: xlings may extract large packages (e.g. LLVM) into its + // global data dir instead of the mcpp sandbox, because the extraction + // subprocess doesn't always inherit XLINGS_HOME. Detect this and copy + // the payload into the sandbox so mcpp remains self-contained. + // Originally Windows-only; extended to all platforms for the same + // reason (xlings subprocess XLINGS_HOME propagation is unreliable). if (!std::filesystem::exists(verdir)) { - // Try xlings' own data dir (where `xlings self install` placed it) - auto xhome = std::getenv("USERPROFILE"); + const char* xhome = nullptr; +#if defined(_WIN32) + xhome = std::getenv("USERPROFILE"); +#endif if (!xhome) xhome = std::getenv("HOME"); if (xhome) { // xlings stores xpkgs at /.xlings/data/xpkgs/ or @@ -635,7 +638,6 @@ Fetcher::resolve_xpkg_path(std::string_view target, } } } -#endif if (!std::filesystem::exists(verdir)) { return std::unexpected(CallError{ std::format("xpkg payload missing: {}", verdir.string())}); diff --git a/src/toolchain/probe.cppm b/src/toolchain/probe.cppm index b356775..ef25032 100644 --- a/src/toolchain/probe.cppm +++ b/src/toolchain/probe.cppm @@ -262,7 +262,10 @@ probe_sysroot(const std::filesystem::path& compilerBin, auto s = trim_line(*r); if (!s.empty() && std::filesystem::exists(s)) return s; } - // macOS fallback: use xcrun to discover the SDK path + // 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. if (auto sdk = mcpp::platform::macos::sdk_path()) return *sdk; return {}; diff --git a/src/toolchain/stdmod.cppm b/src/toolchain/stdmod.cppm index 34a1d06..0220d75 100644 --- a/src/toolchain/stdmod.cppm +++ b/src/toolchain/stdmod.cppm @@ -92,8 +92,26 @@ std::expected ensure_built( : mcpp::toolchain::gcc::std_bmi_path(sm.cacheDir); 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 std::string sysroot_flag; - if (!tc.sysroot.empty()) { + 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()); + } else if (!tc.sysroot.empty()) { sysroot_flag = std::format(" --sysroot='{}'", tc.sysroot.string()); } diff --git a/src/xlings.cppm b/src/xlings.cppm index b7faf06..572848e 100644 --- a/src/xlings.cppm +++ b/src/xlings.cppm @@ -609,24 +609,29 @@ int install_with_progress(const Env& env, std::string_view target, auto argsJson = std::format( R"({{"targets":["{}"],"yes":true}})", target); - if constexpr (mcpp::platform::is_windows) { - mcpp::platform::env::set("XLINGS_HOME", env.home.string()); - mcpp::platform::env::set("XLINGS_PROJECT_DIR", ""); - std::error_code ec_mkdir; - std::filesystem::create_directories(env.home, ec_mkdir); - // Use direct `install` command instead of `interface install_packages` - // on Windows. The NDJSON interface may have issues with large packages - // where the extraction subprocess doesn't respect XLINGS_HOME. - auto directCmd = std::format("{} install {} -y", - env.binary.string(), target); - int directRc = mcpp::platform::process::run_silent(directCmd); + // All platforms: try direct `xlings install ... -y` first. + // The direct command is more reliable for large packages (e.g. LLVM + // ~800MB) because: + // - it doesn't pipe through NDJSON interface (simpler subprocess chain) + // - xlings manages its own stdin/stdout/stderr + // - extraction subprocess coordination works normally + // The NDJSON interface path is kept as a fallback for progress reporting. + { + auto directCmd = build_command_prefix(env) + + std::format(" install {} -y {}", target, mcpp::platform::shell::silent_redirect); + // Use std::system() directly — do NOT redirect stdin via std::string { if constexpr (mcpp::platform::is_windows) { - // Fallback to interface path if direct install fails return std::format("{} interface install_packages --args {} {}", - env.binary.string(), + build_command_prefix(env), shq(argsJson), mcpp::platform::null_redirect); } else {