8000 Merge pull request #1563 from tmat/NativeLoad · repo-archive/libgit2sharp@8f6ad7c · GitHub
[go: up one dir, main page]

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 8f6ad7c

Browse files
authored
Merge pull request libgit2#1563 from tmat/NativeLoad
Avoid reading and writing global state when loading native library
2 parents 76b997a + 497f91f commit 8f6ad7c

14 files changed

+375
-83
lines changed

LibGit2Sharp.Tests/GlobalSettingsFixture.cs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
using System.Text.RegularExpressions;
1+
using System;
2+
using System.IO;
3+
using System.Text.RegularExpressions;
4+
using LibGit2Sharp.Core;
25
using LibGit2Sharp.Tests.TestHelpers;
36
using Xunit;
47

@@ -49,5 +52,34 @@ public void TryingToResetNativeLibraryPathAfterLoadedThrows()
4952

5053
Assert.Throws<LibGit2SharpException>(() => { GlobalSettings.NativeLibraryPath = "C:/Foo"; });
5154
}
55+
56+
[SkippableTheory]
57+
[InlineData("x86")]
58+
[InlineData("x64")]
59+
public void LoadFromSpecifiedPath(string architecture)
60+
{
61+
Skip.IfNot(Platform.IsRunningOnNetFramework(), ".NET Framework only test.");
62+
63+
var nativeDllFileName = NativeDllName.Name + ".dll";
64+
var testDir = Path.GetDirectoryName(typeof(GlobalSettingsFixture).Assembly.Location);
65+
var testAppExe = Path.Combine(testDir, $"NativeLibraryLoadTestApp.{architecture}.exe");
66+
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
67+
var platformDir = Path.Combine(tempDir, "plat");
68+
69+
try
70+
{
71+
Directory.CreateDirectory(Path.Combine(platformDir, architecture));
72+
File.Copy(Path.Combine(GlobalSettings.NativeLibraryPath, architecture, nativeDllFileName), Path.Combine(platformDir, architecture, nativeDllFileName));
73+
74+
var (output, exitCode) = ProcessHelper.RunProcess(testAppExe, arguments: $@"{NativeDllName.Name} ""{platformDir}""", workingDirectory: tempDir);
75+
76+
Assert.Empty(output);
77+
Assert.Equal(0, exitCode);
78+
}
79+
finally
80+
{
81+
DirectoryHelper.DeleteDirectory(tempDir);
82+
}
83+
}
5284
}
5385
}

LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
<ItemGroup>
99
<ProjectReference Include="..\LibGit2Sharp\LibGit2Sharp.csproj" />
10+
<ProjectReference Include="..\NativeLibraryLoadTestApp\x86\NativeLibraryLoadTestApp.x86.csproj" Condition="'$(TargetFramework)' == 'net461'" ReferenceOutputAssembly="false" OutputItemType="TestAppExe" />
11+
<ProjectReference Include="..\NativeLibraryLoadTestApp\x64\NativeLibraryLoadTestApp.x64.csproj" Condition="'$(TargetFramework)' == 'net461'" ReferenceOutputAssembly="false" OutputItemType="TestAppExe" />
1012
</ItemGroup>
1113

1214
<ItemGroup>
@@ -24,4 +26,18 @@
2426
<Compile Remove="desktop\**" Condition=" '$(TargetFramework)' != 'net461' " />
2527
</ItemGroup>
2628

29+
<Target Name="CopyTestAppExes" AfterTargets="ResolveProjectReferences">
30+
<ItemGroup>
31+
<_TestAppFile Include="@(TestAppExe->'%(RootDir)%(Directory)%(Filename).exe')" />
32+
<_TestAppFile Include="@(TestAppExe->'%(RootDir)%(Directory)%(Filename).exe.config')" />
33+
<_TestAppFile Include="@(TestAppExe->'%(RootDir)%(Directory)%(Filename).pdb')" />
34+
</ItemGroup>
35+
36+
<ItemGroup>
37+
<Content Include="@(_TestAppFile)" CopyToOutputDirectory="PreserveNewest" Visible="false" />
38+
</ItemGroup>
39+
</Target>
40+
41+
<Import Project="..\Targets\GenerateNativeDllName.targets" />
42+
2743
</Project>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Text;
5+
6+
namespace LibGit2Sharp.Tests
7+
{
8+
public static class ProcessHelper
9+
{
10+
public static (string, int) RunProcess(string fileName, string arguments, string workingDirectory = null)
11+
{
12+
var process = new Process
13+
{
14+
StartInfo = new ProcessStartInfo(fileName, arguments)
15+
{
16+
RedirectStandardError = true,
17+
RedirectStandardOutput = true,
18+
CreateNoWindow = true,
19+
UseShellExecute = false,
20+
WorkingDirectory = workingDirectory ?? string.Empty
21+
}
22+
};
23+
24+
var output = new StringBuilder();
25+
26+
process.OutputDataReceived += (_, e) => output.AppendLine(e.Data);
27+
process.ErrorDataReceived += (_, e) => output.AppendLine(e.Data);
28+
29+
process.Start();
30+
31+
process.WaitForExit();
32+
33+
return (output.ToString(), process.ExitCode);
34+
}
35+
}
36+
}

