Skip to content

Commit 24016bf

Browse files
committed
Search for native library in runtimes dir and allow setting NativeLibraryPath on .NET Core.
1 parent 04d19fa commit 24016bf

File tree

3 files changed

+115
-34
lines changed

3 files changed

+115
-34
lines changed

LibGit2Sharp/Core/NativeMethods.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,31 @@ internal static class NativeMethods
2727

2828
static NativeMethods()
2929
{
30-
if (Platform.IsRunningOnNetFramework())
30+
if (Platform.IsRunningOnNetFramework() || Platform.IsRunningOnNetCore())
3131
{
32-
string dllPath = Path.Combine(GlobalSettings.GetAndLockNativeLibraryPath(), libgit2 + ".dll");
32+
string dllPath = Path.Combine(GlobalSettings.GetAndLockNativeLibraryPath(), libgit2 + Platform.GetNativeLibraryExtension());
3333

3434
// Try to load the .dll from the path explicitly.
3535
// If this call succeeds further DllImports will find the library loaded and not attempt to load it again.
3636
// If it fails the next DllImport will load the library from safe directories.
37-
LoadWindowsLibrary(dllPath);
37+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
38+
{
39+
LoadWindowsLibrary(dllPath);
40+
}
41+
else
42+
{
43+
LoadUnixLibrary(dllPath, RTLD_NOW);
44+
}
3845
}
3946

4047
InitializeNativeLibrary();
4148
}
4249

50+
public const int RTLD_NOW = 0x002;
51+
52+
[DllImport("libdl", EntryPoint = "dlopen")]
53+
private static extern IntPtr LoadUnixLibrary(string path, int flags);
54+
4355
[DllImport("kernel32", EntryPoint = "LoadLibrary")]
4456
private static extern IntPtr LoadWindowsLibrary(string path);
4557

LibGit2Sharp/Core/Platform.cs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,48 @@ public static OperatingSystemType OperatingSystem
3333
return OperatingSystemType.MacOSX;
3434
}
3535

36-
throw new InvalidOperationException();
36+
throw new PlatformNotSupportedException();
3737
}
3838
}
3939

40+
/// <summary>
41+
/// Determines the RID to use when loading libgit2 native library.
42+
/// This method only supports RIDs that are currently used by LibGit2Sharp.NativeBinaries.
43+
/// </summary>
44+
public static string GetNativeLibraryRuntimeId()
45+
{
46+
switch (OperatingSystem)
47+
{
48+
case OperatingSystemType.MacOSX:
49+
return "osx";
50+
51+
case OperatingSystemType.Unix:
52+
return "linux-" + ProcessorArchitecture;
53+
54+
case OperatingSystemType.Windows:
55+
return "win7-" + ProcessorArchitecture;
56+
}
57+
58+
throw new PlatformNotSupportedException();
59+
}
60+
61+
public static string GetNativeLibraryExtension()
62+
{
63+
switch (OperatingSystem)
64+
{
65+
case OperatingSystemType.MacOSX:
66+
return ".dylib";
67+
68+
case OperatingSystemType.Unix:
69+
return ".so";
70+
71+
case OperatingSystemType.Windows:
72+
return ".dll";
73+
}
74+
75+
throw new PlatformNotSupportedException();
76+
}
77+
4078
/// <summary>
4179
/// Returns true if the runtime is Mono.
4280
/// </summary>
@@ -48,5 +86,11 @@ public static bool IsRunningOnMono()
4886
/// </summary>
4987
public static bool IsRunningOnNetFramework()
5088
=> typeof(object).Assembly.GetName().Name == "mscorlib" && !IsRunningOnMono();
89+
90+
/// <summary>
91+
/// Returns true if the runtime is .NET Core.
92+
/// </summary>
93+
public static bool IsRunningOnNetCore()
94+
=> typeof(object).Assembly.GetName().Name != "mscorlib";
5195
}
5296
}

LibGit2Sharp/GlobalSettings.cs

Lines changed: 55 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,36 +23,57 @@ public static class GlobalSettings
2323

2424
static GlobalSettings()
2525
{
26-
nativeLibraryPathAllowed = Platform.IsRunningOnNetFramework();
26+
bool netFX = Platform.IsRunningOnNetFramework();
27+
bool netCore = Platform.IsRunningOnNetCore();
28+
29+
nativeLibraryPathAllowed = netFX || netCore;
2730

2831
if (nativeLibraryPathAllowed)
2932
{
30-
/* Assembly.CodeBase is not actually a correctly formatted
31-
* URI. It's merely prefixed with `file:///` and has its
32-
* backslashes flipped. This is superior to EscapedCodeBase,
33-
* which does not correctly escape things, and ambiguates a
34-
* space (%20) with a literal `%20` in the path. Sigh.
35-
*/
36-
var managedPath = Assembly.GetExecutingAssembly().CodeBase;
37-
if (managedPath == null)
38-
{
39-
managedPath = Assembly.GetExecutingAssembly().Location;
40-
}
41-
else if (managedPath.StartsWith("file:///"))
33+
string assemblyDirectory = GetExecutingAssemblyDirectory();
34+
35+
if (netFX)
4236
{
43-
managedPath = managedPath.Substring(8).Replace('/', '\\');
37+
// For .NET Framework apps the dependencies are deployed to lib/win32/{architecture} directory
38+
nativeLibraryPath = Path.Combine(assemblyDirectory, "lib", "win32");
4439
}
45-
else if (managedPath.StartsWith("file://"))
40+
else
4641
{
47-
managedPath = @"\\" + managedPath.Substring(7).Replace('/', '\\');
42+
// .NET Core apps that depend on native libraries load them directly from paths specified
43+
// in .deps.json file of that app and the native library loader just works.
44+
// However, .NET Core doesn't support .deps.json for plugins yet (such as msbuild tasks).
45+
// To address that shortcoming we assume that the plugin deploys the native binaries to runtimes\{rid}\native
46+
// directories and search there.
47+
nativeLibraryPath = Path.Combine(assemblyDirectory, "runtimes", Platform.GetNativeLibraryRuntimeId(), "native");
4848
}
49+
}
4950

50-
managedPath = Path.GetDirectoryName(managedPath);
51+
registeredFilters = new Dictionary<Filter, FilterRegistration>();
52+
}
5153

