From 94b95dd7759255707ab8c3fe45f99d8a0312140f Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Mon, 9 Sep 2019 11:22:14 +0200 Subject: [PATCH 1/2] Linux: support broader range of distros/architectures by trying to load packed native libraries. LibGit2Sharp includes a number of native libraries that are built on different OSes. These native libraries have a dependency on OpenSSL and the system c-library. On x64, in case the Linux flavor is not known, a fallback is performed to the 'linux-x64' native library. This library is built with a dependency on OpenSSL 1.0. OpenSSL 1.0 is deprecated by OpenSSL 1.1, so on more recent version of Linux, the 'linux-x64' library fails to load. On arm64, native libraries are currently included for debian.* (OpenSSL 1.1) and ubuntu.18.04 (OpenSSL 1.0). Loading on other distros will fail. In both cases LibGit2Sharp is probably including a library that works, but the default resolution logic is not able to find it. By using the 'NativeLibrary' class (.NET Core 3.0) we can extend the resolution logic, and try to load the other native libraries that are packed with LibGit2Sharp. --- LibGit2Sharp/Core/NativeMethods.cs | 135 ++++++++++++++++++++++++++--- 1 file changed, 124 insertions(+), 11 deletions(-) diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index aa82516a4..9aae5980a 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; @@ -29,25 +30,30 @@ static NativeMethods() { if (Platform.IsRunningOnNetFramework() || Platform.IsRunningOnNetCore()) { - string nativeLibraryDir = GlobalSettings.GetAndLockNativeLibraryPath(); - if (nativeLibraryDir != null) + // Use .NET Core 3.0+ NativeLibrary when available. + if (!TryUseNativeLibrary()) { - string nativeLibraryPath = Path.Combine(nativeLibraryDir, libgit2 + Platform.GetNativeLibraryExtension()); + // NativeLibrary is not available, fall back. + // Use GlobalSettings.NativeLibraryPath when set. // Try to load the .dll from the path explicitly. // If this call succeeds further DllImports will find the library loaded and not attempt to load it again. // If it fails the next DllImport will load the library from safe directories. + string nativeLibraryPath = GetGlobalSettingsNativeLibraryPath(); + if (nativeLibraryPath != null) + { #if NETFRAMEWORK - if (Platform.OperatingSystem == OperatingSystemType.Windows) + if (Platform.OperatingSystem == OperatingSystemType.Windows) #else - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) #endif - { - LoadWindowsLibrary(nativeLibraryPath); - } - else - { - LoadUnixLibrary(nativeLibraryPath, RTLD_NOW); + { + LoadWindowsLibrary(nativeLibraryPath); + } + else + { + LoadUnixLibrary(nativeLibraryPath, RTLD_NOW); + } } } } @@ -55,6 +61,113 @@ static NativeMethods() InitializeNativeLibrary(); } + private delegate bool TryLoadLibraryByNameDelegate(string libraryName, Assembly assembly, DllImportSearchPath? searchPath, out IntPtr handle); + private delegate bool TryLoadLibraryByPathDelegate(string libraryPath, out IntPtr handle); + + private static string GetGlobalSettingsNativeLibraryPath() + { + string nativeLibraryDir = GlobalSettings.GetAndLockNativeLibraryPath(); + if (nativeLibraryDir == null) + { + return null; + } + return Path.Combine(nativeLibraryDir, libgit2 + Platform.GetNativeLibraryExtension()); + } + + static TryLoadLibraryByNameDelegate _tryLoadLibraryByName; + static TryLoadLibraryByPathDelegate _tryLoadLibraryByPath; + + static bool TryLoadLibrary(string libraryName, Assembly assembly, DllImportSearchPath? searchPath, out IntPtr handle) + { + if (_tryLoadLibraryByName == null) + { + throw new NotSupportedException(); + } + return _tryLoadLibraryByName(libraryName, assembly, searchPath, out handle); + } + + static bool TryLoadLibrary(string libraryPath, out IntPtr handle) + { + if (_tryLoadLibraryByPath == null) + { + throw new NotSupportedException(); + } + return _tryLoadLibraryByPath(libraryPath, out handle); + } + + private static bool TryUseNativeLibrary() + { + // NativeLibrary is available in .NET Core 3.0+. + // We use reflection to use NativeLibrary so this library can target 'netstandard2.0'. + + Type dllImportResolverType = Type.GetType("System.Runtime.InteropServices.DllImportResolver, System.Runtime.InteropServices", throwOnError: false); + Type nativeLibraryType = Type.GetType("System.Runtime.InteropServices.NativeLibrary, System.Runtime.InteropServices", throwOnError: false); + var tryLoadLibraryByName = (TryLoadLibraryByNameDelegate)nativeLibraryType?.GetMethod("TryLoad", + new Type[] { typeof(string), typeof(Assembly), typeof(DllImportSearchPath?), typeof(IntPtr).MakeByRefType() })?.CreateDelegate(typeof(TryLoadLibraryByNameDelegate)); + var tryLoadLibraryByPath = (TryLoadLibraryByPathDelegate)nativeLibraryType?.GetMethod("TryLoad", + new Type[] { typeof(string), typeof(IntPtr).MakeByRefType() })?.CreateDelegate(typeof(TryLoadLibraryByPathDelegate)); + MethodInfo setDllImportResolver = nativeLibraryType?.GetMethod("SetDllImportResolver", new Type[] { typeof(Assembly), dllImportResolverType}); + + if (dllImportResolverType == null || + nativeLibraryType == null || + tryLoadLibraryByName == null || + tryLoadLibraryByPath == null || + setDllImportResolver == null) + { + return false; + } + + _tryLoadLibraryByPath = tryLoadLibraryByPath; + _tryLoadLibraryByName = tryLoadLibraryByName; + + // NativeMethods.SetDllImportResolver(typeof(NativeMethods).Assembly, ResolveDll); + object resolveDelegate = typeof(NativeMethods).GetMethod(nameof(ResolveDll), BindingFlags.NonPublic | BindingFlags.Static).CreateDelegate(dllImportResolverType); + setDllImportResolver.Invoke(null, new object[] { typeof(NativeMethods).Assembly, resolveDelegate }); + + return true; + } + + private static IntPtr ResolveDll(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) + { + IntPtr handle = IntPtr.Zero; + if (libraryName == libgit2) + { + // Use GlobalSettings.NativeLibraryPath when set. + string nativeLibraryPath = GetGlobalSettingsNativeLibraryPath(); + if (nativeLibraryPath != null && + TryLoadLibrary(nativeLibraryPath, out handle)) + { + return handle; + } + + // Use Default DllImport resolution. + if (TryLoadLibrary(libraryName, assembly, searchPath, out handle)) + { + return handle; + } + + // We cary a number of .so files for Linux which are linked against various + // libc/OpenSSL libraries. Try them out. + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + // The libraries are located at 'runtimes//native/lib{libraryName}.so' + // The ends with the processor architecture. e.g. fedora-x64. + + string assemblyDirectory = Path.GetDirectoryName(typeof(NativeMethods).Assembly.Location); + string processorArchitecture = RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant(); + foreach (var runtimeFolder in Directory.GetDirectories(Path.Combine(assemblyDirectory, "runtimes"), $"*-{processorArchitecture}")) + { + string libPath = Path.Combine(runtimeFolder, "native", $"lib{libraryName}.so"); + if (TryLoadLibrary(libPath, out handle)) + { + return handle; + } + } + } + } + return handle; + } + public const int RTLD_NOW = 0x002; [DllImport("libdl", EntryPoint = "dlopen")] From cdab62d102d886bc71f36da1f53d6750300f73c5 Mon Sep 17 00:00:00 2001 From: Tom Deseyn Date: Mon, 9 Sep 2019 16:51:29 +0200 Subject: [PATCH 2/2] Fix netfx build --- LibGit2Sharp/Core/NativeMethods.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index 9aae5980a..d237832be 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -61,9 +61,6 @@ static NativeMethods() InitializeNativeLibrary(); } - private delegate bool TryLoadLibraryByNameDelegate(string libraryName, Assembly assembly, DllImportSearchPath? searchPath, out IntPtr handle); - private delegate bool TryLoadLibraryByPathDelegate(string libraryPath, out IntPtr handle); - private static string GetGlobalSettingsNativeLibraryPath() { string nativeLibraryDir = GlobalSettings.GetAndLockNativeLibraryPath(); @@ -74,6 +71,9 @@ private static string GetGlobalSettingsNativeLibraryPath() return Path.Combine(nativeLibraryDir, libgit2 + Platform.GetNativeLibraryExtension()); } + private delegate bool TryLoadLibraryByNameDelegate(string libraryName, Assembly assembly, DllImportSearchPath? searchPath, out IntPtr handle); + private delegate bool TryLoadLibraryByPathDelegate(string libraryPath, out IntPtr handle); + static TryLoadLibraryByNameDelegate _tryLoadLibraryByName; static TryLoadLibraryByPathDelegate _tryLoadLibraryByPath; @@ -146,13 +146,14 @@ private static IntPtr ResolveDll(string libraryName, Assembly assembly, DllImpor return handle; } +#if NETFRAMEWORK +#else // We cary a number of .so files for Linux which are linked against various // libc/OpenSSL libraries. Try them out. if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { // The libraries are located at 'runtimes//native/lib{libraryName}.so' // The ends with the processor architecture. e.g. fedora-x64. - string assemblyDirectory = Path.GetDirectoryName(typeof(NativeMethods).Assembly.Location); string processorArchitecture = RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant(); foreach (var runtimeFolder in Directory.GetDirectories(Path.Combine(assemblyDirectory, "runtimes"), $"*-{processorArchitecture}")) @@ -164,6 +165,7 @@ private static IntPtr ResolveDll(string libraryName, Assembly assembly, DllImpor } } } +#endif } return handle; }