From 518bd9200e8c0946145adb484dde758c01eae67d Mon Sep 17 00:00:00 2001 From: sunrisepeak Date: Sat, 6 Jun 2026 00:13:15 +0800 Subject: [PATCH] feat(macos): built-in 14.0 deployment floor + static libc++ by default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A fresh user's `mcpp new hello && mcpp run` on macOS 14 died at dyld (`__ZNSt3__119__is_posix_terminalEP7__sFILE` missing from the system libc++): with no declared floor the binary compiled against libc++-20 headers but dynamically linked the host system libc++. Caught by the macos-14 fresh-install CI lane. The resolver (platform::macos::deployment_target) now falls back to a built-in 14.0 floor (rustc-style baseline; the official LLVM static archive floor), so the declared-floor-implies-static semantic becomes portable-by-default: every macOS build gets minos=14.0 + static LLVM libc++ out of the box. Opt-out: [build] static_stdlib = false. Both blockers that deferred this default are resolved: split-brain SIGSEGV fixed by -Wl,-load_hidden (#117 forensics, shipped in 0.0.50), fingerprint/stdmod drift fixed by the single resolver (#119) — which is why the default lives in the resolver, not at any single consumer. --- docs/05-mcpp-toml.md | 20 +++++++++++--------- src/build/flags.cppm | 27 +++++++++++++-------------- src/cli.cppm | 10 ++++------ src/platform/macos.cppm | 24 ++++++++++++++++++------ 4 files changed, 46 insertions(+), 35 deletions(-) diff --git a/docs/05-mcpp-toml.md b/docs/05-mcpp-toml.md index 31f9b606..d22b4770 100644 --- a/docs/05-mcpp-toml.md +++ b/docs/05-mcpp-toml.md @@ -98,15 +98,17 @@ macos_deployment_target = "14.0" # macOS 产物的最低支持系统版本(仅 (`LC_BUILD_VERSION minos`),即二进制能运行的最老 macOS。优先级与各生态 惯例一致:环境变量 `MACOSX_DEPLOYMENT_TARGET`(单次调用的显式覆盖, cargo/rustc、cc 等同样尊重该变量)> 本字段(项目默认,类似 SwiftPM 的 -`platforms:`)> 工具链/SDK 默认。该值会进入 BMI 指纹——切换 target 会 -自动重建模块缓存。 - -**声明 floor 即静态运行时**:显式设置了 deployment target(env 或本 -字段)且 `static_stdlib = true`(默认)时,macOS 链接会静态链入 LLVM -自带的 libc++/libc++abi —— 系统 libc++ 会把实际可运行版本钉死在构建机 -的 OS(老系统缺新符号,如 `std::print` 的支撑符号),静态化才能真正 -兑现声明的 floor。注意 LLVM 官方静态库自身的下限是 **14.0**。未声明 -floor 时保持动态系统 libc++(产物只保证在构建机同版本及以上运行)。 +`platforms:`)> **内建默认 `14.0`**(rustc 风格——每个 target 都有基线, +14.0 即 LLVM 官方静态库自身的下限)。该值会进入 BMI 指纹——切换 target +会自动重建模块缓存。 + +**默认即静态运行时(portable by default)**:`static_stdlib = true` +(默认)时,macOS 链接会静态链入 LLVM 自带的 libc++/libc++abi —— +系统 libc++ 会把实际可运行版本钉死在构建机的 OS(老系统缺新符号, +如 `std::print` 的支撑符号),静态化才能真正兑现 floor。因此默认构建的 +产物在任何 macOS ≥ 14 上开箱即用。设 `static_stdlib = false` 退回动态 +系统 libc++(产物只保证在构建机同版本及以上运行)。更低 floor(11–13) +需自建 libc++ 归档(已验证可行,数据级切换,按需提供)。 C++ 标准不要通过 `build.cxxflags = ["-std=..."]` 配置。请使用: diff --git a/src/build/flags.cppm b/src/build/flags.cppm index 08fb435d..059ed6bf 100644 --- a/src/build/flags.cppm +++ b/src/build/flags.cppm @@ -369,23 +369,22 @@ CompileFlags compute_flags(const BuildPlan& plan) { // lld ships with the exact toolchain doing the compile. f.ldStdlibDefault = " -lc++"; f.ldStdlibTest = " -lc++"; - // Static libc++ is tied to an EXPLICIT deployment floor: when the - // user (or the release pipeline) declares a minimum macOS via the - // env var or [build] macos_deployment_target, the static LLVM - // libc++ is what makes that floor real (the system libc++ caps it - // at the build host's OS). With no declared floor, keep the - // 0.0.49 behavior — dynamic system libc++, host-coupled. - // - // TODO(macos-static-default): flip static to the unconditional - // default (rust-style "portable by default") once two tracked - // issues are fixed — (1) mixed C/C++ static binaries SIGSEGV at - // runtime (e2e 36_llvm_toolchain: answer.c + std::cout main.cpp, - // exit 139; root cause not yet isolated), (2) the std-module - // staging/fingerprint boundary (see canonical_compile_flags). + // Static libc++ + the deployment floor are the DEFAULT (rust-style + // "portable by default"): the resolver always yields a floor on + // macOS (built-in 14.0 unless env/manifest override), and the + // static LLVM libc++ is what makes that floor real — the system + // libc++ caps binaries at the build host's OS (a fresh user's + // std::println hello on macOS 14 died at dyld against the system + // libc++ before this). Opt-out: [build] static_stdlib = false + // (host-coupled dynamic libc++, the pre-0.0.52 no-declaration + // behavior). The two blockers that deferred this default are + // resolved: (1) mixed C/C++ split-brain SIGSEGV — fixed by + // -load_hidden (PR #117 forensics), (2) std-module staging / + // fingerprint drift — fixed by the single resolver (PR #119). // TODO(macos-floor-11): the official LLVM archives are built for // macOS 14; supporting 11-13 needs a custom libc++ build shipped // via xlings-res (data-only change — swap the archive source). - // Both tracked in xlings + // Tracked in xlings // .agents/docs/2026-06-05-macos-min-version-support.md §5. if (f.staticStdlib && !macosDeploymentTarget.empty() && !llvmRootForStdlib.empty()) { diff --git a/src/cli.cppm b/src/cli.cppm index d5707259..2831e63c 100644 --- a/src/cli.cppm +++ b/src/cli.cppm @@ -594,13 +594,11 @@ std::string canonical_compile_flags(const mcpp::manifest::Manifest& m) { // into the fingerprint so switching targets rebuilds the BMI cache // instead of dying with a module config mismatch. // - // TODO(macos-default-floor): a built-in default floor (rustc-style, - // see the 0.0.50 revert) cannot land until the std-module prebuild / - // staging pipeline consumes the SAME resolved value as this rule and - // flags.cppm — injecting a default here alone left the test build's + // The built-in default floor (rustc-style) lives in the single + // resolver (platform::macos::deployment_target), so this rule, the + // flags and the std-module prebuild always agree — the 0.0.50-era + // attempt to inject a default here alone left the test build's // std.pcm unstaged (import std failed wholesale on macos CI). - // Centralize the resolution in one helper, then re-land. - // See xlings .agents/docs/2026-06-05-macos-min-version-support.md §5. if constexpr (mcpp::platform::is_macos) { auto dtv = mcpp::platform::macos::deployment_target( m.buildConfig.macosDeploymentTarget); diff --git a/src/platform/macos.cppm b/src/platform/macos.cppm index c0cf6073..99db91e2 100644 --- a/src/platform/macos.cppm +++ b/src/platform/macos.cppm @@ -32,14 +32,24 @@ bool has_xcode_clt(); // Returns the SDK path if found, or nullopt. std::optional sdk_path(); +// Built-in default deployment floor (rustc-style: every target has a +// baseline). 14.0 = the floor of the official LLVM static libc++ +// archives; with the default-static stdlib this makes `mcpp run` +// binaries portable to any macOS ≥ 14 out of the box (no declaration +// needed — a fresh user's std::println hello on macOS 14 used to die +// at dyld against the system libc++). Lower floors need a custom +// libc++ build (tracked; data-only swap via xlings-res). +inline constexpr std::string_view default_deployment_target = "14.0"; + // Resolve the effective macOS deployment target: the // MACOSX_DEPLOYMENT_TARGET env var (explicit per-invocation override, // the convention cargo/rustc/cc honor) wins over `manifestValue` (the -// [build] macos_deployment_target project default); empty means -// toolchain/SDK default. THE single source of truth — flags.cppm, the -// BMI fingerprint rule and the std-module prebuild must all consume -// this same resolution, or cached std.pcm modules drift from the TUs -// (config-mismatch / unstaged-module failures observed on macos CI). +// [build] macos_deployment_target project default), which wins over +// the built-in default floor — the result is never empty on macOS. +// THE single source of truth — flags.cppm, the BMI fingerprint rule +// and the std-module prebuild must all consume this same resolution, +// or cached std.pcm modules drift from the TUs (config-mismatch / +// unstaged-module failures observed on macos CI). std::string deployment_target(std::string_view manifestValue); // Return macOS-specific runtime library directories for LLVM toolchains. @@ -47,7 +57,9 @@ std::string deployment_target(std::string_view manifestValue) { #if defined(__APPLE__) if (const char* dt = std::getenv("MACOSX_DEPLOYMENT_TARGET"); dt && *dt) return dt; - return std::string(manifestValue); + if (!manifestValue.empty()) + return std::string(manifestValue); + return std::string(default_deployment_target); #else (void)manifestValue; return {};