Skip to content
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

Fix libgit2 native module load failure #920

Merged
merged 4 commits into from
Apr 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
54 changes: 43 additions & 11 deletions src/NerdBank.GitVersioning/LibGit2/LibGit2GitExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Text;
using LibGit2Sharp;
using Validation;
using Windows.Win32;
using Version = System.Version;

#nullable enable
Expand Down Expand Up @@ -41,6 +42,10 @@ public static class LibGit2GitExtensions
ContextLines = 0,
};

#if !NETCOREAPP
private static FreeLibrarySafeHandle? nativeLibrary;
#endif

/// <summary>
/// Gets the number of commits in the longest single path between
/// the specified commit and the most distant ancestor (inclusive).
Expand Down Expand Up @@ -111,24 +116,51 @@ public static IEnumerable<Commit> GetCommitsFromVersion(LibGit2Context context,
/// <summary>
/// Finds the directory that contains the appropriate native libgit2 module.
/// </summary>
/// <param name="basePath">The path to the directory that contains the lib folder.</param>
/// <param name="basePath">The path to the directory that contains the runtimes folder.</param>
/// <returns>Receives the directory that native binaries are expected.</returns>
public static string? FindLibGit2NativeBinaries(string basePath)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return Path.Combine(basePath, "lib", "win32", IntPtr.Size == 4 ? "x86" : "x64");
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
string arch = RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant();

// TODO: learn how to detect when to use "linux-musl".
string? os =
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "win" :
RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "linux" :
RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "osx" :
null;

if (os is null)
{
return Path.Combine(basePath, "lib", "linux", IntPtr.Size == 4 ? "x86" : "x86_64");
return null;
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))

string candidatePath = Path.Combine(basePath, "runtimes", $"{os}-{arch}", "native");
return Directory.Exists(candidatePath) ? candidatePath : null;
}

/// <summary>
/// Loads the git2 native library from the expected path so that it's available later when needed.
/// </summary>
/// <param name="basePath"><inheritdoc cref="FindLibGit2NativeBinaries(string)" path="/param" /></param>
/// <remarks>
/// This method should only be called on .NET Framework, and only loads the module when on Windows.
/// </remarks>
public static void LoadNativeBinary(string basePath)
{
#if NETCOREAPP
throw new PlatformNotSupportedException();
#else
if (nativeLibrary is null && RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return Path.Combine(basePath, "lib", "osx", RuntimeInformation.OSArchitecture == Architecture.Arm64 ? "arm_64" : "x86_64");
if (FindLibGit2NativeBinaries(basePath) is string directoryPath)
{
if (Directory.EnumerateFiles(directoryPath).FirstOrDefault() is string filePath)
{
nativeLibrary = PInvoke.LoadLibrary(filePath);
}
}
}

return null;
#endif
}

/// <summary>
Expand Down
8 changes: 4 additions & 4 deletions src/NerdBank.GitVersioning/ManagedGit/FileHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
using System.Runtime.Versioning;
using Microsoft.Win32.SafeHandles;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Storage.FileSystem;
using Windows.Win32.System.SystemServices;

namespace Nerdbank.GitVersioning.ManagedGit;

