Skip to content

Commit 6740402

Browse files
authored
Merge pull request #1714 from tmds/lib_resolve_linux
Linux: support broader range of distros/architectures by trying to load packed native libraries.
2 parents 8950f49 + cdab62d commit 6740402

File tree

1 file changed

+126
-11
lines changed

1 file changed

+126
-11
lines changed

LibGit2Sharp/Core/NativeMethods.cs

+126-11
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,146 @@ 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 static string GetGlobalSettingsNativeLibraryPath()
65+
{
66+
string nativeLibraryDir = GlobalSettings.GetAndLockNativeLibraryPath();
67+
if (nativeLibraryDir == null)
68+
{
69+
return null;
70+
}
71+
return Path.Combine(nativeLibraryDir, libgit2 + Platform.GetNativeLibraryExtension());
72+
}
73+
74+
private delegate bool TryLoadLibraryByNameDelegate(string libraryName, Assembly assembly, DllImportSearchPath? searchPath, out IntPtr handle);
75+
private delegate bool TryLoadLibraryByPathDelegate(string libraryPath, out IntPtr handle);
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+
#if NETFRAMEWORK
150+
#else
151+
// We cary a number of .so files for Linux which are linked against various
152+
// libc/OpenSSL libraries. Try them out.
153+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
154+
{
155+
// The libraries are located at 'runtimes/<rid>/native/lib{libraryName}.so'
156+
// The <rid> ends with the processor architecture. e.g. fedora-x64.
157+
string assemblyDirectory = Path.GetDirectoryName(typeof(NativeMethods).Assembly.Location);
158+
string processorArchitecture = RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant();
159+
foreach (var runtimeFolder in Directory.GetDirectories(Path.Combine(assemblyDirectory, "runtimes"), $"*-{processorArchitecture}"))
160+
{
161+
string libPath = Path.Combine(runtimeFolder, "native", $"lib{libraryName}.so");
162+
if (TryLoadLibrary(libPath, out handle))
163+
{
164+
return handle;
165+
}
166+
}
167+
}
168+
#endif
169+
}
170+
return handle;
171+
}
172+
58173
public const int RTLD_NOW = 0x002;
59174

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

0 commit comments

Comments
 (0)