diff --git a/README.md b/README.md index 698d064c..9b02d739 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/packages/libClangSharp/libClangSharp.runtime.linux-arm64/libClangSharp.runtime.linux-arm64.nuspec b/packages/libClangSharp/libClangSharp.runtime.linux-arm64/libClangSharp.runtime.linux-arm64.nuspec index cb33cb03..60edc18c 100644 --- a/packages/libClangSharp/libClangSharp.runtime.linux-arm64/libClangSharp.runtime.linux-arm64.nuspec +++ b/packages/libClangSharp/libClangSharp.runtime.linux-arm64/libClangSharp.runtime.linux-arm64.nuspec @@ -2,7 +2,7 @@ libClangSharp.runtime.linux-arm64 - 22.1.8.2 + 22.1.8.3 .NET Foundation and Contributors .NET Foundation and Contributors true diff --git a/packages/libClangSharp/libClangSharp.runtime.linux-x64/libClangSharp.runtime.linux-x64.nuspec b/packages/libClangSharp/libClangSharp.runtime.linux-x64/libClangSharp.runtime.linux-x64.nuspec index 4ef7d465..a053ccc6 100644 --- a/packages/libClangSharp/libClangSharp.runtime.linux-x64/libClangSharp.runtime.linux-x64.nuspec +++ b/packages/libClangSharp/libClangSharp.runtime.linux-x64/libClangSharp.runtime.linux-x64.nuspec @@ -2,7 +2,7 @@ libClangSharp.runtime.linux-x64 - 22.1.8.2 + 22.1.8.3 .NET Foundation and Contributors .NET Foundation and Contributors true diff --git a/packages/libClangSharp/libClangSharp.runtime.osx-arm64/libClangSharp.runtime.osx-arm64.nuspec b/packages/libClangSharp/libClangSharp.runtime.osx-arm64/libClangSharp.runtime.osx-arm64.nuspec index b1e813f4..f30fcf59 100644 --- a/packages/libClangSharp/libClangSharp.runtime.osx-arm64/libClangSharp.runtime.osx-arm64.nuspec +++ b/packages/libClangSharp/libClangSharp.runtime.osx-arm64/libClangSharp.runtime.osx-arm64.nuspec @@ -2,7 +2,7 @@ libClangSharp.runtime.osx-arm64 - 22.1.8.2 + 22.1.8.3 .NET Foundation and Contributors .NET Foundation and Contributors true diff --git a/packages/libClangSharp/libClangSharp.runtime.win-arm64/libClangSharp.runtime.win-arm64.nuspec b/packages/libClangSharp/libClangSharp.runtime.win-arm64/libClangSharp.runtime.win-arm64.nuspec index fabe3499..959c33a9 100644 --- a/packages/libClangSharp/libClangSharp.runtime.win-arm64/libClangSharp.runtime.win-arm64.nuspec +++ b/packages/libClangSharp/libClangSharp.runtime.win-arm64/libClangSharp.runtime.win-arm64.nuspec @@ -2,7 +2,7 @@ libClangSharp.runtime.win-arm64 - 22.1.8.2 + 22.1.8.3 .NET Foundation and Contributors .NET Foundation and Contributors true diff --git a/packages/libClangSharp/libClangSharp.runtime.win-x64/libClangSharp.runtime.win-x64.nuspec b/packages/libClangSharp/libClangSharp.runtime.win-x64/libClangSharp.runtime.win-x64.nuspec index ba12773b..14aeeeea 100644 --- a/packages/libClangSharp/libClangSharp.runtime.win-x64/libClangSharp.runtime.win-x64.nuspec +++ b/packages/libClangSharp/libClangSharp.runtime.win-x64/libClangSharp.runtime.win-x64.nuspec @@ -2,7 +2,7 @@ libClangSharp.runtime.win-x64 - 22.1.8.2 + 22.1.8.3 .NET Foundation and Contributors .NET Foundation and Contributors true diff --git a/packages/libClangSharp/libClangSharp/libClangSharp.nuspec b/packages/libClangSharp/libClangSharp/libClangSharp.nuspec index df8de877..e65a11f5 100644 --- a/packages/libClangSharp/libClangSharp/libClangSharp.nuspec +++ b/packages/libClangSharp/libClangSharp/libClangSharp.nuspec @@ -2,7 +2,7 @@ libClangSharp - 22.1.8.2 + 22.1.8.3 .NET Foundation and Contributors .NET Foundation and Contributors true diff --git a/packages/libClangSharp/libClangSharp/runtime.json b/packages/libClangSharp/libClangSharp/runtime.json index c74df919..7cc5d4c5 100644 --- a/packages/libClangSharp/libClangSharp/runtime.json +++ b/packages/libClangSharp/libClangSharp/runtime.json @@ -2,27 +2,27 @@ "runtimes": { "linux-arm64": { "libClangSharp": { - "libClangSharp.runtime.linux-arm64": "22.1.8.2" + "libClangSharp.runtime.linux-arm64": "22.1.8.3" } }, "linux-x64": { "libClangSharp": { - "libClangSharp.runtime.linux-x64": "22.1.8.2" + "libClangSharp.runtime.linux-x64": "22.1.8.3" } }, "osx-arm64": { "libClangSharp": { - "libClangSharp.runtime.osx-arm64": "22.1.8.2" + "libClangSharp.runtime.osx-arm64": "22.1.8.3" } }, "win-arm64": { "libClangSharp": { - "libClangSharp.runtime.win-arm64": "22.1.8.2" + "libClangSharp.runtime.win-arm64": "22.1.8.3" } }, "win-x64": { "libClangSharp": { - "libClangSharp.runtime.win-x64": "22.1.8.2" + "libClangSharp.runtime.win-x64": "22.1.8.3" } } } 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() diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/StructDeclarationTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/StructDeclarationTest.cs index 3612d285..8da44024 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/StructDeclarationTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/Baseline/StructDeclarationTest.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Runtime.InteropServices; using System.Threading.Tasks; using NUnit.Framework; @@ -169,6 +170,11 @@ typedef struct [Test] public Task DependentTemplateBaseTest() { + if (OperatingSystem.IsWindows() && (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)) + { + Assert.Ignore("The win-arm64 libClangSharp prebuilt ships broken clangsharp shims. Remove this guard once the native lib is rebuilt. See https://github.com/dotnet/ClangSharp/issues/806."); + } + // A dependent template-specialization base (`Base` within a class template) previously // crashed `GetRecordDecl` with an `InvalidCastException` when both template bindings and // empty-record exclusion were enabled, as the base resolves to a `ClassTemplateDecl` diff --git a/tests/ClangSharp.UnitTests/TypeTest.cs b/tests/ClangSharp.UnitTests/TypeTest.cs index 42973186..ef03033c 100644 --- a/tests/ClangSharp.UnitTests/TypeTest.cs +++ b/tests/ClangSharp.UnitTests/TypeTest.cs @@ -2,6 +2,7 @@ using System; using System.Linq; +using System.Runtime.InteropServices; using ClangSharp.Interop; using NUnit.Framework; @@ -170,6 +171,11 @@ struct S { int field; void method(); }; [Test] public void TemplateNameTest() { + if (OperatingSystem.IsWindows() && (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)) + { + Assert.Ignore("The win-arm64 libClangSharp prebuilt ships broken clangsharp shims. Remove this guard once the native lib is rebuilt. See https://github.com/dotnet/ClangSharp/issues/806."); + } + var inputContents = """ template struct Tmpl { T value; }; using SpecT = Tmpl;