LibGit2Sharp.sln

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,18 @@ EndProject
99
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0CA739FD-DA4D-4F64-9834-DA14A3ECD04B}"
1010
ProjectSection(SolutionItems) = preProject
1111
.gitignore = .gitignore
12+
Targets\CodeGenerator.targets = Targets\CodeGenerator.targets
1213
Directory.Build.props = Directory.Build.props
1314
Directory.Build.targets = Directory.Build.targets
15+
Targets\GenerateNativeDllName.targets = Targets\GenerateNativeDllName.targets
1416
nuget.config = nuget.config
1517
version.json = version.json
1618
EndProjectSection
1719
EndProject
20+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NativeLibraryLoadTestApp.x86", "NativeLibraryLoadTestApp\x86\NativeLibraryLoadTestApp.x86.csproj", "{86453D2C-4953-4DF4-B12A-ADE579608BAA}"
21+
EndProject
22+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NativeLibraryLoadTestApp.x64", "NativeLibraryLoadTestApp\x64\NativeLibraryLoadTestApp.x64.csproj", "{5C55175D-6A1F-4C51-B791-BF7DD00124EE}"
23+
EndProject
1824
Global
1925
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2026
Debug|Any CPU = Debug|Any CPU
@@ -29,6 +35,14 @@ Global
2935
{286E63EB-04DD-4ADE-88D6-041B57800761}.Debug|Any CPU.Build.0 = Debug|Any CPU
3036
{286E63EB-04DD-4ADE-88D6-041B57800761}.Release|Any CPU.ActiveCfg = Release|Any CPU
3137
{286E63EB-04DD-4ADE-88D6-041B57800761}.Release|Any CPU.Build.0 = Release|Any CPU
38+
{86453D2C-4953-4DF4-B12A-ADE579608BAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39+
{86453D2C-4953-4DF4-B12A-ADE579608BAA}.Debug|Any CPU.Build.0 = Debug|Any CPU
40+
{86453D2C-4953-4DF4-B12A-ADE579608BAA}.Release|Any CPU.ActiveCfg = Release|Any CPU
41+
{86453D2C-4953-4DF4-B12A-ADE579608BAA}.Release|Any CPU.Build.0 = Release|Any CPU
42+
{5C55175D-6A1F-4C51-B791-BF7DD00124EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
43+
{5C55175D-6A1F-4C51-B791-BF7DD00124EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
44+
{5C55175D-6A1F-4C51-B791-BF7DD00124EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
45+
{5C55175D-6A1F-4C51-B791-BF7DD00124EE}.Release|Any CPU.Build.0 = Release|Any CPU
3246
EndGlobalSection
3347
GlobalSection(SolutionProperties) = preSolution
3448
HideSolutionNode = FALSE

LibGit2Sharp/Core/NativeMethods.cs

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
using System;
2-
using System.Globalization;
32
using System.IO;
43
using System.Runtime.CompilerServices;
4+
using System.Runtime.ConstrainedExecution;
55
using System.Runtime.InteropServices;
66
using LibGit2Sharp.Core.Handles;
77

8+
// Restrict the set of directories where the native library is loaded from to safe directories.
9+
[assembly: DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.ApplicationDirectory | DllImportSearchPath.SafeDirectories)]
10+
11+
#pragma warning disable IDE1006 // Naming Styles
12+
813
// ReSharper disable InconsistentNaming
914
namespace LibGit2Sharp.Core
1015
{
@@ -17,41 +22,68 @@ internal static class NativeMethods
1722
// This will handle initialization and shutdown of the underlying
1823
// native library.
1924
#pragma warning disable 0414
20-
private static readonly NativeShutdownObject shutdownObject;
21-
#pragma warning restore 0414
25+
private static NativeShutdownObject shutdownObject;
26+
#pragma warning restore 0414
2227

2328
static NativeMethods()
2429
{
25-
if (Platform.OperatingSystem == OperatingSystemType.Windows)
30+
if (Platform.IsRunningOnNetFramework() || Platform.IsRunningOnNetCore())
2631
{
27-
string nativeLibraryPath = GlobalSettings.GetAndLockNativeLibraryPath();
28-
29-
string path = Path.Combine(nativeLibraryPath, Platform.ProcessorArchitecture);
30-
31-
const string pathEnvVariable = "PATH";
32-
Environment.SetEnvironmentVariable(pathEnvVariable,
33-
String.Format(CultureInfo.InvariantCulture, "{0}{1}{2}", path, Path.PathSeparator, Environment.GetEnvironmentVariable(pathEnvVariable)));
32+
string nativeLibraryDir = GlobalSettings.GetAndLockNativeLibraryPath();
33+
if (nativeLibraryDir != null)
34+
{
35+
string nativeLibraryPath = Path.Combine(nativeLibraryDir, libgit2 + Platform.GetNativeLibraryExtension());
36+
37+
// Try to load the .dll from the path explicitly.
38+
// If this call succeeds further DllImports will find the library loaded and not attempt to load it again.
39+
// If it fails the next DllImport will load the library from safe directories.
40+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
41+
{
42+
LoadWindowsLibrary(nativeLibraryPath);
43+
}
44+
else
45+
{
46+
LoadUnixLibrary(nativeLibraryPath, RTLD_NOW);
47+
}
48+
}
3449
}
3550

36-
LoadNativeLibrary();
37-
shutdownObject = new NativeShutdownObject();
51+
InitializeNativeLibrary();
3852
}
3953

54+
public const int RTLD_NOW = 0x002;
55+
56+
[DllImport("libdl", EntryPoint = "dlopen")]
57+
private static extern IntPtr LoadUnixLibrary(string path, int flags);
58+
59+
[DllImport("kernel32", EntryPoint = "LoadLibrary")]
60+
private static extern IntPtr LoadWindowsLibrary(string path);
61+
4062
// Avoid inlining this method because otherwise mono's JITter may try
4163
// to load the library _before_ we've configured the path.
4264
[MethodImpl(MethodImplOptions.NoInlining)]
43-
private static void LoadNativeLibrary()
65+
private static void InitializeNativeLibrary()
4466
{
45-
// Configure the OpenSSL locking on the true initialization
46-
// of the library.
47-
if (git_libgit2_init() == 1)
67+
int initCounter;
68+
try
69+
{
70+
}
71+
finally // avoid thread aborts
72+
{
73+
// Initialization can be called multiple times as long as there is a corresponding shutdown to each initialization.
74+
initCounter = git_libgit2_init();
75+
shutdownObject = new NativeShutdownObject();
76+
}
77+
78+
// Configure the OpenSSL locking on the first initialization of the library in the current process.
79+
if (initCounter == 1)
4880
{
4981
git_openssl_set_locking();
5082
}
5183
}
5284

5385
// Shutdown the native library in a finalizer.
54-
private sealed class NativeShutdownObject
86+
private sealed class NativeShutdownObject : CriticalFinalizerObject
5587
{
5688
~NativeShutdownObject()
5789
{

LibGit2Sharp/Core/Platform.cs

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

36-
throw new InvalidOperationException();
36+
throw new PlatformNotSupportedException();
3737
}
3838
}
39+
40+
public static string GetNativeLibraryExtension()
41+
{
42+
switch (OperatingSystem)
43+
{
44+
case OperatingSystemType.MacOSX:
45+
return ".dylib";
46+
47+
case OperatingSystemType.Unix:
48 D0FE +
return ".so";
49+
50+
case OperatingSystemType.Windows:
51+
return ".dll";
52+
}
53+
54+
throw new PlatformNotSupportedException();
55+
}
56+
57+
/// <summary>
58+
/// Returns true if the runtime is Mono.
59+
/// </summary>
60+
public static bool IsRunningOnMono()
61+
=> Type.GetType("Mono.Runtime") != null;
62+
63+
/// <summary>
64+
/// Returns true if the runtime is .NET Framework.
65+
/// </summary>
66+
public static bool IsRunningOnNetFramework()
67+
=> typeof(object).Assembly.GetName().Name == "mscorlib" && !IsRunningOnMono();
68+
69+
/// <summary>
70+
/// Returns true if the runtime is .NET Core.
71+
/// </summary>
72+
public static bool IsRunningOnNetCore()
73+
=> typeof(object).Assembly.GetName().Name != "mscorlib";
3974
}
4075
}

0 commit comments

Comments
 (0)
0