From 80849b51f6bcb071d6660d3226420f79de33205f Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Wed, 24 Jun 2026 09:36:49 +0800 Subject: [PATCH] fix(macos): pass SDK to the linker explicitly (-isysroot) + harden sdk_path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit macOS `mcpp build` could fail at link with 'library not found for -lSystem' (plus every libSystem symbol undefined) even though compile succeeded. Root cause: the macOS link command (flags.cppm f.ld) never passed the SDK — it relied on clang's IMPLICIT SDK detection (xcrun/SDKROOT -> ld64 -syslibroot) to resolve -lSystem. On GitHub's clean Xcode runners that always works, so the gap was latent (CI green); on a real machine where detection fails (misconfigured xcode-select, CLT-only, fresh bundled clang) ld64.lld can't find libSystem. Code identical 0.0.60->0.0.61, hence 'CI ok, fresh install not'. Fix: - flags.cppm: add `-isysroot ` to the macOS f.ld so the linker gets the SDK deterministically (compile side already had --sysroot). - macos.cppm: harden sdk_path() with fallbacks — SDKROOT env, `xcrun --sdk macosx`, `xcode-select -p` derived SDK, then well-known CLT/Xcode paths — so the SDK is found even when `xcrun --show-sdk-path` returns empty. - doc: .agents/docs/2026-06-24-macos-link-lsystem-sdk.md (analysis + fix). Build: clean self-host. ci-macos validates the macOS link path. --- .../docs/2026-06-24-macos-link-lsystem-sdk.md | 66 +++++++++++++++++++ src/build/flags.cppm | 15 ++++- src/platform/macos.cppm | 35 +++++++++- 3 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 .agents/docs/2026-06-24-macos-link-lsystem-sdk.md diff --git a/.agents/docs/2026-06-24-macos-link-lsystem-sdk.md b/.agents/docs/2026-06-24-macos-link-lsystem-sdk.md new file mode 100644 index 00000000..bb5c9496 --- /dev/null +++ b/.agents/docs/2026-06-24-macos-link-lsystem-sdk.md @@ -0,0 +1,66 @@ +# macOS `mcpp build`:`library not found for -lSystem` 根因与修复 + +**日期**: 2026-06-24 +**症状**: macOS 上全新安装的 mcpp 构建用户工程时,**编译通过、链接失败**: +``` +ld64.lld: error: library not found for -lSystem +ld64.lld: error: undefined symbol: __stack_chk_fail / __stdoutp / _Unwind_Resume / fflush / ... +``` +全是 libSystem(macOS libc)的符号。 + +## 1. 为什么「以前 ok、CI 也 ok、新装的就挂」 + +链接 macOS 代码**没变**(0.0.60→0.0.61 我合的 PR 都没碰 `flags.cppm`/`macos.cppm`/工具链 SDK 逻辑), +所以不是版本回归。关键在 `src/build/flags.cppm` 的 macOS 链接命令 `f.ld`: + +```cpp +// macOS 分支(needs_explicit_libcxx) +f.ld = std::format("{}{}{} -fuse-ld=lld{}{}{}", full_static, static_stdlib, + b_flag, version_min, user_ldflags, link_extra); +``` + +**`f.ld` 从不显式传 SDK**(无 `-isysroot`/`--sysroot`)。编译侧拿到了 `--sysroot=` +(flags.cppm:186,经 `xcrun --show-sdk-path`),但**链接侧依赖 clang 的「隐式 SDK 探测」** +(xcrun / `SDKROOT` → 给 ld64 传 `-syslibroot`)去找 `libSystem`。 + +- **GitHub CI 的干净 Xcode runner**:隐式探测正常 → `-syslibroot` 自动加上 → `-lSystem` 解析成功。**缺陷被掩盖**。 +- **用户真机**:当隐式探测失效——`xcode-select` 指向错/换了、只装了 Command Line Tools、 + 或新装 mcpp 拉了全新的捆绑 clang——ld64.lld 拿不到 `-syslibroot`,`/usr/lib/libSystem.tbd` + 搜不到 → `library not found for -lSystem`,继而所有 libc 符号未定义。 + +所以这是个**潜伏缺陷**:链接从来就靠隐式探测,真机环境一旦不满足就暴露。「编译过、链接挂」 +正是这个特征(编译用 bundled libc++ 头,不依赖 SDK 探测;链接才需要 libSystem)。 + +## 2. 修复 + +让 macOS 链接**显式带上 SDK**,不再赌隐式探测: + +1. **`flags.cppm`**:macOS `f.ld` 加 `-isysroot `(SDK 取自 `macos::sdk_path()`)。 + ```cpp + std::string macos_sdk; + if (auto sdk = mcpp::platform::macos::sdk_path()) + macos_sdk = " -isysroot " + escape_path(*sdk); + f.ld = std::format("{}{}{}{} -fuse-ld=lld{}{}{}", full_static, static_stdlib, + b_flag, macos_sdk, version_min, user_ldflags, link_extra); + ``` +2. **`macos.cppm::sdk_path()` 加固**:不止 `xcrun --show-sdk-path`,按序回退—— + `SDKROOT` 环境变量 → `xcrun --sdk macosx --show-sdk-path` → `xcode-select -p` 推导 + `.../SDKs/MacOSX.sdk` → 固定路径(`/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk`、 + Xcode.app 内 SDK)。即使 xcrun 返回空也能定位 SDK。 + +**为何安全**:CI 上 `sdk_path()` 返回 Xcode SDK,`-isysroot` = clang 本会隐式探到的同一个, +行为不变;真机上把以前「碰运气」变「确定」。`ci-macos` 验证未回归工作路径。 + +## 3. 用户侧自查(若仍失败) + +``` +xcrun --show-sdk-path # 应打印有效 SDK 路径 +xcode-select -p # 应指向 CommandLineTools 或 Xcode.app/Contents/Developer +``` +若 `xcrun` 为空:`xcode-select --install`(或 `sudo xcode-select --reset`)。 +加固后 mcpp 即便 xcrun 异常也会走回退路径找到 SDK。 + +## 4. 相关 +- macOS 部署底线 / 静态 libc++:xlings `.agents/docs/2026-06-05-macos-min-version-support.md`。 +- 同批 macOS 首跑问题(单独追踪):ninja bootstrap ~145s、首跑需回车(stdin)、 + `xlings update` 子索引 artifact 404。 diff --git a/src/build/flags.cppm b/src/build/flags.cppm index 66f24ed5..d3d7d5b5 100644 --- a/src/build/flags.cppm +++ b/src/build/flags.cppm @@ -417,8 +417,19 @@ CompileFlags compute_flags(const BuildPlan& plan) { if (!macosDeploymentTarget.empty()) { version_min = " -mmacosx-version-min=" + macosDeploymentTarget; } - f.ld = std::format("{}{}{} -fuse-ld=lld{}{}{}", full_static, static_stdlib, - b_flag, version_min, user_ldflags, link_extra); + // Pass the macOS SDK to the LINKER explicitly. The link otherwise relies + // on clang's implicit SDK detection (xcrun/SDKROOT → ld64 -syslibroot) + // to resolve -lSystem and friends. On a clean Xcode (CI) that works, so + // the gap is latent; but on a machine where that detection fails — + // misconfigured `xcode-select`, Command-Line-Tools-only, or a freshly + // installed bundled clang — ld64.lld dies with "library not found for + // -lSystem". -isysroot makes it deterministic regardless of the host's + // developer-tools state. (compile side already gets --sysroot above.) + std::string macos_sdk; + if (auto sdk = mcpp::platform::macos::sdk_path()) + macos_sdk = " -isysroot " + escape_path(*sdk); + f.ld = std::format("{}{}{}{} -fuse-ld=lld{}{}{}", full_static, static_stdlib, + b_flag, macos_sdk, version_min, user_ldflags, link_extra); } else { f.ld = std::format("{}{}{}{}{}{}{}{}", full_static, static_stdlib, link_toolchain_flags, b_flag, runtime_dirs, payload_ld, user_ldflags, link_extra); diff --git a/src/platform/macos.cppm b/src/platform/macos.cppm index 99db91e2..8322dc02 100644 --- a/src/platform/macos.cppm +++ b/src/platform/macos.cppm @@ -109,9 +109,38 @@ bool has_xcode_clt() { std::optional sdk_path() { #if defined(__APPLE__) - auto result = run_capture_trimmed("xcrun --show-sdk-path 2>/dev/null"); - if (!result.empty() && std::filesystem::exists(result)) - return std::filesystem::path(result); + // 1. Explicit override wins (matches clang's own SDKROOT handling). + if (const char* env = std::getenv("SDKROOT"); env && *env) { + std::filesystem::path p(env); + if (std::filesystem::exists(p)) return p; + } + // 2. xcrun — the canonical query. Try the generic form, then the + // macosx-specific one (works even when the active developer dir's + // default SDK isn't macOS, e.g. an iOS-defaulted setup). + for (const char* cmd : {"xcrun --show-sdk-path 2>/dev/null", + "xcrun --sdk macosx --show-sdk-path 2>/dev/null"}) { + auto result = run_capture_trimmed(cmd); + if (!result.empty() && std::filesystem::exists(result)) + return std::filesystem::path(result); + } + // 3. Derive from the active developer dir (`xcode-select -p`) — covers + // machines where xcrun is misconfigured but the SDK is present. + auto devdir = run_capture_trimmed("xcode-select -p 2>/dev/null"); + if (!devdir.empty()) { + std::filesystem::path base(devdir); + for (auto cand : { + base / "Platforms" / "MacOSX.platform" / "Developer" / "SDKs" / "MacOSX.sdk", + base / "SDKs" / "MacOSX.sdk" }) { + if (std::filesystem::exists(cand)) return cand; + } + } + // 4. Well-known fixed locations (Command-Line-Tools-only / standard Xcode). + for (const char* p : { + "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk", + "/Applications/Xcode.app/Contents/Developer/Platforms/" + "MacOSX.platform/Developer/SDKs/MacOSX.sdk" }) { + if (std::filesystem::exists(p)) return std::filesystem::path(p); + } #endif return std::nullopt; }