Skip to content

[WIP] Resolve native library location from runtimes folder #1571

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 14 additions & 16 deletions LibGit2Sharp/Core/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,22 @@ internal static class NativeMethods

static NativeMethods()
{
if (Platform.IsRunningOnNetFramework() || Platform.IsRunningOnNetCore())
var nativeLibraryDir = GlobalSettings.GetAndLockNativeLibraryPath();

if (nativeLibraryDir != null)
{
string nativeLibraryDir = GlobalSettings.GetAndLockNativeLibraryPath();
if (nativeLibraryDir != null)
var nativeLibraryPath = Path.Combine(nativeLibraryDir, libgit2 + Platform.GetNativeLibraryExtension());

// Try to load the library 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.
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
LoadWindowsLibrary(nativeLibraryPath);
}
else
{
string nativeLibraryPath = Path.Combine(nativeLibraryDir, libgit2 + Platform.GetNativeLibraryExtension());

// 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.
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
LoadWindowsLibrary(nativeLibraryPath);
}
else
{
LoadUnixLibrary(nativeLibraryPath, RTLD_NOW);
}
LoadUnixLibrary(nativeLibraryPath, RTLD_NOW);
}
}

Expand Down
55 changes: 3 additions & 52 deletions LibGit2Sharp/GlobalSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ public static class GlobalSettings
{
private static readonly Lazy<Version> version = new Lazy<Version>(Version.Build);
private static readonly Dictionary<Filter, FilterRegistration> registeredFilters;
private static readonly bool nativeLibraryPathAllowed;

private static LogConfiguration logConfiguration = LogConfiguration.None;

Expand All @@ -24,49 +23,11 @@ public static class GlobalSettings

static GlobalSettings()
{
bool netFX = Platform.IsRunningOnNetFramework();
bool netCore = Platform.IsRunningOnNetCore();

nativeLibraryPathAllowed = netFX || netCore;

if (netFX)
{
// For .NET Framework apps the dependencies are deployed to lib/win32/{architecture} directory
nativeLibraryDefaultPath = Path.Combine(GetExecutingAssemblyDirectory(), "lib", "win32");
}
else
{
nativeLibraryDefaultPath = null;
}
nativeLibraryDefaultPath = NativeLibraryPathResolver.GetNativeLibraryDefaultPath();

registeredFilters = new Dictionary<Filter, FilterRegistration>();
}

private static string GetExecutingAssemblyDirectory()
{
// Assembly.CodeBase is not actually a correctly formatted
// URI. It's merely prefixed with `file:///` and has its
// backslashes flipped. This is superior to EscapedCodeBase,
// which does not correctly escape things, and ambiguates a
// space (%20) with a literal `%20` in the path. Sigh.
var managedPath = Assembly.GetExecutingAssembly().CodeBase;
if (managedPath == null)
{
managedPath = Assembly.GetExecutingAssembly().Location;
}
else if (managedPath.StartsWith("file:///"))
{
managedPath = managedPath.Substring(8).Replace('/', '\\');
}
else if (managedPath.StartsWith("file://"))
{
managedPath = @"\\" + managedPath.Substring(7).Replace('/', '\\');
}

managedPath = Path.GetDirectoryName(managedPath);
return managedPath;
}

/// <summary>
/// Returns information related to the current LibGit2Sharp
/// library.
Expand Down Expand Up @@ -186,21 +147,11 @@ public static string NativeLibraryPath
{
get
{
if (!nativeLibraryPathAllowed)
{
throw new LibGit2SharpException("Querying the native hint path is only supported on .NET Framework and .NET Core platforms");
}

return nativeLibraryPath ?? nativeLibraryDefaultPath;
}

set
{
if (!nativeLibraryPathAllowed)
{
throw new LibGit2SharpException("Setting the native hint path is only supported on .NET Framework and .NET Core platforms");
}

if (nativeLibraryPathLocked)
{
throw new LibGit2SharpException("You cannot set the native library path after it has been loaded");
Expand All @@ -220,8 +171,8 @@ public static string NativeLibraryPath
internal static string GetAndLockNativeLibraryPath()
{
nativeLibraryPathLocked = true;
string result = nativeLibraryPath ?? nativeLibraryDefaultPath;
return Platform.IsRunningOnNetFramework() ? Path.Combine(result, Platform.ProcessorArchitecture) : result;

return nativeLibraryPath ?? nativeLibraryDefaultPath;
}

/// <summary>
Expand Down
5 changes: 4 additions & 1 deletion LibGit2Sharp/LibGit2Sharp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,13 @@
<None Include="..\LICENSE.md" Pack="true" PackagePath="App_Readme\" />
<None Include="..\CHANGES.md" Pack="true" PackagePath="App_Readme\" />
<Compile Update="Core\Handles\Objects.cs" DependentUpon="Objects.tt" />
<None Remove="runtime.json" />
<EmbeddedResource Include="runtime.json" LogicalName="runtime.json" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="LibGit2Sharp.NativeBinaries" Version="[1.0.210]" PrivateAssets="none" />
<PackageReference Include="LibGit2Sharp.NativeBinaries" Version="[1.0.215-pre20180422060246]" PrivateAssets="none" />
<PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="2.0.4" />
<PackageReference Include="Nerdbank.GitVersioning" Version="2.1.23" PrivateAssets="all" />
<PackageReference Include="SourceLink.Create.GitHub" Version="2.8.0" PrivateAssets="all" />
<DotNetCliToolReference Include="dotnet-sourcelink-git" Version="2.8.0" />
Expand Down
156 changes: 156 additions & 0 deletions LibGit2Sharp/NativeLibraryPathResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using SimpleJson;

using static Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment;

namespace LibGit2Sharp
{
static partial class NativeLibraryPathResolver
{
public static string GetNativeLibraryDefaultPath()
{
var runtimeIdentifier = GetRuntimeIdentifier();
var graph = BuildRuntimeGraph();

var compatibleRuntimeIdentifiers = graph.GetCompatibleRuntimeIdentifiers(runtimeIdentifier);

return GetNativeLibraryPath(compatibleRuntimeIdentifiers);
}

private static Graph BuildRuntimeGraph()
{
var assembly = Assembly.GetExecutingAssembly();
var resourceName = "runtime.json";

var rids = new Dictionary<string, Runtime>();

using (var stream = assembly.GetManifestResourceStream(resourceName))
using (var reader = new StreamReader(stream))
{
var result = reader.ReadToEnd();
var json = (JsonObject)SimpleJson.SimpleJson.DeserializeObject(result);

var runtimes = (JsonObject)json["runtimes"];

foreach (var runtime in runtimes)
{
var imports = (JsonArray)((JsonObject)runtime.Value)["#import"];

var importedRuntimeIdentifiers = new List<string>();

foreach (var import in imports)
{
importedRuntimeIdentifiers.Add((string)import);
}

rids.Add(runtime.Key, new Runtime(runtime.Key, importedRuntimeIdentifiers));
}
}

return new Graph(rids);
}

private static string GetNativeLibraryPath(List<string> runtimeIdentifiers)
{
foreach (var runtimeIdentifier in runtimeIdentifiers)
{
if (nativeLibraries.Contains(runtimeIdentifier))
{
return Path.Combine(GetExecutingAssemblyDirectory(), "runtimes", runtimeIdentifier, "native");
}
}

return null;
}

private static string GetExecutingAssemblyDirectory()
{
// Assembly.CodeBase is not actually a correctly formatted
// URI. It's merely prefixed with `file:///` and has its
// backslashes flipped. This is superior to EscapedCodeBase,
// which does not correctly escape things, and ambiguates a
// space (%20) with a literal `%20` in the path. Sigh.
var managedPath = Assembly.GetExecutingAssembly().CodeBase ?? Assembly.GetExecutingAssembly().Location;

if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
if (managedPath.StartsWith("file:///"))
{
managedPath = managedPath.Substring(8).Replace('/', '\\');
}
else if (managedPath.StartsWith("file://"))
{
managedPath = @"\\" + managedPath.Substring(7).Replace('/', '\\');
}
}
else if (managedPath.StartsWith("file://"))
{
managedPath = managedPath.Substring(7);
}

managedPath = Path.GetDirectoryName(managedPath);

return managedPath;
}

class Runtime
{
public string RuntimeIdentifier { get; }

public List<string> ImportedRuntimeIdentifiers { get; }

public Runtime(string runtimeIdentifier, List<string> importedRuntimeIdentifiers)
{
RuntimeIdentifier = runtimeIdentifier;
ImportedRuntimeIdentifiers = importedRuntimeIdentifiers;
}
}

class Graph
{
readonly Dictionary<string, Runtime> runtimes;

public Graph(Dictionary<string, Runtime> runtimes)
{
this.runtimes = runtimes;
}

public List<string> GetCompatibleRuntimeIdentifiers(string runtimeIdentifier)
{
var result = new List<string>();

if (runtimes.TryGetValue(runtimeIdentifier, out var initialRuntime))
{
var queue = new Queue<Runtime>();
var hash = new HashSet<string>();

hash.Add(runtimeIdentifier);
queue.Enqueue(initialRuntime);

while (queue.Count > 0)
{
var runtime = queue.Dequeue();
result.Add(runtime.RuntimeIdentifier);

foreach (var item in runtime.ImportedRuntimeIdentifiers)
{
if (hash.Add(item))
{
queue.Enqueue(runtimes[item]);
}
}
}
}
else
{
result.Add(runtimeIdentifier);
}

return result;
}
}
}
}
Loading