Expand All @@ -31,7 +31,7 @@ internal static bool TryOpen(string path, out FileStream? stream)
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
#endif
{
SafeFileHandle? handle = PInvoke.CreateFile(path, FILE_ACCESS_FLAGS.FILE_GENERIC_READ, FILE_SHARE_MODE.FILE_SHARE_READ, lpSecurityAttributes: null, FILE_CREATION_DISPOSITION.OPEN_EXISTING, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NORMAL, null);
SafeFileHandle? handle = PInvoke.CreateFile(path, (uint)FILE_ACCESS_RIGHTS.FILE_GENERIC_READ, FILE_SHARE_MODE.FILE_SHARE_READ, lpSecurityAttributes: null, FILE_CREATION_DISPOSITION.OPEN_EXISTING, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NORMAL, null);

if (!handle.IsInvalid)
{
Expand Down Expand Up @@ -76,10 +76,10 @@ internal static unsafe bool TryOpen(ReadOnlySpan<char> path, [NotNullWhen(true)]
HANDLE handle;
fixed (char* pPath = &path[0])
{
handle = PInvoke.CreateFile(pPath, FILE_ACCESS_FLAGS.FILE_GENERIC_READ, FILE_SHARE_MODE.FILE_SHARE_READ, null, FILE_CREATION_DISPOSITION.OPEN_EXISTING, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NORMAL, default);
handle = PInvoke.CreateFile(pPath, (uint)FILE_ACCESS_RIGHTS.FILE_GENERIC_READ, FILE_SHARE_MODE.FILE_SHARE_READ, null, FILE_CREATION_DISPOSITION.OPEN_EXISTING, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NORMAL, default);
}

if (!handle.Equals(Constants.INVALID_HANDLE_VALUE))
if (!handle.Equals(HANDLE.INVALID_HANDLE_VALUE))
{
var fileHandle = new SafeFileHandle(handle, ownsHandle: true);
stream = new FileStream(fileHandle, System.IO.FileAccess.Read);
Expand Down
2 changes: 2 additions & 0 deletions src/NerdBank.GitVersioning/NativeMethods.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
CreateFile
FILE_ACCESS_RIGHTS
INVALID_HANDLE_VALUE
LoadLibrary
2 changes: 1 addition & 1 deletion src/NerdBank.GitVersioning/Nerdbank.GitVersioning.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<PackageReference Include="DotNetMDDocs" Version="0.112.39" PrivateAssets="all" Condition=" '$(GenerateMarkdownApiDocs)' == 'true' " />
<PackageReference Include="LibGit2Sharp" Version="0.27.0-preview-0182" PrivateAssets="none" />
<PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="3.1.6" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.1.478-beta" PrivateAssets="all" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.2.206-beta" PrivateAssets="all" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Validation" Version="2.5.51" />
<PackageReference Include="Nerdbank.GitVersioning.LKG" Version="3.4.173-alpha" />
Expand Down
21 changes: 6 additions & 15 deletions src/Nerdbank.GitVersioning.Tasks/ContextAwareTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#endif
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Nerdbank.GitVersioning.LibGit2;

namespace MSBuildExtensionTask
{
Expand All @@ -23,10 +24,11 @@ public abstract class ContextAwareTask : Microsoft.Build.Utilities.Task
/// <inheritdoc/>
public override bool Execute()
{
#if NETCOREAPP
string taskAssemblyPath = this.GetType().GetTypeInfo().Assembly.Location;

Assembly inContextAssembly = GitLoaderContext.Instance.LoadFromAssemblyPath(taskAssemblyPath);
string unmanagedBaseDirectory = Path.GetDirectoryName(Path.GetDirectoryName(taskAssemblyPath));
#if NETCOREAPP
GitLoaderContext loaderContext = new(unmanagedBaseDirectory);
Assembly inContextAssembly = loaderContext.LoadFromAssemblyPath(taskAssemblyPath);
Type innerTaskType = inContextAssembly.GetType(this.GetType().FullName);
object innerTask = Activator.CreateInstance(innerTaskType);

Expand Down Expand Up @@ -55,18 +57,7 @@ public override bool Execute()

return result;
#else
// On .NET Framework (on Windows), we find native binaries by adding them to our PATH.
if (this.UnmanagedDllDirectory is not null)
{
string pathEnvVar = Environment.GetEnvironmentVariable("PATH") ?? string.Empty;
string[] searchPaths = pathEnvVar.Split(Path.PathSeparator);
if (!searchPaths.Contains(this.UnmanagedDllDirectory, StringComparer.OrdinalIgnoreCase))
{
pathEnvVar += Path.PathSeparator + this.UnmanagedDllDirectory;
Environment.SetEnvironmentVariable("PATH", pathEnvVar);
}
}

LibGit2GitExtensions.LoadNativeBinary(unmanagedBaseDirectory);
return this.ExecuteInner();
#endif
}
Expand Down
50 changes: 48 additions & 2 deletions src/Nerdbank.GitVersioning.Tasks/GitLoaderContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,77 @@

// This code originally copied from https://github.com/dotnet/sourcelink/tree/c092238370e0437eb95722f28c79273244dc7f1a/src/Microsoft.Build.Tasks.Git
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See license information at https://github.com/dotnet/sourcelink/blob/c092238370e0437eb95722f28c79273244dc7f1a/License.txt.
#nullable enable

#if NETCOREAPP

using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using Nerdbank.GitVersioning.LibGit2;
using RuntimeEnvironment = Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment;

namespace Nerdbank.GitVersioning
{
public class GitLoaderContext : AssemblyLoadContext
{
public const string RuntimePath = "./runtimes";
private readonly string nativeDependencyBasePath;

private (string?, IntPtr) lastLoadedLibrary;

public static readonly GitLoaderContext Instance = new GitLoaderContext();
/// <summary>
/// Initializes a new instance of the <see cref="GitLoaderContext"/> class.
/// </summary>
/// <param name="nativeDependencyBasePath">The path to the directory that contains the "runtimes" folder.</param>
public GitLoaderContext(string nativeDependencyBasePath)
{
this.nativeDependencyBasePath = nativeDependencyBasePath;
}

/// <inheritdoc/>
protected override Assembly Load(AssemblyName assemblyName)
{
string path = Path.Combine(Path.GetDirectoryName(typeof(GitLoaderContext).Assembly.Location), assemblyName.Name + ".dll");
string path = Path.Combine(Path.GetDirectoryName(typeof(GitLoaderContext).Assembly.Location)!, assemblyName.Name + ".dll");
return File.Exists(path)
? this.LoadFromAssemblyPath(path)
: Default.LoadFromAssemblyName(assemblyName);
}

protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
IntPtr p = base.LoadUnmanagedDll(unmanagedDllName);

if (p == IntPtr.Zero)
{
if (unmanagedDllName == this.lastLoadedLibrary.Item1)
{
return this.lastLoadedLibrary.Item2;
}

string prefix =
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? string.Empty :
"lib";

string? extension =
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".dll" :
RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? ".so" :
RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? ".dylib" :
null;

string fileName = $"{prefix}{unmanagedDllName}{extension}";
string? directoryPath = LibGit2GitExtensions.FindLibGit2NativeBinaries(this.nativeDependencyBasePath);
if (directoryPath is not null && NativeLibrary.TryLoad(Path.Combine(directoryPath, fileName), out p))
{
// Cache this to make us a little faster next time.
this.lastLoadedLibrary = (unmanagedDllName, p);
}
}

return p;
}
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,6 @@
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>

<Target Name="SetNuSpecProperties" BeforeTargets="GenerateNuspec" DependsOnTargets="GetBuildVersion">
<PropertyGroup>
<LibGit2SharpNativeBinaries>$(NuGetPackageRoot)libgit2sharp.nativebinaries\$(LibGit2SharpNativeVersion)\</LibGit2SharpNativeBinaries>
<NuspecProperties>$(NuspecProperties);Version=$(Version);commit=$(GitCommitId);BaseOutputPath=$(OutputPath);LibGit2SharpNativeBinaries=$(LibGit2SharpNativeBinaries)</NuspecProperties>
<NuspecProperties Condition=" '$(PackLKG)' == 'true' ">$(NuspecProperties);LKGSuffix=.LKG</NuspecProperties>
</PropertyGroup>
</Target>

<Target Name="PackBuildOutputs" DependsOnTargets="SatelliteDllsProjectOutputGroup;DebugSymbolsProjectOutputGroup">
<PropertyGroup>
<BuildSubDir Condition=" '$(TargetFramework)' == 'net6.0' ">MSBuildCore\</BuildSubDir>
<BuildSubDir Condition=" '$(TargetFramework)' == 'net462' ">MSBuildFull\</BuildSubDir>
</PropertyGroup>
<Error Text="Unrecognized TargetFramework" Condition=" '$(BuildSubDir)' == '' " />
<ItemGroup>
<TfmSpecificPackageFile Include="&#xD;&#xA; $(OutputPath)LibGit2Sharp.dll*;&#xD;&#xA; $(OutputPath)MSBuildExtensionTask.dll;&#xD;&#xA; $(OutputPath)Microsoft.DotNet.PlatformAbstractions.dll;&#xD;&#xA; $(OutputPath)Nerdbank.GitVersioning.*dll;&#xD;&#xA; $(OutputPath)Newtonsoft.Json.dll;&#xD;&#xA; $(OutputPath)System.Text.Json.dll;&#xD;&#xA; $(OutputPath)System.Runtime.CompilerServices.Unsafe.dll;&#xD;&#xA; $(OutputPath)Validation.dll;&#xD;&#xA; ">
<PackagePath>build\$(BuildSubDir)</PackagePath>
</TfmSpecificPackageFile>
<!-- Package up the libgit2 native binaries -->
<TfmSpecificPackageFile Include="@(ContentWithTargetPath)" Condition=" '%(ContentWithTargetPath.CopyToOutputDirectory)' == 'PreserveNewest' ">
<PackagePath>build\$(BuildSubDir)%(ContentWithTargetPath.TargetPath)</PackagePath>
</TfmSpecificPackageFile>
</ItemGroup>
</Target>

<ItemGroup>
<None Include="build\**">
<Pack>true</Pack>
Expand Down Expand Up @@ -81,4 +56,6 @@
<ItemGroup>
<Compile Include="..\Shared\**\*.cs" LinkBase="Shared" />
</ItemGroup>

<Import Project="$(MSBuildProjectName).targets"/>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<Project>
<Target Name="SetNuSpecProperties" BeforeTargets="GenerateNuspec" DependsOnTargets="GetBuildVersion">
<PropertyGroup>
<LibGit2SharpNativeBinaries>$(NuGetPackageRoot)libgit2sharp.nativebinaries\$(LibGit2SharpNativeVersion)\</LibGit2SharpNativeBinaries>
<NuspecProperties>$(NuspecProperties);Version=$(Version);commit=$(GitCommitId);BaseOutputPath=$(OutputPath);LibGit2SharpNativeBinaries=$(LibGit2SharpNativeBinaries)</NuspecProperties>
<NuspecProperties Condition=" '$(PackLKG)' == 'true' ">$(NuspecProperties);LKGSuffix=.LKG</NuspecProperties>
</PropertyGroup>
</Target>

<Target Name="PackBuildOutputs" DependsOnTargets="SatelliteDllsProjectOutputGroup;DebugSymbolsProjectOutputGroup">
<PropertyGroup>
<BuildSubDir Condition=" '$(TargetFramework)' == 'net6.0' ">MSBuildCore\</BuildSubDir>
<BuildSubDir Condition=" '$(TargetFramework)' == 'net462' ">MSBuildFull\</BuildSubDir>
</PropertyGroup>
<Error Text="Unrecognized TargetFramework" Condition=" '$(BuildSubDir)' == '' " />
<ItemGroup>
<TfmSpecificPackageFile Include="
$(OutputPath)LibGit2Sharp.dll*;
$(OutputPath)MSBuildExtensionTask.dll;
$(OutputPath)Microsoft.DotNet.PlatformAbstractions.dll;
$(OutputPath)Nerdbank.GitVersioning.*dll;
$(OutputPath)Newtonsoft.Json.dll;
$(OutputPath)System.Text.Json.dll;
$(OutputPath)System.Runtime.CompilerServices.Unsafe.dll;
$(OutputPath)Validation.dll;
">
<PackagePath>build\$(BuildSubDir)</PackagePath>
</TfmSpecificPackageFile>
<!-- Package up the libgit2 native binaries -->
<TfmSpecificPackageFile Include="@(ContentWithTargetPath)" Condition=" '%(ContentWithTargetPath.CopyToOutputDirectory)' == 'PreserveNewest' ">
<PackagePath>build\$(BuildSubDir)%(ContentWithTargetPath.TargetPath)</PackagePath>
</TfmSpecificPackageFile>
</ItemGroup>
</Target>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,6 @@ IMPORTANT: The 3.x release may produce a different version height than prior maj
<file src="$LibGit2SharpNativeBinaries$runtimes\osx-arm64\native\libgit2-b7bad55.dylib" target="build\MSBuildFull\lib\osx-arm64\libgit2-b7bad55.dylib" />
<file src="$LibGit2SharpNativeBinaries$runtimes\linux-x64\native\libgit2-b7bad55.so" target="build\MSBuildFull\lib\linux-x64\libgit2-b7bad55.so" />

<!-- Additional copies to work with our own ps1 scripts (https://github.com/dotnet/Nerdbank.GitVersioning/issues/248) -->
<file src="$LibGit2SharpNativeBinaries$runtimes\win-x64\native\git2-b7bad55.dll" target="build\MSBuildFull\lib\win32\x64\git2-b7bad55.dll" />
<file src="$LibGit2SharpNativeBinaries$runtimes\win-x86\native\git2-b7bad55.dll" target="build\MSBuildFull\lib\win32\x86\git2-b7bad55.dll" />

<file src="$LibGit2SharpNativeBinaries$libgit2\LibGit2Sharp.dll.config" target="build\MSBuildCore\LibGit2Sharp.dll.config" />
<file src="$BaseOutputPath$net6.0\LibGit2Sharp.dll" target="build\MSBuildCore\LibGit2Sharp.dll" />
<file src="$BaseOutputPath$net6.0\Microsoft.DotNet.PlatformAbstractions.dll" target="build\MSBuildCore\Microsoft.DotNet.PlatformAbstractions.dll" />
Expand Down
3 changes: 2 additions & 1 deletion src/nbgv/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ public static int Main(string[] args)
{
string thisAssemblyPath = typeof(Program).GetTypeInfo().Assembly.Location;

Assembly inContextAssembly = GitLoaderContext.Instance.LoadFromAssemblyPath(thisAssemblyPath);
GitLoaderContext loaderContext = new(Path.GetDirectoryName(thisAssemblyPath));
Assembly inContextAssembly = loaderContext.LoadFromAssemblyPath(thisAssemblyPath);
Type innerProgramType = inContextAssembly.GetType(typeof(Program).FullName);
object innerProgram = Activator.CreateInstance(innerProgramType);

Expand Down