From cba74d7ca4bcb523587076a035784674a594b11b Mon Sep 17 00:00:00 2001 From: Steve Pfister Date: Wed, 18 Mar 2026 23:06:52 -0400 Subject: [PATCH 1/5] Prefer AppleClang from Xcode toolchain on macOS The macos-15 runner image (20260317.0278) upgraded LLVM from 17 to 18, placing clang-18 at /usr/local/bin/clang-18. The init-compiler.sh version search loop finds this upstream LLVM before falling through to unversioned clang (AppleClang), causing three classes of build failures: - NativeAOT: invalid CFI advance_loc assembly errors (LLVM 18 stricter) - Native libs: Swift linker undefined symbols (missing Apple Swift paths) - CoreCLR: CMake LINK_GROUP RESCAN unsupported (LLVM linker vs Apple ld) On Darwin, use xcrun --find clang to resolve the Xcode toolchain clang (AppleClang) instead of searching PATH for versioned LLVM binaries. This matches the pattern Mono already uses in mono.proj. The existing CLR_CC/CLR_CXX override and explicit version requests (e.g. clang-18) are preserved as escape hatches. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- eng/common/native/init-compiler.sh | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/eng/common/native/init-compiler.sh b/eng/common/native/init-compiler.sh index 9a0e1f2b4567b5..4052c00e001ffa 100644 --- a/eng/common/native/init-compiler.sh +++ b/eng/common/native/init-compiler.sh @@ -80,8 +80,20 @@ EOF if [ -z "$CLR_CC" ]; then + if [ "$__baseOS" = "Darwin" ] && [ "$compiler" = "clang" ] && [ -z "$majorVersion" ]; then + # On Darwin, prefer the Xcode toolchain clang (AppleClang) over any + # versioned LLVM clang that may be installed via Homebrew or other + # package managers. AppleClang is required for correct Apple platform + # linking, Swift interop, and CFI directive handling. + xcrun_cc="$(xcrun --find clang 2>/dev/null)" + if [ -n "$xcrun_cc" ]; then + CC="$xcrun_cc" + CXX="$(xcrun --find clang++ 2>/dev/null)" + fi + fi + # Set default versions - if [ -z "$majorVersion" ]; then + if [ -z "$majorVersion" ] && [ -z "$CC" ]; then minVersion=8 maxVersion="$((maxVersion + 1))" # +1 for headspace i="$maxVersion" @@ -101,7 +113,7 @@ if [ -z "$CLR_CC" ]; then CXX="$(command -v "$cxxCompiler" 2> /dev/null)" set_compiler_version_from_CC fi - else + elif [ -n "$majorVersion" ] && [ -z "$CC" ]; then desired_version="$(check_version_exists "$majorVersion")" if [ "$desired_version" = "-1" ]; then echo "Error: Could not find specific version of $compiler: $majorVersion." From 4fbe54899900b935d0edba918be64139de472a1e Mon Sep 17 00:00:00 2001 From: Steve Pfister Date: Thu, 19 Mar 2026 00:58:33 -0400 Subject: [PATCH 2/5] Use Apple toolchain shim instead of raw Xcode clang on macOS The previous commit used xcrun --find clang to resolve the Xcode toolchain clang directly. While this correctly avoids Homebrew LLVM, the raw Xcode clang does not inject -isysroot automatically, causing the linker to fail to find system libraries (libdl, libobjc, libswiftCore, etc.) on macOS 26 with Xcode 26. Switch to /usr/bin/clang, the Apple-provided shim that always delegates to the active Xcode's AppleClang and automatically injects -isysroot pointing to the macOS SDK. This preserves the Homebrew LLVM avoidance while ensuring native linking works correctly. --- eng/common/native/init-compiler.sh | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/eng/common/native/init-compiler.sh b/eng/common/native/init-compiler.sh index 4052c00e001ffa..163c48c106b4e4 100644 --- a/eng/common/native/init-compiler.sh +++ b/eng/common/native/init-compiler.sh @@ -81,14 +81,16 @@ EOF if [ -z "$CLR_CC" ]; then if [ "$__baseOS" = "Darwin" ] && [ "$compiler" = "clang" ] && [ -z "$majorVersion" ]; then - # On Darwin, prefer the Xcode toolchain clang (AppleClang) over any + # On Darwin, prefer /usr/bin/clang (the Apple toolchain shim) over any # versioned LLVM clang that may be installed via Homebrew or other - # package managers. AppleClang is required for correct Apple platform - # linking, Swift interop, and CFI directive handling. - xcrun_cc="$(xcrun --find clang 2>/dev/null)" - if [ -n "$xcrun_cc" ]; then - CC="$xcrun_cc" - CXX="$(xcrun --find clang++ 2>/dev/null)" + # package managers. The shim always delegates to the active Xcode's + # AppleClang and automatically injects -isysroot, which is required + # for the linker to find system libraries, frameworks, and Swift + # runtime paths. Calling the Xcode toolchain clang directly (via + # xcrun --find) skips the implicit -isysroot, breaking native linking. + if [ -x "/usr/bin/clang" ]; then + CC="/usr/bin/clang" + CXX="/usr/bin/clang++" fi fi From f9a65658f2b6ba2e4b8278c377f3f999615bfdac Mon Sep 17 00:00:00 2001 From: Steve Pfister Date: Thu, 19 Mar 2026 06:58:18 -0400 Subject: [PATCH 3/5] Export CC and CXX in gen-buildsys.sh so CMake uses the resolved compiler gen-buildsys.sh sources init-compiler.sh which resolves the correct compiler into CC/CXX, but only CCC_CC/CCC_CXX (scan-build variables) were exported. CMake ignores those and falls back to PATH-based detection, which can find Homebrew LLVM 18 before Apple clang on macOS CI runners. This caused native host test libraries like libmockhostpolicy.dylib to link against Homebrew's libunwind. --- eng/native/gen-buildsys.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/native/gen-buildsys.sh b/eng/native/gen-buildsys.sh index c7d06b70ee3b71..5d6cd0f4b59862 100755 --- a/eng/native/gen-buildsys.sh +++ b/eng/native/gen-buildsys.sh @@ -32,7 +32,7 @@ if [[ "$compiler" != "default" ]]; then CCC_CXX="$CXX" fi -export CCC_CC CCC_CXX +export CCC_CC CCC_CXX CC CXX buildtype=DEBUG code_coverage=OFF From 5c5fdb442542c386e1db68499ecc9deb580b8042 Mon Sep 17 00:00:00 2001 From: Steve Pfister Date: Thu, 19 Mar 2026 09:06:42 -0400 Subject: [PATCH 4/5] Remove Homebrew LLVM during macOS CI setup The CI runner image may ship with a Homebrew LLVM whose libraries (e.g., an x86_64-only libunwind.dylib in /usr/local/lib) conflict with the Apple SDK and break native linking. The build uses Apple clang from /usr/bin/clang exclusively and does not need Homebrew LLVM. --- eng/common/native/init-compiler.sh | 18 ++---------------- eng/common/native/install-dependencies.sh | 7 +++++++ eng/native/gen-buildsys.sh | 2 +- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/eng/common/native/init-compiler.sh b/eng/common/native/init-compiler.sh index 163c48c106b4e4..9a0e1f2b4567b5 100644 --- a/eng/common/native/init-compiler.sh +++ b/eng/common/native/init-compiler.sh @@ -80,22 +80,8 @@ EOF if [ -z "$CLR_CC" ]; then - if [ "$__baseOS" = "Darwin" ] && [ "$compiler" = "clang" ] && [ -z "$majorVersion" ]; then - # On Darwin, prefer /usr/bin/clang (the Apple toolchain shim) over any - # versioned LLVM clang that may be installed via Homebrew or other - # package managers. The shim always delegates to the active Xcode's - # AppleClang and automatically injects -isysroot, which is required - # for the linker to find system libraries, frameworks, and Swift - # runtime paths. Calling the Xcode toolchain clang directly (via - # xcrun --find) skips the implicit -isysroot, breaking native linking. - if [ -x "/usr/bin/clang" ]; then - CC="/usr/bin/clang" - CXX="/usr/bin/clang++" - fi - fi - # Set default versions - if [ -z "$majorVersion" ] && [ -z "$CC" ]; then + if [ -z "$majorVersion" ]; then minVersion=8 maxVersion="$((maxVersion + 1))" # +1 for headspace i="$maxVersion" @@ -115,7 +101,7 @@ if [ -z "$CLR_CC" ]; then CXX="$(command -v "$cxxCompiler" 2> /dev/null)" set_compiler_version_from_CC fi - elif [ -n "$majorVersion" ] && [ -z "$CC" ]; then + else desired_version="$(check_version_exists "$majorVersion")" if [ "$desired_version" = "-1" ]; then echo "Error: Could not find specific version of $compiler: $majorVersion." diff --git a/eng/common/native/install-dependencies.sh b/eng/common/native/install-dependencies.sh index 4742177a7685d5..19dc3e109b58d5 100755 --- a/eng/common/native/install-dependencies.sh +++ b/eng/common/native/install-dependencies.sh @@ -47,6 +47,13 @@ case "$os" in export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 # Skip brew update for now, see https://github.com/actions/setup-python/issues/577 # brew update --preinstall + + # Remove Homebrew LLVM if present. The CI runner image may ship with a + # Homebrew LLVM whose libraries (e.g., libunwind.dylib) are the wrong + # architecture or conflict with the Apple SDK, breaking native linking. + # The build uses Apple clang from /usr/bin/clang exclusively. + brew uninstall --ignore-dependencies llvm 2>/dev/null || true + brew bundle --no-upgrade --file=- < Date: Thu, 19 Mar 2026 16:59:54 +0100 Subject: [PATCH 5/5] Change Homebrew LLVM uninstallation to llvm@18 --- eng/common/native/install-dependencies.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/common/native/install-dependencies.sh b/eng/common/native/install-dependencies.sh index 19dc3e109b58d5..23b42886236ff6 100755 --- a/eng/common/native/install-dependencies.sh +++ b/eng/common/native/install-dependencies.sh @@ -52,7 +52,7 @@ case "$os" in # Homebrew LLVM whose libraries (e.g., libunwind.dylib) are the wrong # architecture or conflict with the Apple SDK, breaking native linking. # The build uses Apple clang from /usr/bin/clang exclusively. - brew uninstall --ignore-dependencies llvm 2>/dev/null || true + brew uninstall --ignore-dependencies llvm@18 2>/dev/null || true brew bundle --no-upgrade --file=- <