-
Notifications
You must be signed in to change notification settings - Fork 76
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support loading plugin dependencies from .deps.json on .NET Framework…
… and Visual Studio MSBuild (#411) * Support loading plugin dependencies from .deps.json on .NET Framework and Visual Studio MSBuild * Update NUnitRetry * Fix loading plugin assemblies from deps.json on linux (case sensitive) * refactor resolvers and introduce base class * Move external plugin tests to ExternalPluginsTest * Add external test for SpecSync.AzureDevOps.TestSuiteBasedExecution.Reqnroll plugin --------- Co-authored-by: Gáspár Nagy <[email protected]>
- Loading branch information
1 parent
f59ced6
commit 182e933
Showing
28 changed files
with
379 additions
and
106 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Reflection; | ||
using Microsoft.Extensions.DependencyModel; | ||
using Microsoft.Extensions.DependencyModel.Resolution; | ||
|
||
namespace Reqnroll.Plugins; | ||
|
||
public abstract class AssemblyResolverBase | ||
{ | ||
private readonly Lazy<Assembly> _assembly; | ||
|
||
public Assembly GetAssembly() => _assembly.Value; | ||
|
||
private ICompilationAssemblyResolver _assemblyResolver; | ||
private DependencyContext _dependencyContext; | ||
|
||
protected AssemblyResolverBase(string relativePath) | ||
{ | ||
var path = Path.GetFullPath(relativePath); | ||
_assembly = new Lazy<Assembly>(() => Initialize(path)); | ||
} | ||
|
||
protected abstract Assembly Initialize(string path); | ||
|
||
protected void SetupDependencyContext(string path, Assembly assembly, bool throwOnError) | ||
{ | ||
try | ||
{ | ||
_dependencyContext = DependencyContext.Load(assembly); | ||
|
||
if (_dependencyContext is null) return; | ||
|
||
_assemblyResolver = new CompositeCompilationAssemblyResolver( | ||
[ | ||
new AppBaseCompilationAssemblyResolver(Path.GetDirectoryName(path)!), | ||
new ReferenceAssemblyPathResolver(), | ||
new PackageCompilationAssemblyResolver() | ||
]); | ||
} | ||
catch (Exception) | ||
{ | ||
if (throwOnError) | ||
throw; | ||
|
||
// We ignore if there was a problem with initializing context from .deps.json | ||
} | ||
} | ||
|
||
protected abstract Assembly LoadAssemblyFromPath(string assemblyPath); | ||
|
||
protected Assembly TryResolveAssembly(AssemblyName name) | ||
{ | ||
var library = _dependencyContext?.RuntimeLibraries.FirstOrDefault( | ||
runtimeLibrary => string.Equals(runtimeLibrary.Name, name.Name, StringComparison.OrdinalIgnoreCase)); | ||
|
||
if (library == null) | ||
return null; | ||
|
||
var wrapper = new CompilationLibrary( | ||
library.Type, | ||
library.Name, | ||
library.Version, | ||
library.Hash, | ||
library.RuntimeAssemblyGroups.SelectMany(g => g.AssetPaths), | ||
library.Dependencies, | ||
library.Serviceable, | ||
library.Path, | ||
library.HashPath); | ||
|
||
var assemblies = new List<string>(); | ||
_assemblyResolver.TryResolveAssemblyPaths(wrapper, assemblies); | ||
|
||
if (_assemblyResolver.TryResolveAssemblyPaths(wrapper, assemblies) && assemblies.Count > 0) | ||
{ | ||
foreach (var assemblyPath in assemblies) | ||
{ | ||
try | ||
{ | ||
return LoadAssemblyFromPath(assemblyPath); | ||
} | ||
catch | ||
{ | ||
// Don't throw if we can't load the specified assembly (perhaps something is missing or misconfigured) | ||
} | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
using System.Reflection; | ||
using System.Runtime.Loader; | ||
|
||
namespace Reqnroll.Plugins; | ||
|
||
/// <summary> | ||
/// This class is used for .NET Core based frameworks (.NET 6+) only. See <see cref="PlatformCompatibility.PlatformHelper"/>. | ||
/// </summary> | ||
public sealed class DotNetCorePluginAssemblyResolver(string path) : AssemblyResolverBase(path) | ||
{ | ||
private AssemblyLoadContext _loadContext; | ||
|
||
protected override Assembly Initialize(string path) | ||
{ | ||
_loadContext = AssemblyLoadContext.GetLoadContext(typeof(DotNetCorePluginAssemblyResolver).Assembly); | ||
var assembly = LoadAssemblyFromPath(path); | ||
|
||
SetupDependencyContext(path, assembly, true); | ||
|
||
_loadContext.Resolving += OnResolving; | ||
_loadContext.Unloading += OnUnloading; | ||
|
||
return assembly; | ||
} | ||
|
||
protected override Assembly LoadAssemblyFromPath(string assemblyPath) | ||
=> _loadContext.LoadFromAssemblyPath(assemblyPath); | ||
|
||
private void OnUnloading(AssemblyLoadContext context) | ||
{ | ||
_loadContext.Resolving -= OnResolving; | ||
_loadContext.Unloading -= OnUnloading; | ||
} | ||
|
||
private Assembly OnResolving(AssemblyLoadContext context, AssemblyName name) | ||
{ | ||
return TryResolveAssembly(name); | ||
} | ||
|
||
public static Assembly Load(string path) | ||
{ | ||
return new DotNetCorePluginAssemblyResolver(path).GetAssembly(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
using System; | ||
using System.Reflection; | ||
|
||
namespace Reqnroll.Plugins; | ||
|
||
/// <summary> | ||
/// This class is used for .NET Framework 4.* only. See <see cref="PlatformCompatibility.PlatformHelper"/>. | ||
/// </summary> | ||
public sealed class DotNetFrameworkPluginAssemblyResolver(string path) : AssemblyResolverBase(path) | ||
{ | ||
protected override Assembly Initialize(string path) | ||
{ | ||
var assembly = LoadAssemblyFromPath(path); | ||
|
||
SetupDependencyContext(path, assembly, false); | ||
AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve; | ||
|
||
return assembly; | ||
} | ||
|
||
protected override Assembly LoadAssemblyFromPath(string assemblyPath) | ||
=> Assembly.LoadFrom(assemblyPath); | ||
|
||
private Assembly OnAssemblyResolve(object sender, ResolveEventArgs args) | ||
{ | ||
var assemblyName = new AssemblyName(args.Name); | ||
return TryResolveAssembly(assemblyName); | ||
} | ||
|
||
public static Assembly Load(string path) | ||
{ | ||
return new DotNetFrameworkPluginAssemblyResolver(path).GetAssembly(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
5 changes: 5 additions & 0 deletions
5
Tests/Reqnroll.SystemTests/ExternalPlugins/ExternalPluginsTestBase.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
namespace Reqnroll.SystemTests.ExternalPlugins; | ||
|
||
public abstract class ExternalPluginsTestBase : SystemTestBase | ||
{ | ||
} |
Oops, something went wrong.