52-
nativeLibraryPath = Path.Combine(managedPath, "lib", "win32");
54+
private static string GetExecutingAssemblyDirectory()
55+
{
56+
// Assembly.CodeBase is not actually a correctly formatted
57+
// URI. It's merely prefixed with `file:///` and has its
58+
// backslashes flipped. This is superior to EscapedCodeBase,
59+
// which does not correctly escape things, and ambiguates a
60+
// space (%20) with a literal `%20` in the path. Sigh.
61+
var managedPath = Assembly.GetExecutingAssembly().CodeBase;
62+
if (managedPath == null)
63+
{
64+
managedPath = Assembly.GetExecutingAssembly().Location;
65+
}
66+
else if (managedPath.StartsWith("file:///"))
67+
{
68+
managedPath = managedPath.Substring(8).Replace('/', '\\');
69+
}
70+
else if (managedPath.StartsWith("file://"))
71+
{
72+
managedPath = @"\\" + managedPath.Substring(7).Replace('/', '\\');
5373
}
5474

55-
registeredFilters = new Dictionary<Filter, FilterRegistration>();
75+
managedPath = Path.GetDirectoryName(managedPath);
76+
return managedPath;
5677
}
5778

5879
/// <summary>
@@ -152,17 +173,18 @@ public static LogConfiguration LogConfiguration
152173
}
153174

154175
/// <summary>
155-
/// Sets a path for loading native binaries on .NET Framework.
156-
/// When specified, native .dll will first be searched in a
157-
/// subdirectory of the given path corresponding to the
158-
/// architecture ("x86" or "x64") before falling
159-
/// back to searching the default load directories
160-
/// (<see cref="DllImportSearchPath.AssemblyDirectory"/>,
176+
/// Sets a path for loading native binaries on .NET Framework or .NET Core.
177+
/// When specified, native library will first be searched under the given path.
178+
/// On .NET Framework a subdirectory corresponding to the architecture ("x86" or "x64") is appended,
179+
/// otherwise the native library is expected to be found in the directory as specified.
180+
///
181+
/// If the library is not found it will be searched in standard search paths:
182+
/// <see cref="DllImportSearchPath.AssemblyDirectory"/>,
161183
/// <see cref="DllImportSearchPath.ApplicationDirectory"/> and
162-
/// <see cref="DllImportSearchPath.SafeDirectories"/>).
184+
/// <see cref="DllImportSearchPath.SafeDirectories"/>.
163185
/// <para>
164186
/// This must be set before any other calls to the library,
165-
/// and is not available on other platforms than .NET Framework.
187+
/// and is not available on other platforms than .NET Framework and .NET Core.
166188
/// </para>
167189
/// </summary>
168190
public static string NativeLibraryPath
@@ -171,7 +193,7 @@ public static string NativeLibraryPath
171193
{
172194
if (!nativeLibraryPathAllowed)
173195
{
174-
throw new LibGit2SharpException("Querying the native hint path is only supported on Windows platforms");
196+
throw new LibGit2SharpException("Querying the native hint path is only supported on .NET Framework and .NET Core platforms");
175197
}
176198

177199
return nativeLibraryPath;
@@ -181,7 +203,7 @@ public static string NativeLibraryPath
181203
{
182204
if (!nativeLibraryPathAllowed)
183205
{
184-
throw new LibGit2SharpException("Setting the native hint path is only supported on Windows platforms");
206+
throw new LibGit2SharpException("Setting the native hint path is only supported on .NET Framework and .NET Core platforms");
185207
}
186208

187209
if (nativeLibraryPathLocked)
@@ -203,7 +225,10 @@ public static string NativeLibraryPath
203225
internal static string GetAndLockNativeLibraryPath()
204226
{
205227
nativeLibraryPathLocked = true;
206-
return Path.Combine(nativeLibraryPath, Platform.ProcessorArchitecture);
228+
229+
return Platform.IsRunningOnNetFramework() ?
230+
Path.Combine(nativeLibraryPath, Platform.ProcessorArchitecture) :
231+
nativeLibraryPath;
207232
}
208233

209234
/// <summary>

0 commit comments

Comments
 (0)