mirror of
https://github.com/AlexMacocian/Plumsy.git
synced 2026-07-15 14:59:30 +00:00
Add project files.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
* amacocian@yahoo.com
|
||||
@@ -0,0 +1,50 @@
|
||||
name: Plumsy CD Pipeline
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
environment: Default
|
||||
strategy:
|
||||
matrix:
|
||||
targetplatform: [x64]
|
||||
|
||||
runs-on: windows-latest
|
||||
|
||||
env:
|
||||
Configuration: Release
|
||||
Solution_Path: Plumsy.sln
|
||||
Test_Project_Path: Plumsy.Tests\Plumsy.Tests.csproj
|
||||
Source_Project_Path: Plumsy\Plumsy.csproj
|
||||
Actions_Allow_Unsecure_Commands: true
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install .NET Core
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: '7.x'
|
||||
|
||||
- name: Setup MSBuild.exe
|
||||
uses: microsoft/setup-msbuild@v1.0.1
|
||||
|
||||
- name: Restore project
|
||||
run: msbuild $env:Solution_Path /t:Restore /p:Configuration=$env:Configuration /p:RuntimeIdentifier=$env:RuntimeIdentifier
|
||||
env:
|
||||
RuntimeIdentifier: win-${{ matrix.targetplatform }}
|
||||
|
||||
- name: Build Plumsy project
|
||||
run: dotnet build Plumsy -c $env:Configuration
|
||||
|
||||
- name: Package Plumsy
|
||||
run: dotnet pack -c Release -o . $env:Source_Project_Path
|
||||
|
||||
- name: Publish
|
||||
run: dotnet nuget push *.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
|
||||
@@ -0,0 +1,59 @@
|
||||
name: Plumsy CI Pipeline
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
targetplatform: [x64]
|
||||
|
||||
runs-on: windows-latest
|
||||
|
||||
env:
|
||||
Solution_Path: Plumsy.sln
|
||||
Test_Project_Path: Plumsy.Tests\Plumsy.Tests.csproj
|
||||
Test_Plugin_Project_Path: Plumsy.Tests.SimplePlugin\Plumsy.Tests.SimplePlugin.csproj
|
||||
Source_Project_Path: Plumsy\Plumsy.csproj
|
||||
Actions_Allow_Unsecure_Commands: true
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install .NET Core
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: '6.x'
|
||||
|
||||
- name: Setup MSBuild.exe
|
||||
uses: microsoft/setup-msbuild@v1.0.1
|
||||
|
||||
- name: Build SimplePlugin project
|
||||
run: dotnet build $env:Test_Plugin_Project_Path
|
||||
|
||||
- name: Prepare plugin files # Needs to be done manually in this pipeline
|
||||
run: |
|
||||
mkdir ${{ github.workspace }}\SimplePlugin
|
||||
cp ${{ github.workspace }}\Plumsy.Tests.SimplePlugin\bin\Debug\net6.0\Plumsy.Tests.SimplePlugin.dll ${{ github.workspace }}\SimplePlugin\Plumsy.Tests.SimplePlugin.dll
|
||||
cp ${{ github.workspace }}\Plumsy.Tests.SimplePlugin\bin\Debug\net6.0\SystemExtensions.NetStandard.dll ${{ github.workspace }}\SimplePlugin\SystemExtensions.NetStandard.dll
|
||||
|
||||
- name: Echo solution path
|
||||
run: echo /p:SolutionDir=${{ github.workspace }}\
|
||||
|
||||
- name: Execute Plumsy Unit Tests
|
||||
run: dotnet test $env:Test_Project_Path /p:SolutionDir=${{ github.workspace }}\ --logger:"console;verbosity=normal" # Need to manually set the SolutionDir, otherwise it's unspecified
|
||||
|
||||
- name: Restore Project
|
||||
run: msbuild $env:Solution_Path /t:Restore /p:Configuration=$env:Configuration /p:RuntimeIdentifier=$env:RuntimeIdentifier
|
||||
env:
|
||||
Configuration: Debug
|
||||
RuntimeIdentifier: win-${{ matrix.targetplatform }}
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Macocian Alexandru Victor
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Extensions;
|
||||
|
||||
namespace Plum.Net.Tests.SimplePlugin;
|
||||
|
||||
public sealed class Main
|
||||
{
|
||||
public Main()
|
||||
{
|
||||
this.ThrowIfNull("this");
|
||||
}
|
||||
|
||||
public bool ReturnTrue() => true;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<DestinationFolder>$(SolutionDir)SimplePlugin</DestinationFolder>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="CopyAssembliesToSolution" AfterTargets="PostBuildEvent">
|
||||
<Copy SourceFiles="$(OutputPath)$(AssemblyName).dll" DestinationFolder="$(DestinationFolder)" />
|
||||
<Copy SourceFiles="$(OutputPath)SystemExtensions.NetStandard.dll" DestinationFolder="$(DestinationFolder)" />
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SystemExtensions.NetStandard" Version="1.6.2" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,209 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using NSubstitute;
|
||||
using Plum.Net.Models;
|
||||
using Plum.Net.Tests.Resolvers;
|
||||
using Plum.Net.Tests.SimplePlugin;
|
||||
using Plum.Net.Validators;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Runtime.Loader;
|
||||
|
||||
namespace Plum.Net.Tests;
|
||||
|
||||
[TestClass]
|
||||
public class PluginManagerTests
|
||||
{
|
||||
private readonly string pluginDirectory;
|
||||
private readonly PluginManager pluginManager;
|
||||
|
||||
public PluginManagerTests()
|
||||
{
|
||||
this.pluginDirectory = Path.Combine(Environment.CurrentDirectory, "Plugins");
|
||||
this.pluginManager = new(this.pluginDirectory);
|
||||
this.pluginManager.WithDependencyResolver(new SimpleDependencyResolver());
|
||||
}
|
||||
|
||||
[TestInitialize]
|
||||
public void TestInitialize()
|
||||
{
|
||||
var assemblyLoadEventHandlerField = typeof(AssemblyLoadContext).GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).First(f => f.Name == "AssemblyLoad");
|
||||
assemblyLoadEventHandlerField.SetValue(AssemblyLoadContext.Default, null);
|
||||
|
||||
var resolveDependencyEventHandlerField = typeof(AssemblyLoadContext).GetFields(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).First(f => f.Name == "AssemblyResolve");
|
||||
resolveDependencyEventHandlerField.SetValue(AssemblyLoadContext.Default, null);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PluginManager_MissingPluginDirectory_ShouldReturnNoPlugins()
|
||||
{
|
||||
var pluginManager = new PluginManager(Path.Combine(this.pluginDirectory, Guid.NewGuid().ToString()));
|
||||
|
||||
var plugins = pluginManager.GetAvailablePlugins();
|
||||
|
||||
plugins.Should().HaveCount(0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PluginManager_ExistingPlugin_ShouldReturnExpectedPlugin()
|
||||
{
|
||||
var plugins = this.pluginManager.GetAvailablePlugins();
|
||||
|
||||
plugins.Should().HaveCount(1);
|
||||
plugins.First().Name.Should().Be("Plum.Net.Tests.SimplePlugin");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PluginManager_VersionValidator_ValidatesVersion()
|
||||
{
|
||||
var called = false;
|
||||
var validator = Substitute.For<IEnvironmentVersionValidator>();
|
||||
this.pluginManager.WithEnvironmentVersionValidator(validator);
|
||||
validator.Validate(Arg.Any<Version>(), Arg.Any<Version>()).Returns(callinfo =>
|
||||
{
|
||||
called = true;
|
||||
return true;
|
||||
});
|
||||
|
||||
var plugins = this.pluginManager.GetAvailablePlugins().ToList();
|
||||
|
||||
called.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PluginManager_VersionValidator_CurrentVersionMatchesEnvironment()
|
||||
{
|
||||
var validator = Substitute.For<IEnvironmentVersionValidator>();
|
||||
this.pluginManager.WithEnvironmentVersionValidator(validator);
|
||||
validator.Validate(Arg.Any<Version>(), Arg.Any<Version>()).Returns(callinfo =>
|
||||
{
|
||||
var currentVersion = callinfo.ArgAt<Version>(0);
|
||||
currentVersion.Should().Be(Environment.Version);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
var plugins = this.pluginManager.GetAvailablePlugins().ToList();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PluginManager_VersionValidatorDenies_ReturnsNoPlugins()
|
||||
{
|
||||
var validator = Substitute.For<IEnvironmentVersionValidator>();
|
||||
this.pluginManager.WithEnvironmentVersionValidator(validator);
|
||||
validator.Validate(Arg.Any<Version>(), Arg.Any<Version>()).Returns(callinfo =>
|
||||
{
|
||||
return false;
|
||||
});
|
||||
|
||||
var plugins = this.pluginManager.GetAvailablePlugins().ToList();
|
||||
|
||||
plugins.Should().HaveCount(0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PluginManager_TypeDefinitionsValidator_CallsValidator()
|
||||
{
|
||||
var called = false;
|
||||
var validator = Substitute.For<ITypeDefinitionsValidator>();
|
||||
this.pluginManager.WithTypeDefinitionsValidator(validator);
|
||||
validator.Validate(Arg.Any<IEnumerable<TypeDefinition>>(), Arg.Any<MetadataReader>()).Returns(callinfo =>
|
||||
{
|
||||
called = true;
|
||||
return true;
|
||||
});
|
||||
|
||||
var plugins = this.pluginManager.GetAvailablePlugins().ToList();
|
||||
called.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PluginManager_TypeDefinitionsValidator_ReceivesMainType()
|
||||
{
|
||||
var validator = Substitute.For<ITypeDefinitionsValidator>();
|
||||
this.pluginManager.WithTypeDefinitionsValidator(validator);
|
||||
validator.Validate(Arg.Any<IEnumerable<TypeDefinition>>(), Arg.Any<MetadataReader>()).Returns(callinfo =>
|
||||
{
|
||||
var typeDefinitions = callinfo.ArgAt<IEnumerable<TypeDefinition>>(0);
|
||||
var metadataReader = callinfo.ArgAt<MetadataReader>(1);
|
||||
typeDefinitions.Select(t => metadataReader.GetString(t.Name)).Any(t => t == "Main").Should().BeTrue();
|
||||
return true;
|
||||
});
|
||||
|
||||
var plugins = this.pluginManager.GetAvailablePlugins().ToList();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PluginManager_MetadataValidator_CallsValidator()
|
||||
{
|
||||
var called = false;
|
||||
var validator = Substitute.For<IMetadataValidator>();
|
||||
this.pluginManager.WithMetadataValidator(validator);
|
||||
validator.Validate(Arg.Any<MetadataReader>()).Returns(callinfo =>
|
||||
{
|
||||
var metadataReader = callinfo.ArgAt<MetadataReader>(0);
|
||||
metadataReader.Should().NotBeNull();
|
||||
called = true;
|
||||
return true;
|
||||
});
|
||||
|
||||
var plugins = this.pluginManager.GetAvailablePlugins().ToList();
|
||||
called.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PluginManager_LoadPlugins_ShouldSucceed()
|
||||
{
|
||||
var plugins = this.pluginManager.GetAvailablePlugins();
|
||||
|
||||
var results = this.pluginManager.LoadPlugins(plugins);
|
||||
|
||||
results.Should().HaveCount(1);
|
||||
results.First().Should().BeOfType<PluginLoadOperation.Success>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PluginManager_LoadPlugins_ReturnsExpectedAssembly()
|
||||
{
|
||||
this.pluginManager.WithForceLoadDependencies(true);
|
||||
var plugins = this.pluginManager.GetAvailablePlugins();
|
||||
|
||||
var results = this.pluginManager.LoadPlugins(plugins);
|
||||
var assembly = results.OfType<PluginLoadOperation.Success>().First().Plugin.Assembly;
|
||||
var mainType = assembly.GetTypes().First(t => t.Name == nameof(Main));
|
||||
var main = Activator.CreateInstance(mainType).As<Main>();
|
||||
|
||||
main.Should().NotBeNull();
|
||||
main.ReturnTrue().Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Ignore("This test fails when run together with the other tests, as the dependent assembly is loaded into the AppDomain by other tests")]
|
||||
public void PluginManager_LoadPlugins_WithNoResolver_FailsToResolveAssembly()
|
||||
{
|
||||
var pluginManager = new PluginManager(this.pluginDirectory);
|
||||
pluginManager.WithForceLoadDependencies(true);
|
||||
var plugins = pluginManager.GetAvailablePlugins();
|
||||
|
||||
var results = pluginManager.LoadPlugins(plugins);
|
||||
|
||||
results.First().Should().BeOfType<PluginLoadOperation.ExceptionEncountered>();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PluginManager_LoadPlugins_WithNoResolverAndNoForcedDependencyResolve_Succeeds()
|
||||
{
|
||||
var pluginManager = new PluginManager(this.pluginDirectory);
|
||||
pluginManager.WithForceLoadDependencies(false);
|
||||
var plugins = pluginManager.GetAvailablePlugins();
|
||||
|
||||
var results = pluginManager.LoadPlugins(plugins);
|
||||
|
||||
results.First().Should().BeOfType<PluginLoadOperation.Success>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="PreparePluginTestSetup" BeforeTargets="PrepareForBuild">
|
||||
<Copy SourceFiles="$(SolutionDir)SimplePlugin\Plum.Net.Tests.SimplePlugin.dll" DestinationFolder="$(OutputPath)Plugins" />
|
||||
<Copy SourceFiles="$(SolutionDir)SimplePlugin\SystemExtensions.NetStandard.dll" DestinationFolder="$(OutputPath)Dependencies" />
|
||||
</Target>
|
||||
|
||||
<Target Name="RemoveUnwantedDependency" AfterTargets="Build">
|
||||
<!-- We need to delete SystemExtensions.NetStandard.dll from the output path, to make the assembly resolver have to search for the dependency where we point it to -->
|
||||
<Delete Files="$(OutputPath)SystemExtensions.NetStandard.dll" />
|
||||
</Target>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
|
||||
<PackageReference Include="coverlet.collector" Version="3.2.0" />
|
||||
<PackageReference Include="NSubstitute" Version="5.1.0" />
|
||||
<PackageReference Include="NSubstitute.Analyzers.CSharp" Version="1.0.16">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Plum.Net.Tests.SimplePlugin\Plum.Net.Tests.SimplePlugin.csproj" />
|
||||
<ProjectReference Include="..\Plum.Net\Plum.Net.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,16 @@
|
||||
using Plum.Net.Resolvers;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Plum.Net.Tests.Resolvers;
|
||||
|
||||
public sealed class SimpleDependencyResolver : IDependencyResolver
|
||||
{
|
||||
public bool TryResolveDependency(Assembly? requestingAssembly, string dependencyName, out string? path)
|
||||
{
|
||||
var dllName = dependencyName.Split(',').First();
|
||||
path = Path.Combine(Path.GetFullPath("Dependencies"), $"{dllName}.dll");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.7.34024.191
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plumsy", "Plumsy\Plumsy.csproj", "{A9C9E517-90BE-4B5D-8B06-491DF4F125EF}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plumsy.Tests.SimplePlugin", "Plumsy.Tests.SimplePlugin\Plumsy.Tests.SimplePlugin.csproj", "{93D702F9-320D-4490-AE7B-8F6D70A1B2DB}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plumsy.Tests", "Plumsy.Tests\Plumsy.Tests.csproj", "{0C18CB86-2495-4713-A27C-C06321CC8539}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{93D702F9-320D-4490-AE7B-8F6D70A1B2DB} = {93D702F9-320D-4490-AE7B-8F6D70A1B2DB}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Pipelines", "Pipelines", "{EB205ABC-DAFB-4F8D-A7E0-E83EC03A2678}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.github\workflows\cd.yaml = .github\workflows\cd.yaml
|
||||
.github\workflows\ci.yaml = .github\workflows\ci.yaml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{A9C9E517-90BE-4B5D-8B06-491DF4F125EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A9C9E517-90BE-4B5D-8B06-491DF4F125EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A9C9E517-90BE-4B5D-8B06-491DF4F125EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A9C9E517-90BE-4B5D-8B06-491DF4F125EF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{93D702F9-320D-4490-AE7B-8F6D70A1B2DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{93D702F9-320D-4490-AE7B-8F6D70A1B2DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{93D702F9-320D-4490-AE7B-8F6D70A1B2DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{93D702F9-320D-4490-AE7B-8F6D70A1B2DB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0C18CB86-2495-4713-A27C-C06321CC8539}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0C18CB86-2495-4713-A27C-C06321CC8539}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0C18CB86-2495-4713-A27C-C06321CC8539}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0C18CB86-2495-4713-A27C-C06321CC8539}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {D3A216E7-9DC1-445B-8C7F-F080CC413731}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,8 @@
|
||||
using System.Reflection;
|
||||
|
||||
namespace Plum.Net.Callbacks;
|
||||
|
||||
public interface IAssemblyLoadedCallback
|
||||
{
|
||||
void AssemblyLoaded(Assembly assembly);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Plum.Net.Models;
|
||||
|
||||
namespace Plum.Net.Exceptions;
|
||||
|
||||
public sealed class PluginLoadException : Exception
|
||||
{
|
||||
public PluginEntry Plugin { get; }
|
||||
|
||||
public PluginLoadException(string? message, Exception? innerException, PluginEntry plugin) : base(message, innerException)
|
||||
{
|
||||
this.Plugin = plugin;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Reflection;
|
||||
|
||||
namespace Plum.Net.Models;
|
||||
|
||||
public sealed class Plugin
|
||||
{
|
||||
public PluginEntry PluginEntry { get; init; } = default!;
|
||||
public Assembly Assembly { get; init; } = default!;
|
||||
|
||||
internal Plugin()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Plum.Net.Models;
|
||||
|
||||
public sealed class PluginEntry
|
||||
{
|
||||
public string Name { get; init; }
|
||||
public string Path { get; init; }
|
||||
|
||||
internal PluginEntry()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
namespace Plum.Net.Models;
|
||||
|
||||
public abstract class PluginLoadOperation
|
||||
{
|
||||
public PluginEntry PluginEntry { get; init; } = default!;
|
||||
public abstract string Description { get; }
|
||||
|
||||
public sealed class Success : PluginLoadOperation
|
||||
{
|
||||
public Plugin Plugin { get; init; } = default!;
|
||||
public override string Description => "Plugin loaded successfully";
|
||||
|
||||
internal Success()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class NullEntry : PluginLoadOperation
|
||||
{
|
||||
public override string Description => $"Provided {nameof(PluginEntry)} is null";
|
||||
|
||||
internal NullEntry()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class FileNotFound : PluginLoadOperation
|
||||
{
|
||||
public string Path { get; init; } = default!;
|
||||
public override string Description => $"Failed to load plugin. Check {nameof(Path)} property for details";
|
||||
|
||||
internal FileNotFound()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ExceptionEncountered : PluginLoadOperation
|
||||
{
|
||||
public override string Description => "Exception encountered while loading plugin";
|
||||
public Exception Exception { get; init; } = default!;
|
||||
|
||||
internal ExceptionEncountered()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class UnexpectedErrorOccurred : PluginLoadOperation
|
||||
{
|
||||
public override string Description { get; }
|
||||
|
||||
internal UnexpectedErrorOccurred(string description)
|
||||
{
|
||||
this.Description = description;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,361 @@
|
||||
using Plum.Net.Callbacks;
|
||||
using Plum.Net.Exceptions;
|
||||
using Plum.Net.Models;
|
||||
using Plum.Net.Resolvers;
|
||||
using Plum.Net.Validators;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Reflection.PortableExecutable;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Plum.Net;
|
||||
|
||||
public sealed class PluginManager
|
||||
{
|
||||
private static readonly object Lock = new();
|
||||
|
||||
private readonly string pluginsBaseDirectory;
|
||||
private readonly List<IEnvironmentVersionValidator> environmentVersionValidators = new();
|
||||
private readonly List<IMetadataValidator> metadataValidators = new();
|
||||
private readonly List<ITypeDefinitionsValidator> typeDefinitionsValidators = new();
|
||||
private readonly List<IAssemblyLoadedCallback> assemblyLoadCallbacks = new();
|
||||
private readonly List<IDependencyResolver> dependencyResolvers = new();
|
||||
|
||||
private bool locked = false;
|
||||
private bool attachedDomainEvents = false;
|
||||
private bool forceLoadDependencies = true;
|
||||
|
||||
public PluginManager(string pluginsBaseDirectory)
|
||||
{
|
||||
this.pluginsBaseDirectory = Path.GetFullPath(pluginsBaseDirectory);
|
||||
}
|
||||
|
||||
public PluginManager WithEnvironmentVersionValidator(IEnvironmentVersionValidator environmentVersionValidator)
|
||||
{
|
||||
if (this.locked)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(PluginManager)} is in locked state and cannot be modified anymore");
|
||||
}
|
||||
|
||||
this.environmentVersionValidators.Add(environmentVersionValidator);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PluginManager WithMetadataValidator(IMetadataValidator metadataValidator)
|
||||
{
|
||||
if (this.locked)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(PluginManager)} is in locked state and cannot be modified anymore");
|
||||
}
|
||||
|
||||
this.metadataValidators.Add(metadataValidator);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PluginManager WithTypeDefinitionsValidator(ITypeDefinitionsValidator typeDefinitionsValidator)
|
||||
{
|
||||
if (this.locked)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(PluginManager)} is in locked state and cannot be modified anymore");
|
||||
}
|
||||
|
||||
this.typeDefinitionsValidators.Add(typeDefinitionsValidator);
|
||||
return this;
|
||||
}
|
||||
|
||||
public PluginManager WithAssemblyLoadedCallback(IAssemblyLoadedCallback assemblyLoadedCallback)
|
||||
{
|
||||
if (this.locked)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(PluginManager)} is in locked state and cannot be modified anymore");
|
||||
}
|
||||
|
||||
this.assemblyLoadCallbacks.Add(assemblyLoadedCallback);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a <see cref="IDependencyResolver"/> to the <see cref="PluginManager"/>. To ensure that this is called,
|
||||
/// call <see cref="WithForceLoadDependencies(bool)"/> with <see cref="true"/> parameter.
|
||||
/// </summary>
|
||||
/// <param name="dependencyResolver"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
public PluginManager WithDependencyResolver(IDependencyResolver dependencyResolver)
|
||||
{
|
||||
if (this.locked)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(PluginManager)} is in locked state and cannot be modified anymore");
|
||||
}
|
||||
|
||||
this.dependencyResolvers.Add(dependencyResolver);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When false, loading plugins might not trigger dependency resolvers, as the runtime loads dependencies lazily.
|
||||
/// By default, this value is true.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException"></exception>
|
||||
public PluginManager WithForceLoadDependencies(bool forceLoadDependencies)
|
||||
{
|
||||
if (this.locked)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(PluginManager)} is in locked state and cannot be modified anymore");
|
||||
}
|
||||
|
||||
this.forceLoadDependencies = forceLoadDependencies;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates over all the plugins found in the configured directory.
|
||||
/// </summary>
|
||||
/// <returns>Validated list of plugin entries that can be loaded by the plugin manager.</returns>
|
||||
public IEnumerable<PluginEntry> GetAvailablePlugins()
|
||||
{
|
||||
if (Directory.Exists(pluginsBaseDirectory))
|
||||
{
|
||||
foreach (var potentialPluginPath in Directory.GetFiles(pluginsBaseDirectory, "*.dll", SearchOption.AllDirectories))
|
||||
{
|
||||
if (this.ValidatePlugin(potentialPluginPath, out var plugin))
|
||||
{
|
||||
yield return plugin!;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads a list of plugins. This operation locks the <see cref="PluginManager"/> and no more validators/callbacks/resolvers can be added anymore.
|
||||
/// </summary>
|
||||
/// <returns>List of <see cref="PluginLoadOperation"/> results in the same order as the provided plugins.</returns>
|
||||
/// <exception cref="ArgumentNullException">Throws when provided plugins are null.</exception>
|
||||
public IEnumerable<PluginLoadOperation> LoadPlugins(IEnumerable<PluginEntry> plugins)
|
||||
{
|
||||
_ = plugins ?? throw new ArgumentNullException(nameof(plugins));
|
||||
try
|
||||
{
|
||||
while (!Monitor.TryEnter(Lock)) { }
|
||||
|
||||
this.locked = true;
|
||||
if (!this.attachedDomainEvents)
|
||||
{
|
||||
AppDomain.CurrentDomain.AssemblyLoad += this.AssemblyLoaded;
|
||||
AppDomain.CurrentDomain.AssemblyResolve += this.AssemblyResolve;
|
||||
this.attachedDomainEvents = true;
|
||||
}
|
||||
|
||||
var results = this.LoadPluginsInternal(plugins).ToList();
|
||||
return results;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Monitor.Exit(Lock);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<PluginLoadOperation> LoadPluginsInternal(IEnumerable<PluginEntry> plugins)
|
||||
{
|
||||
foreach (var plugin in plugins)
|
||||
{
|
||||
if (plugin is null)
|
||||
{
|
||||
yield return new PluginLoadOperation.NullEntry();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!File.Exists(plugin.Path))
|
||||
{
|
||||
yield return new PluginLoadOperation.FileNotFound { PluginEntry = plugin, Path = plugin.Path };
|
||||
continue;
|
||||
}
|
||||
|
||||
var success = false;
|
||||
PluginLoadException pluginLoadException = default!;
|
||||
Assembly assembly = default!;
|
||||
try
|
||||
{
|
||||
assembly = Assembly.LoadFrom(plugin.Path);
|
||||
if (this.forceLoadDependencies)
|
||||
{
|
||||
ForceLoadDependenciesOfLoadedAssembly(assembly);
|
||||
}
|
||||
|
||||
success = true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
success = false;
|
||||
pluginLoadException = new PluginLoadException("Failed to load plugin. Check inner exception for details", e, plugin);
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
if (assembly is not null)
|
||||
{
|
||||
yield return new PluginLoadOperation.Success { Plugin = new Plugin { Assembly = assembly, PluginEntry = plugin }, PluginEntry = plugin };
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return new PluginLoadOperation.UnexpectedErrorOccurred($"Plugin loaded without any error but no Assembly was obtained from {nameof(Assembly.LoadFrom)}") { PluginEntry = plugin };
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return new PluginLoadOperation.ExceptionEncountered { Exception = pluginLoadException!, PluginEntry = plugin };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AssemblyLoaded(object? _, AssemblyLoadEventArgs assemblyLoadEventArgs)
|
||||
{
|
||||
foreach (var callback in this.assemblyLoadCallbacks)
|
||||
{
|
||||
callback.AssemblyLoaded(assemblyLoadEventArgs.LoadedAssembly);
|
||||
}
|
||||
}
|
||||
|
||||
private Assembly AssemblyResolve(object? _, ResolveEventArgs resolveEventArgs)
|
||||
{
|
||||
foreach (var resolver in this.dependencyResolvers)
|
||||
{
|
||||
if (resolver.TryResolveDependency(resolveEventArgs.RequestingAssembly, resolveEventArgs.Name, out var assemblyPath) &&
|
||||
assemblyPath is string resolvedPath)
|
||||
{
|
||||
return Assembly.LoadFrom(resolvedPath);
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Unable to resolve dependency {resolveEventArgs.Name}. Requesting assembly: {resolveEventArgs.RequestingAssembly}");
|
||||
}
|
||||
|
||||
private bool ValidatePlugin(string path, out PluginEntry? plugin)
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
plugin = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
var pluginName = Path.GetFileNameWithoutExtension(path);
|
||||
plugin = new PluginEntry { Name = pluginName, Path = path };
|
||||
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
|
||||
using var peReader = new PEReader(stream);
|
||||
var metadataReader = peReader.GetMetadataReader();
|
||||
|
||||
if (!this.IsValidPlugin(metadataReader))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.IsTargetingCorrectDotnetVersion(metadataReader))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var typeDefinitions = metadataReader.TypeDefinitions.Select(metadataReader.GetTypeDefinition);
|
||||
if (!this.HasValidTypeDefinitions(typeDefinitions, metadataReader))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsTargetingCorrectDotnetVersion(MetadataReader metadataReader)
|
||||
{
|
||||
foreach (var referenceHandle in metadataReader.AssemblyReferences)
|
||||
{
|
||||
var reference = metadataReader.GetAssemblyReference(referenceHandle);
|
||||
var name = metadataReader.GetString(reference.Name);
|
||||
|
||||
if (name.Equals("System.Runtime", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
/*
|
||||
* If any validator rejects the plugin version, reject the plugin.
|
||||
* Otherwise, accept the plugin.
|
||||
*/
|
||||
var pluginVersion = reference.Version;
|
||||
var currentVersion = Environment.Version;
|
||||
foreach(var validator in this.environmentVersionValidators)
|
||||
{
|
||||
if (!validator.Validate(currentVersion, pluginVersion))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool HasValidTypeDefinitions(IEnumerable<TypeDefinition> typeDefinitions, MetadataReader metadataReader)
|
||||
{
|
||||
foreach(var validator in this.typeDefinitionsValidators)
|
||||
{
|
||||
if (!validator.Validate(typeDefinitions, metadataReader))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsValidPlugin(MetadataReader metadataReader)
|
||||
{
|
||||
foreach(var validator in this.metadataValidators)
|
||||
{
|
||||
if (!validator.Validate(metadataReader))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void ForceLoadDependenciesOfLoadedAssembly(Assembly assembly)
|
||||
{
|
||||
foreach (var type in assembly.GetTypes())
|
||||
{
|
||||
// We'll force JIT compilation of all the methods, constructors and property setter/getter, which in turn forces dependencies to load
|
||||
foreach (var method in type.GetMethods(BindingFlags.DeclaredOnly |
|
||||
BindingFlags.Public |
|
||||
BindingFlags.NonPublic |
|
||||
BindingFlags.Instance |
|
||||
BindingFlags.Static))
|
||||
{
|
||||
RuntimeHelpers.PrepareMethod(method.MethodHandle);
|
||||
}
|
||||
|
||||
foreach(var constructor in type.GetConstructors())
|
||||
{
|
||||
RuntimeHelpers.PrepareMethod(constructor.MethodHandle);
|
||||
}
|
||||
|
||||
foreach (var property in type.GetProperties(BindingFlags.Public |
|
||||
BindingFlags.NonPublic |
|
||||
BindingFlags.Instance |
|
||||
BindingFlags.Static))
|
||||
{
|
||||
MethodInfo? getMethod = property.GetGetMethod(nonPublic: true);
|
||||
MethodInfo? setMethod = property.GetSetMethod(nonPublic: true);
|
||||
|
||||
if (getMethod is not null)
|
||||
{
|
||||
RuntimeHelpers.PrepareMethod(getMethod.MethodHandle);
|
||||
}
|
||||
|
||||
if (setMethod is not null)
|
||||
{
|
||||
RuntimeHelpers.PrepareMethod(setMethod.MethodHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Authors>Alexandru Macocian</Authors>
|
||||
<Company />
|
||||
<Product></Product>
|
||||
<Description>Plugin management library.</Description>
|
||||
<AssemblyVersion>1.0</AssemblyVersion>
|
||||
<FileVersion>1.0</FileVersion>
|
||||
<PackageProjectUrl>https://github.com/AlexMacocian/Plum</PackageProjectUrl>
|
||||
<Version>1.0</Version>
|
||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Reflection.Metadata" Version="7.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\LICENSE">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath></PackagePath>
|
||||
</None>
|
||||
<None Include="..\README.md" Link="README.md" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,8 @@
|
||||
using System.Reflection;
|
||||
|
||||
namespace Plum.Net.Resolvers;
|
||||
|
||||
public interface IDependencyResolver
|
||||
{
|
||||
bool TryResolveDependency(Assembly? requestingAssembly, string dependencyName, out string? path);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Plum.Net.Validators;
|
||||
|
||||
public interface IEnvironmentVersionValidator
|
||||
{
|
||||
/// <summary>
|
||||
/// Validate the version of .net of the plugin in comparison to the version of .net of the running assembly.
|
||||
/// </summary>
|
||||
/// <param name="currentVersion">.net version of the currently running assembly</param>
|
||||
/// <param name="pluginVersion">.net version of the plugin</param>
|
||||
/// <returns></returns>
|
||||
bool Validate(Version currentVersion, Version pluginVersion);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Reflection.Metadata;
|
||||
|
||||
namespace Plum.Net.Validators;
|
||||
|
||||
/// <summary>
|
||||
/// A general validator that validates a plugin based on the entire metadata
|
||||
/// </summary>
|
||||
public interface IMetadataValidator
|
||||
{
|
||||
bool Validate(MetadataReader metadataReader);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Reflection.Metadata;
|
||||
|
||||
namespace Plum.Net.Validators;
|
||||
|
||||
/// <summary>
|
||||
/// A validator that validates a plugin based on the TypeDefinitions it contains.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Useful when you want to validate if the plugin contains a certain type that may implement a base type, in order to determine the entry point of the plugin.
|
||||
/// </remarks>
|
||||
public interface ITypeDefinitionsValidator
|
||||
{
|
||||
bool Validate(IEnumerable<TypeDefinition> typeDefinitions, MetadataReader metadataReader);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
# Plumsy
|
||||
## Plugin management library
|
||||
Plumsy is a library for plugin management in C#. It is capable of inspecting and validating dlls, before loading them into the current AppDomain.
|
||||
|
||||
Plumsy offers a set of extensions and callbacks to allow the caller to configure what plugins to load, how to handle plugin dependencies and runtime differences.
|
||||
|
||||
## Examples
|
||||
|
||||
### Create an instance of PluginManager
|
||||
```C#
|
||||
var pluginManager = new PluginManager(pathToPlugins);
|
||||
````
|
||||
|
||||
### Retrieve a list of available plugins
|
||||
```C#
|
||||
var availablePlugins = pluginManager.GetAvailablePlugins();
|
||||
```
|
||||
Plumsy will call the validators for each of the dlls found in the `pathToPlugins` and validate the metadata of those dlls. If the dlls pass the validation, they are marked as available and returned by the above call.
|
||||
|
||||
### Load plugins into the current AppDomain
|
||||
```C#
|
||||
var results = pluginManager.LoadPlugins(availablePlugins);
|
||||
```
|
||||
Plumsy will attempt to load the provided list of plugin entries into the current AppDomain. `results` is a list of `PluginLoadOperation`, the type depending on the result of the load operation.
|
||||
- `PluginLoadOperation.Success` contains a reference to the loaded assembly and implies that the plugin has been loaded successfully.
|
||||
- `PluginLoadOperation.NullEntry` implies that the plugin entry was null.
|
||||
- `PluginLoadOperation.FileNotFound` implies that the plugin path was not found.
|
||||
- `PluginLoadOperation.ExceptionEncountered` implies that the plugin failed to load due to an exception. Check the inner `Exception` property for details.
|
||||
- `PluginLoadOperation.UnexpectedErrorOccurred` implies that an unexpected error occurred. This happens when the `Assembly.Load` call succeeds but the returned assembly is `null`.
|
||||
|
||||
### Setup validators
|
||||
```C#
|
||||
pluginManager
|
||||
.WithEnvironmentVersionValidator(environmentVersionValidator)
|
||||
.WithMetadataValidator(metadataValidator)
|
||||
.WithTypeDefinitionsValidator(typeDefinitionsValidator);
|
||||
```
|
||||
- `environmentVersionValidator` is of type `IEnvironmentVersionValidator`. This validator is used to validate the `Version` of the current runtime in comparison to the plugin target runtime.
|
||||
- `metadataValidator` is of type `IMetadataValidator`. This validator receives a reference to the `MetadataReader` and can perform general validations over the plugin.
|
||||
- `typeDefinitionsValidator` is of type `ITypeDefinitionsValidator`. This validator receives a reference to the `MetadataReader` as well as a list of `Type`s in order to perform validations over the plugin. This is useful when wanting to validate that the plugin contains a specific entry point class of a known type.
|
||||
|
||||
### Callbacks
|
||||
```C#
|
||||
pluginManager
|
||||
.WithAssemblyLoadedCallback(assemblyLoadedCallback);
|
||||
```
|
||||
- `assemblyLoadedCallback` is of type `IAssemblyLoadedCallback`. This callback is called after an assembly has been loaded into the current AppDomain.
|
||||
|
||||
### Dependency Resolving
|
||||
```C#
|
||||
pluginManager
|
||||
.WithDependencyResolver(dependencyResolver);
|
||||
```
|
||||
- `dependencyResolver` is of type `IDependencyResolver`. The dependency solver is called when the runtime is unable to find some dependencies of the currently loading plugin. *!! Due to the runtime lazily loading types, dependencies might only be resolved when calling specific methods !!*
|
||||
|
||||
```C#
|
||||
pluginManager
|
||||
.WithForceLoadDependencies(true|false);
|
||||
```
|
||||
- When forcing the Plumsy to load dependencies, Plumsy will attempt to manually cause JIT-ing of the methods in the loaded assembly. This would in turn cause the runtime to load its dependencies and would trigger `IDependencyResolver.TryResolveDependency` in cases where a dependency is not found.
|
||||
- By default, Plumsy forces dependencies to load on assembly load. If you want instead to lazily load dependencies, call `pluginManager.WithForceLoadDependencies(false)`.
|
||||
Reference in New Issue
Block a user