Skip to content

Commit a900c05

Browse files
committed
fix(macos): per-unit stdlib link — static libc++ for distributables, system libc++ for tests
Statically linked libc++ SIGABRTs during static destruction unless the entry point guards with _Exit (mcpp/xlings do; gtest main does not): with the static link applied globally, every mcpp-test binary on macOS exited 6 (-hidden-l visibility isolation did not change that — the abort is in libc++'s own exit path, not a symbol clash). Split the stdlib choice per link unit via unit_ldflags: distributable targets (Binary/SharedLibrary) keep the static LLVM libc++ (-hidden-l archive form, portable across macOS versions), TestBinary targets link the system -lc++ — they only ever run on the build host. Other platforms unaffected (fields empty).
1 parent 1a96088 commit a900c05

2 files changed

Lines changed: 31 additions & 14 deletions

File tree

src/build/flags.cppm

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ struct CompileFlags {
3232
std::string bFlag; // -B<binutils> (for ninja ldflags)
3333
bool staticStdlib = true;
3434
std::string linkage; // "static" or ""
35+
// macOS per-unit C++ stdlib link (appended via unit_ldflags):
36+
// distributable targets get the static LLVM libc++ (portable across
37+
// macOS versions), TestBinary targets get the system -lc++ — they
38+
// only ever run on the build host, and statically linked libc++
39+
// SIGABRTs during static destruction unless the entry point guards
40+
// with _Exit (mcpp/xlings do; gtest main does not). Empty on other
41+
// platforms (stdlib handled by their existing paths).
42+
std::string ldStdlibDefault;
43+
std::string ldStdlibTest;
3544
};
3645

3746
CompileFlags compute_flags(const BuildPlan& plan);
@@ -361,30 +370,29 @@ CompileFlags compute_flags(const BuildPlan& plan) {
361370
// the host Xcode (observed: Xcode 15.4's ld aborting at launch
362371
// on macos-14 CI when its libc++ resolution was diverted), and
363372
// lld ships with the exact toolchain doing the compile.
364-
std::string stdlib_link = " -lc++";
373+
f.ldStdlibDefault = " -lc++";
374+
f.ldStdlibTest = " -lc++";
365375
if (f.staticStdlib && !llvmRootForStdlib.empty()) {
366376
auto libDir = llvmRootForStdlib / "lib";
367377
auto libcxxA = libDir / "libc++.a";
368378
auto libcxxAbiA = libDir / "libc++abi.a";
369379
if (std::filesystem::exists(libcxxA)
370380
&& std::filesystem::exists(libcxxAbiA)) {
371-
// -hidden-l: ld64/lld feature made for exactly this —
372-
// links the ARCHIVE (never the sibling dylib) and gives
373-
// its symbols hidden visibility. Without it the static
374-
// libc++/libc++abi symbols clash with the system copies
375-
// that libSystem pulls in indirectly, and processes
376-
// SIGABRT during static destruction (observed: every
377-
// gtest binary exiting 6 on macos CI).
378-
stdlib_link = " -nostdlib++ -L" + escape_path(libDir)
379-
+ " -Wl,-hidden-lc++ -Wl,-hidden-lc++abi";
381+
// -hidden-l links the ARCHIVE (never the sibling dylib)
382+
// and gives its symbols hidden visibility so the static
383+
// copy can coexist with the system libc++ that libSystem
384+
// pulls in indirectly. Distributable targets only — see
385+
// the field comments in CompileFlags.
386+
f.ldStdlibDefault = " -nostdlib++ -L" + escape_path(libDir)
387+
+ " -Wl,-hidden-lc++ -Wl,-hidden-lc++abi";
380388
}
381389
}
382390
std::string version_min;
383391
if (!macosDeploymentTarget.empty()) {
384392
version_min = " -mmacosx-version-min=" + macosDeploymentTarget;
385393
}
386-
f.ld = std::format("{}{}{} -fuse-ld=lld{}{}{}{}", full_static, static_stdlib,
387-
b_flag, version_min, stdlib_link, user_ldflags, link_extra);
394+
f.ld = std::format("{}{}{} -fuse-ld=lld{}{}{}", full_static, static_stdlib,
395+
b_flag, version_min, user_ldflags, link_extra);
388396
} else {
389397
f.ld = std::format("{}{}{}{}{}{}{}{}", full_static, static_stdlib, link_toolchain_flags, b_flag,
390398
runtime_dirs, payload_ld, user_ldflags, link_extra);

src/build/ninja_backend.cppm

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -639,8 +639,17 @@ std::string emit_ninja_string(const BuildPlan& plan) {
639639
implicit.empty() ? std::string{} : " |" + implicit);
640640
if (auto flag = shared_soname_flag(lu); !flag.empty())
641641
out_line += " soname_flag = " + flag + "\n";
642-
if (auto flags = join_flags(lu.linkFlags); !flags.empty())
643-
out_line += " unit_ldflags =" + flags + "\n";
642+
{
643+
// Per-unit C++ stdlib link (macOS; empty elsewhere): test
644+
// binaries run on the build host and use the system -lc++,
645+
// distributable targets get the static LLVM libc++. See
646+
// CompileFlags::ldStdlibDefault/ldStdlibTest.
647+
std::string unit = join_flags(lu.linkFlags);
648+
unit += (lu.kind == mcpp::build::LinkUnit::TestBinary)
649+
? flags.ldStdlibTest : flags.ldStdlibDefault;
650+
if (!unit.empty())
651+
out_line += " unit_ldflags =" + unit + "\n";
652+
}
644653
append(std::move(out_line));
645654

646655
for (auto const& alias : lu.runtimeAliases) {

0 commit comments

Comments
 (0)