diff --git a/README.md b/README.md index 32f3abd3..3bfac564 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,8 @@ The change-detection and version-verification the workflow gates on live in the The staged binary is written to `artifacts/native//`. The LLVM release distribution bundles both the prebuilt `libclang` and the `lib/cmake/{llvm,clang}` config, headers, and import libraries used as `PATH_TO_LLVM` when building `libClangSharp`, so no from-source LLVM build is required (the manual [Building Native](#building-native) steps remain the way to reproduce a build locally). Because lifting `libclang` is just unpacking prebuilt binaries, the workflow does all five runtimes on a single Windows runner (its bundled `bsdtar` handles `.tar.xz`); `libClangSharp` is compiled per-runtime on native runners. +The `win-arm64` runtime is compiled with the LLVM release's own `clang-cl` (via `-G Ninja`) rather than MSVC. The official LLVM binaries are clang-built, and on Arm64 clang and MSVC disagree on the record layout of over-aligned non-POD base classes (e.g. `clang::TemplateSpecializationType`), so an MSVC-built shim reads members of clang-built types at the wrong offset. The other runtimes are unaffected (`win-x64` layouts agree between the two, and the Unix runtimes already use clang). + The jobs run when: * **libclang** — the tracked LLVM major/minor version changes, or the workflow is dispatched manually with the `libclang` input set. diff --git a/scripts/build.ps1 b/scripts/build.ps1 index 7177ba59..a0180ab2 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -162,17 +162,72 @@ function Extract-Libclang([string] $runtime, [string] $source, [string] $destina Copy-Item -Path $lib.FullName -Destination (Join-Path -Path $destination -ChildPath $name) } -function Build-Libclangsharp([string] $runtime, [string] $source, [string] $destination) { - $arch = switch ($runtime) { - "win-x64" { "x64" } - "win-arm64" { "ARM64" } - default { throw "'$runtime' cannot build libClangSharp on Windows; use build.sh on the matching runner" } +function Get-VisualStudioInstallPath() { + $vswhere = Join-Path -Path ${env:ProgramFiles(x86)} -ChildPath "Microsoft Visual Studio\Installer\vswhere.exe" + + if (-not (Test-Path -Path $vswhere)) { + throw "'vswhere.exe' was not found; a Visual Studio installation is required to build libClangSharp on Windows" + } + + $vsPath = & $vswhere -latest -prerelease -products * -property installationPath | Select-Object -First 1 + + if (-not $vsPath) { + throw "No Visual Studio installation was found via 'vswhere.exe'" + } + + return $vsPath +} + +function Import-VisualStudioEnvironment([string] $vsPath, [string] $arch) { + $vcvars = Join-Path -Path $vsPath -ChildPath "VC\Auxiliary\Build\vcvarsall.bat" + + if (-not (Test-Path -Path $vcvars)) { + throw "'vcvarsall.bat' was not found under '$vsPath'" + } + + # clang-cl relies on the MSVC toolchain, Windows SDK, and linker being on + # INCLUDE/LIB/PATH; import the developer environment into this process so the + # subsequent cmake invocation inherits it. + & cmd /c "call `"$vcvars`" $arch > nul && set" | ForEach-Object { + $pair = $_ -split "=", 2 + if ($pair.Count -eq 2) { + [System.Environment]::SetEnvironmentVariable($pair[0], $pair[1]) + } } +} +function Build-Libclangsharp([string] $runtime, [string] $source, [string] $destination) { $nativeBuildDir = Join-Path -Path $ArtifactsDir -ChildPath "bin\native\$runtime" $pathToLlvm = (Resolve-Path -Path $source).Path - & cmake -B "$nativeBuildDir" -S "$RepoRoot" -A "$arch" "-Thost=$arch" "-DPATH_TO_LLVM=$pathToLlvm" + if ($runtime -eq "win-arm64") { + # The official LLVM release binaries are built with clang. On win-arm64, + # clang and MSVC disagree on the record layout of over-aligned non-POD base + # classes (e.g. clang::TemplateSpecializationType), so an MSVC-built shim + # reads members of clang-built types at the wrong offset and returns garbage. + # Build the shim with the release's own clang-cl so the layouts match. + # win-x64 layouts agree between the two, so it keeps using MSVC. + $vsPath = Get-VisualStudioInstallPath + $hostArch = if ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq [System.Runtime.InteropServices.Architecture]::Arm64) { "arm64" } else { "amd64_arm64" } + Import-VisualStudioEnvironment -vsPath $vsPath -arch $hostArch + + $env:PATH = "$pathToLlvm\bin;$env:PATH" + + $ninja = (Get-Command -Name "ninja" -ErrorAction SilentlyContinue).Source + if (-not $ninja) { + $ninja = Join-Path -Path $vsPath -ChildPath "Common7\IDE\CommonExtensions\Microsoft\CMake\Ninja\ninja.exe" + } + + & cmake -G Ninja -B "$nativeBuildDir" -S "$RepoRoot" "-DCMAKE_MAKE_PROGRAM=$ninja" -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl "-DPATH_TO_LLVM=$pathToLlvm" + } + else { + $arch = switch ($runtime) { + "win-x64" { "x64" } + default { throw "'$runtime' cannot build libClangSharp on Windows; use build.sh on the matching runner" } + } + + & cmake -B "$nativeBuildDir" -S "$RepoRoot" -A "$arch" "-Thost=$arch" "-DPATH_TO_LLVM=$pathToLlvm" + } if ($LastExitCode -ne 0) { throw "'cmake' configure failed for '$runtime'" diff --git a/sources/libClangSharp/CMakeLists.txt b/sources/libClangSharp/CMakeLists.txt index 09ab3d17..729b9a20 100644 --- a/sources/libClangSharp/CMakeLists.txt +++ b/sources/libClangSharp/CMakeLists.txt @@ -42,20 +42,37 @@ find_package(Clang REQUIRED CONFIG # into the imported LLVMDebugInfoPDB target in LLVMExports.cmake. That path # won't exist on another machine (or a CI runner) with a different Visual # Studio, causing LNK1181. Repoint it at the DIA SDK of the Visual Studio -# actually being used for this build. -if(MSVC AND CMAKE_GENERATOR_INSTANCE AND TARGET LLVMDebugInfoPDB) - if(CMAKE_GENERATOR_PLATFORM MATCHES "[Aa][Rr][Mm]64") - set(_clangsharp_diaguids "${CMAKE_GENERATOR_INSTANCE}/DIA SDK/lib/arm64/diaguids.lib") - elseif(CMAKE_GENERATOR_PLATFORM MATCHES "[Ww]in32") - set(_clangsharp_diaguids "${CMAKE_GENERATOR_INSTANCE}/DIA SDK/lib/diaguids.lib") +# actually being used for this build. The Visual Studio generator (win-x64) +# exposes its root and target as CMAKE_GENERATOR_INSTANCE/PLATFORM; Ninja + +# clang-cl (win-arm64) gets them from VSINSTALLDIR and the compiler arch id. +if(MSVC AND TARGET LLVMDebugInfoPDB) + if(CMAKE_GENERATOR_INSTANCE) + set(_clangsharp_vs "${CMAKE_GENERATOR_INSTANCE}") else() - set(_clangsharp_diaguids "${CMAKE_GENERATOR_INSTANCE}/DIA SDK/lib/amd64/diaguids.lib") + file(TO_CMAKE_PATH "$ENV{VSINSTALLDIR}" _clangsharp_vs) endif() + string(REGEX REPLACE "/+$" "" _clangsharp_vs "${_clangsharp_vs}") - get_target_property(_clangsharp_pdb_libs LLVMDebugInfoPDB INTERFACE_LINK_LIBRARIES) - if(EXISTS "${_clangsharp_diaguids}") - string(REGEX REPLACE "[^;]*[Dd][Ii][Aa] SDK[^;]*diaguids\\.lib" "${_clangsharp_diaguids}" _clangsharp_pdb_libs "${_clangsharp_pdb_libs}") - set_target_properties(LLVMDebugInfoPDB PROPERTIES INTERFACE_LINK_LIBRARIES "${_clangsharp_pdb_libs}") + if(CMAKE_GENERATOR_PLATFORM) + set(_clangsharp_arch "${CMAKE_GENERATOR_PLATFORM}") + else() + set(_clangsharp_arch "${CMAKE_CXX_COMPILER_ARCHITECTURE_ID}") + endif() + + if(_clangsharp_vs) + if(_clangsharp_arch MATCHES "[Aa][Rr][Mm]64") + set(_clangsharp_diaguids "${_clangsharp_vs}/DIA SDK/lib/arm64/diaguids.lib") + elseif(_clangsharp_arch MATCHES "[Ww]in32|[Xx]86") + set(_clangsharp_diaguids "${_clangsharp_vs}/DIA SDK/lib/diaguids.lib") + else() + set(_clangsharp_diaguids "${_clangsharp_vs}/DIA SDK/lib/amd64/diaguids.lib") + endif() + + get_target_property(_clangsharp_pdb_libs LLVMDebugInfoPDB INTERFACE_LINK_LIBRARIES) + if(EXISTS "${_clangsharp_diaguids}") + string(REGEX REPLACE "[^;]*[Dd][Ii][Aa] SDK[^;]*diaguids\\.lib" "${_clangsharp_diaguids}" _clangsharp_pdb_libs "${_clangsharp_pdb_libs}") + set_target_properties(LLVMDebugInfoPDB PROPERTIES INTERFACE_LINK_LIBRARIES "${_clangsharp_pdb_libs}") + endif() endif() endif()