Fix native library resolution for dotnet tools on Linux#700
Open
Happypig375 wants to merge 5 commits into
Open
Fix native library resolution for dotnet tools on Linux#700Happypig375 wants to merge 5 commits into
Happypig375 wants to merge 5 commits into
Conversation
The TryResolveClang method on Linux tried versioned SONAME names (libclang.so.20, libclang-20) that were hardcoded for LLVM 20. ClangSharp 21.1.8.3 is built for LLVM 21, so these names never match. The hardcoded version number gets stale with every LLVM major release. Remove the version-specific names entirely and try only version-agnostic names: - libclang.so (finds co-located binary in RID packages or system symlink) - libclang.so.1 (Debian/Ubuntu runtime SONAME from libclang1 package) This fixes ClangSharpPInvokeGenerator when running as a dotnet tool from a RID-specific package (e.g. ClangSharpPInvokeGenerator.linux-x64), where libclang.so is placed alongside the executable. On Linux, dlopen does not search the executable's directory by default, but the SafeDirectories DllImportSearchPath adds it, allowing NativeLibrary.TryLoad to find the co-located libclang.so. Relates to dotnet#586.
9dbe7ae to
7f99dd2
Compare
The TryResolveClang method was a Linux-specific fallback that tried versioned SONAMEs (libclang.so.20, libclang-20, libclang.so.1) before the default resolver could run. This was problematic: 1. The version number (20) was hardcoded and didn't match ClangSharp 21.x (built for LLVM 21). This caused version mismatch errors when a system libclang-20 was found instead of v21. 2. The method was unnecessary: when it returned IntPtr.Zero, the default runtime resolver with SafeDirectories would find libclang.so anyway, the same way it finds libclang.dll on Windows (which has no special handling). 3. The versioned SONAME approach is inherently fragile - it breaks with every LLVM major version bump and different distros use different naming conventions. Removing the method entirely lets the default resolver handle all platforms uniformly. The DllImportResolver still supports user-provided resolvers via the ResolveLibrary event for advanced scenarios. Relates to dotnet#586.
The default resolver with SafeDirectories should search the assembly's directory, but this doesn't work reliably for dotnet tools (especially AOT-compiled executables run from the NuGet cache via 'dotnet tool run'). This caused both libclang and libClangSharp to not be found on Linux, even though they are co-located with the executable in the RID package. Fix: After the user-provided resolver event, explicitly try loading from the assembly's own directory using Path.GetDirectoryName(assembly.Location). This handles all native dependencies (libclang, libClangSharp) uniformly on all platforms, without needing versioned SONAME fallbacks or LD_LIBRARY_PATH workarounds.
The previous approach used Path.Combine(AppContext.BaseDirectory, libraryName) which produced a path like '.../tools/any/linux-x64/libclang' without the platform-specific extension (.so/.dylib/.dll). NativeLibrary.TryLoad with an absolute path does NOT apply platform-specific name mangling, so dlopen would fail to find the file. Added platform suffix based on RuntimeInformation: Windows → .dll macOS → .dylib Linux → .so AppContext.BaseDirectory IS the correct directory for co-located native libraries in dotnet tool NuGet packages (tools/any/<rid>/), confirmed by inspecting the actual package layout on disk.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
ClangSharpPInvokeGenerator fails to load
libclangandlibClangSharpon Linux when run as a dotnet tool, even though the RID-specific packages (e.g.ClangSharpPInvokeGenerator.linux-x64) bundle these libraries alongside the executable.Root causes
1. Broken
TryResolveClangmethod: The customDllImportResolvertried hardcoded versioned SONAMEs (libclang.so.20,libclang-20) that don't match the LLVM version ClangSharp is built against (21). This could find a wrong-version system library or fail entirely.2. Default resolver can't find co-located libraries: The
SafeDirectoriessearch path should include the assembly's directory, but this doesn't work reliably for dotnet tools — especially AOT-compiled executables run from the NuGet cache viadotnet tool run. Bothlibclang.soandlibClangSharp.soare co-located with the executable but never found.Fix
1. Remove
TryResolveClangentirely. The versioned SONAME approach was inherently fragile (breaks with every LLVM major version bump, different distros use different naming conventions). The method was also unnecessary — when it returnedIntPtr.Zero, the default resolver should have handled it.2. Explicitly search the assembly's own directory. After the user-provided
ResolveLibraryevent, try loading fromPath.GetDirectoryName(assembly.Location). This handles all native dependencies (libclang,libClangSharp) uniformly on all platforms, without needing versioned SONAMEs orLD_LIBRARY_PATHworkarounds.This is a follow-up to #586 which was closed as resolved by the RID-specific tool package support (20.1.2.2+). While the RID package mechanism correctly makes native binaries available alongside the executable, the custom resolver prevented the default resolution path from finding them on Linux.