Skip to content

Commit f9d8238

Browse files
committed
fix: macOS link strategy — no sysroot for linker + 3-step self-host CI
Root cause: passing --sysroot to macOS linker forces it to use SDK .tbd stubs which lack libc++abi exports. Fix: only pass sysroot to compiler (for headers), let clang++ driver handle linking natively. CI now validates 3 steps: 1. xmake builds mcpp (bootstrap) 2. That mcpp builds itself (self-host) 3. The self-hosted mcpp builds itself again (stability proof)
1 parent 072c25a commit f9d8238

2 files changed

Lines changed: 61 additions & 13 deletions

File tree

.github/workflows/ci-macos.yml

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -268,15 +268,57 @@ jobs:
268268
*) echo "FAIL: unexpected platform"; exit 1 ;;
269269
esac
270270
271-
- name: Install mcpp@0.0.16 via xlings
271+
- name: Build mcpp (xmake bootstrap)
272272
run: |
273-
xlings install mcpp -y
274-
mcpp --version
273+
# Step 1: Use xmake to produce first mcpp binary from source
274+
brew install xmake
275+
cat > xmake.lua << 'XMAKE'
276+
add_rules("mode.release")
277+
set_languages("c++23")
278+
package("cmdline")
279+
set_homepage("https://github.com/mcpplibs/cmdline")
280+
add_urls("https://github.com/mcpplibs/cmdline/archive/refs/tags/$(version).tar.gz")
281+
add_versions("0.0.1", "3fb2f5495c1a144485b3cbb2e43e27059151633460f702af0f3851cbff387ef0")
282+
on_install(function (package)
283+
import("package.tools.xmake").install(package)
284+
end)
285+
package_end()
286+
add_requires("cmdline 0.0.1")
287+
target("mcpp")
288+
set_kind("binary")
289+
add_files("src/main.cpp")
290+
add_files("src/**.cppm")
291+
add_packages("cmdline")
292+
add_includedirs("src/libs/json")
293+
set_policy("build.c++.modules", true)
294+
XMAKE
295+
xmake f -y -m release --toolchain=llvm --sdk="$LLVM_ROOT"
296+
xmake build -y mcpp
297+
rm -f xmake.lua
298+
MCPP_V1=$(find build -name mcpp -type f -perm +111 | head -1)
299+
test -x "$MCPP_V1"
300+
"$MCPP_V1" --version
301+
echo ":: Step 1 PASS: xmake produced mcpp"
302+
echo "MCPP_V1=$MCPP_V1" >> "$GITHUB_ENV"
275303
276304
- name: Self-host (mcpp builds mcpp)
277305
run: |
306+
# Step 2: Use the xmake-built mcpp to build itself
307+
export PATH="$(dirname "$MCPP_V1"):$PATH"
278308
mcpp build
279-
SELFHOST=$(find target -path "*/bin/mcpp" | head -1)
280-
test -x "$SELFHOST"
281-
"$SELFHOST" --version
282-
echo ":: Self-host successful!"
309+
MCPP_V2=$(find target -path "*/bin/mcpp" -not -path "*/build/*" | head -1)
310+
test -x "$MCPP_V2"
311+
"$MCPP_V2" --version
312+
echo ":: Step 2 PASS: mcpp self-hosted"
313+
echo "MCPP_V2=$MCPP_V2" >> "$GITHUB_ENV"
314+
315+
- name: Self-host again (v2 builds itself)
316+
run: |
317+
# Step 3: Use the self-hosted mcpp to build itself again (proves stability)
318+
rm -rf target
319+
export PATH="$(dirname "$MCPP_V2"):$PATH"
320+
mcpp build
321+
MCPP_V3=$(find target -path "*/bin/mcpp" -not -path "*/build/*" | head -1)
322+
test -x "$MCPP_V3"
323+
"$MCPP_V3" --version
324+
echo ":: Step 3 PASS: mcpp self-host stable (v2 → v3)"

src/build/flags.cppm

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -161,15 +161,21 @@ CompileFlags compute_flags(const BuildPlan& plan) {
161161
runtime_dirs += " -L" + escape_path(dir);
162162
runtime_dirs += " -Wl,-rpath," + escape_path(dir);
163163
}
164+
164165
#if defined(__APPLE__)
165-
// macOS: explicitly link libc++ when using Clang with xlings LLVM.
166-
// The cfg's -nostdinc++ suppresses the implicit -lc++ that clang++ normally adds.
167-
std::string stdlib_link = isClang ? " -lc++" : "";
166+
// macOS linking strategy:
167+
// - Do NOT pass --sysroot to the linker. The macOS SDK contains .tbd
168+
// stubs that lack libc++abi exports, causing undefined symbols for
169+
// exception/RTTI infrastructure. Without --sysroot, clang++ driver
170+
// uses the system default (/usr/lib/libc++, libc++abi, libSystem)
171+
// which works correctly.
172+
// - sysroot is still used for compilation (finding system headers).
173+
f.ld = std::format("{}{}{}{}", full_static, static_stdlib, b_flag, runtime_dirs);
168174
#else
169-
std::string stdlib_link;
175+
// Linux: sysroot must be passed to linker (glibc/musl lives there)
176+
f.ld = std::format("{}{}{}{}{}", full_static, static_stdlib, sysroot_flag, b_flag,
177+
runtime_dirs);
170178
#endif
171-
f.ld = std::format("{}{}{}{}{}{}", full_static, static_stdlib, sysroot_flag, b_flag,
172-
runtime_dirs, stdlib_link);
173179

174180
return f;
175181
}

0 commit comments

Comments
 (0)