Skip to content

Commit 94b95dd

Browse files
committed
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.
1 parent 8950f49 commit 94b95dd

File tree

1 file changed

+124
-11
lines changed

1 file changed

+124
-11
lines changed

LibGit2Sharp/Core/NativeMethods.cs

Lines changed: 124 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.IO;
3+
using System.Reflection;
34
using System.Runtime.CompilerServices;
45
using System.Runtime.ConstrainedExecution;
56
using System.Runtime.InteropServices;
@@ -29,32 +30,144 @@ static NativeMethods()
2930
{
3031
if (Platform.IsRunningOnNetFramework() || Platform.IsRunningOnNetCore())
3132
{
32-
string nativeLibraryDir = GlobalSettings.GetAndLockNativeLibraryPath();
33-
if (nativeLibraryDir != null)
33+
// Use .NET Core 3.0+ NativeLibrary when available.
34+
if (!TryUseNativeLibrary())
3435
{
35-
string nativeLibraryPath = Path.Combine(nativeLibraryDir, libgit2 + Platform.GetNativeLibraryExtension());
36+
// NativeLibrary is not available, fall back.
3637

38+
// Use GlobalSettings.NativeLibraryPath when set.
3739
// Try to load the .dll from the path explicitly.
3840
// If this call succeeds further DllImports will find the library loaded and not attempt to load it again.
3941
// If it fails the next DllImport will load the library from safe directories.
42+
string nativeLibraryPath = GetGlobalSettingsNativeLibraryPath();
43+
if (nativeLibraryPath != null)
44+
{
4045
#if NETFRAMEWORK
41-
if (Platform.OperatingSystem == OperatingSystemType.Windows)
46+
if (Platform.OperatingSystem == OperatingSystemType.Windows)
4247
#else
43-
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
48+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
4449
#endif
45-
{
46-
LoadWindowsLibrary(nativeLibraryPath);
47-
}
48-
else
49-
{
50-
LoadUnixLibrary(nativeLibraryPath, RTLD_NOW);
50+
{
51+
LoadWindowsLibrary(nativeLibraryPath);
52+
}
53+
else
54+
{
55+
LoadUnixLibrary(nativeLibraryPath, RTLD_NOW);
56+
}
5157
}
5258
}
5359
}
5460

5561
InitializeNativeLibrary();
5662
}
5763

64+
private delegate bool TryLoadLibraryByNameDelegate(string libraryName, Assembly assembly, DllImportSearchPath? searchPath, out IntPtr handle);
65+
private delegate bool TryLoadLibraryByPathDelegate(string libraryPath, out IntPtr handle);
66+
67+
private static string GetGlobalSettingsNativeLibraryPath()
68+
{
69+
string nativeLibraryDir = GlobalSettings.GetAndLockNativeLibraryPath();
70+
if (nativeLibraryDir == null)
71+
{
72+
return null;
73+
}
74+
return Path.Combine(nativeLibraryDir, libgit2 + Platform.GetNativeLibraryExtension());
75+
}
76+
77+
static TryLoadLibraryByNameDelegate _tryLoadLibraryByName;
78+
static TryLoadLibraryByPathDelegate _tryLoadLibraryByPath;
79+
80+
static bool TryLoadLibrary(string libraryName, Assembly assembly, DllImportSearchPath? searchPath, out IntPtr handle)
81+
{
82+
if (_tryLoadLibraryByName == null)
83+
{
84+
throw new NotSupportedException();
85+
}
86+
return _tryLoadLibraryByName(libraryName, assembly, searchPath, out handle);
87+
}
88+
89+
static bool TryLoadLibrary(string libraryPath, out IntPtr handle)
90+
{
91+
if (_tryLoadLibraryByPath == null)
92+
{
93+
throw new NotSupportedException();
94+
}
95+
return _tryLoadLibraryByPath(libraryPath, out handle);
96+
}
97+
98+
private static bool TryUseNativeLibrary()
99+
{
100+
// NativeLibrary is available in .NET Core 3.0+.
101+
// We use reflection to use NativeLibrary so this library can target 'netstandard2.0'.
102+
103+
Type dllImportResolverType = Type.GetType("System.Runtime.InteropServices.DllImportResolver, System.Runtime.InteropServices", throwOnError: false);
104+
Type nativeLibraryType = Type.GetType("System.Runtime.InteropServices.NativeLibrary, System.Runtime.InteropServices", throwOnError: false);
105+
var tryLoadLibraryByName = (TryLoadLibraryByNameDelegate)nativeLibraryType?.GetMethod("TryLoad",
106+
new Type[] { typeof(string), typeof(Assembly), typeof(DllImportSearchPath?), typeof(IntPtr).MakeByRefType() })?.CreateDelegate(typeof(TryLoadLibraryByNameDelegate));
107+
var tryLoadLibraryByPath = (TryLoadLibraryByPathDelegate)nativeLibraryType?.GetMethod("TryLoad",
108+
new Type[] { typeof(string), typeof(IntPtr).MakeByRefType() })?.CreateDelegate(typeof(TryLoadLibraryByPathDelegate));
109+
MethodInfo setDllImportResolver = nativeLibraryType?.GetMethod("SetDllImportResolver", new Type[] { typeof(Assembly), dllImportResolverType});
110+
111+
if (dllImportResolverType == null ||
112+
nativeLibraryType == null ||
113+
tryLoadLibraryByName == null ||
114+
tryLoadLibraryByPath == null ||
115+
setDllImportResolver == null)
116+
{
117+
return false;
118+
}
119+
120+
_tryLoadLibraryByPath = tryLoadLibraryByPath;
121+
_tryLoadLibraryByName = tryLoadLibraryByName;
122+
123+
// NativeMethods.SetDllImportResolver(typeof(NativeMethods).Assembly, ResolveDll);
124+
object resolveDelegate = typeof(NativeMethods).GetMethod(nameof(ResolveDll), BindingFlags.NonPublic | BindingFlags.Static).CreateDelegate(dllImportResolverType);
125+
setDllImportResolver.Invoke(null, new object[] { typeof(NativeMethods).Assembly, resolveDelegate });
126+
127+
return true;
128+
}
129+
130+
private static IntPtr ResolveDll(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
131+
{
132+
IntPtr handle = IntPtr.Zero;
133+
if (libraryName == libgit2)
134+
{
135+
// Use GlobalSettings.NativeLibraryPath when set.
136+
string nativeLibraryPath = GetGlobalSettingsNativeLibraryPath();
137+
if (nativeLibraryPath != null &&
138+
TryLoadLibrary(nativeLibraryPath, out handle))
139+
{
140+
return handle;
141+
}
142+
143+
// Use Default DllImport resolution.
144+
if (TryLoadLibrary(libraryName, assembly, searchPath, out handle))
145+
{
146+
return handle;
147+
}
148+
149+
// We cary a number of .so files for Linux which are linked against various
150+
// libc/OpenSSL libraries. Try them out.
151+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
152+
{
153+
// The libraries are located at 'runtimes/<rid>/native/lib{libraryName}.so'
154+
// The <rid> ends with the processor architecture. e.g. fedora-x64.
155+
156+
string assemblyDirectory = Path.GetDirectoryName(typeof(NativeMethods).Assembly.Location);
157+
string processorArchitecture = RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant();
158+
foreach (var runtimeFolder in Directory.GetDirectories(Path.Combine(assemblyDirectory, "runtimes"), $"*-{processorArchitecture}"))
159+
{
160+
string libPath = Path.Combine(runtimeFolder, "native", $"lib{libraryName}.so");
161+
if (TryLoadLibrary(libPath, out handle))
162+
{
163+
return handle;
164+
}
165+
}
166+
}
167+
}
168+
return handle;
169+
}
170+
58171
public const int RTLD_NOW = 0x002;
59172

60173
[DllImport("libdl", EntryPoint = "dlopen")]

0 commit comments

Comments
 (0)