Plugins have their own AppDomain now (TODO: dispose on plugin unload)

This commit is contained in:
SpoinkyNL committed 2019-04-13 12:13:44 +02:00
1 parent 3689db9325
commit dba48a2b6a
18 files changed
+118 -69

No files matched your search

+3
View File
@@ -51,6 +51,9 @@
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="AppDomainToolkit, Version=1.0.4.1, Culture=neutral, PublicKeyToken=f2fc7ab5180cf5c4, processorArchitecture=MSIL">
<HintPath>..\packages\AppDomainToolkit.1.0.4.3\lib\net\AppDomainToolkit.dll</HintPath>
</Reference>
<Reference Include="Castle.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">
<HintPath>..\packages\Castle.Core.4.4.0\lib\net45\Castle.Core.dll</HintPath>
</Reference>
@@ -18,7 +18,7 @@ namespace Artemis.Core.Plugins.Abstract
public abstract bool ExpandsMainDataModel { get; }
/// <inheritdoc />
public void LoadPlugin()
public void EnablePlugin()
{
// Load and activate the last active profile
}
@@ -10,6 +10,6 @@ namespace Artemis.Core.Plugins.Interfaces
/// <summary>
/// Called when the plugin is loaded
/// </summary>
void LoadPlugin();
void EnablePlugin();
}
}
+14 -8
View File
@@ -1,5 +1,5 @@
using System;
using System.Collections.Generic;
using AppDomainToolkit;
using Artemis.Core.Plugins.Interfaces;
using Newtonsoft.Json;
@@ -7,11 +7,6 @@ namespace Artemis.Core.Plugins.Models
{
public class PluginInfo
{
public PluginInfo()
{
Instances = new List<IPlugin>();
}
/// <summary>
/// The plugins GUID
/// </summary>
@@ -39,10 +34,21 @@ namespace Artemis.Core.Plugins.Models
public string Folder { get; set; }
/// <summary>
/// A references to the types implementing IPlugin, available after successful load
/// A reference to the type implementing IPlugin, available after successful load
/// </summary>
[JsonIgnore]
public List<IPlugin> Instances { get; set; }
public IPlugin Instance { get; set; }
/// <summary>
/// Indicates whether the user enabled the plugin or not
/// </summary>
[JsonIgnore]
public bool Enabled { get; set; }
/// <summary>
/// The AppDomain context of this plugin
/// </summary>
internal AppDomainContext<AssemblyTargetLoader, PathBasedAssemblyResolver> Context { get; set; }
public override string ToString()
{
+1 -1
View File
@@ -48,7 +48,7 @@ namespace Artemis.Core.Services
{
try
{
var modules = _pluginService.Plugins.SelectMany(p => p.Instances).OfType<IModule>().ToList();
var modules = _pluginService.GetModules();
// Update all active modules
foreach (var module in modules)
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using Artemis.Core.Events;
@@ -45,5 +46,11 @@ namespace Artemis.Core.Services.Interfaces
/// <param name="layerTypeGuid">The GUID of the layer type to find</param>
/// <returns>An instance of the layer type</returns>
ILayerType GetLayerTypeByGuid(Guid layerTypeGuid);
/// <summary>
/// Returns all the plugins implementing <see cref="IModule" />
/// </summary>
/// <returns></returns>
IReadOnlyList<IModule> GetModules();
}
}
+40 -21
View File
@@ -3,8 +3,8 @@ using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using AppDomainToolkit;
using Artemis.Core.Events;
using Artemis.Core.Exceptions;
using Artemis.Core.Plugins.Exceptions;
@@ -53,7 +53,6 @@ namespace Artemis.Core.Services
// Load the plugin assemblies into the plugin context
var directory = new DirectoryInfo(Path.Combine(Constants.DataFolder, "plugins"));
foreach (var subDirectory in directory.EnumerateDirectories())
{
try
{
// Load the metadata
@@ -63,42 +62,58 @@ namespace Artemis.Core.Services
// Locate the main entry
var pluginInfo = JsonConvert.DeserializeObject<PluginInfo>(File.ReadAllText(metadataFile));
// TODO Just temporarily until settings are in place
pluginInfo.Enabled = true;
var mainFile = Path.Combine(subDirectory.FullName, pluginInfo.Main);
if (!File.Exists(mainFile))
throw new ArtemisPluginException(pluginInfo, "Couldn't find the plugins main entry at " + mainFile);
// Load the plugin, all types implementing IPlugin and register them with DI
Assembly assembly;
var setupInfo = new AppDomainSetup
{
ApplicationName = pluginInfo.Guid.ToString(),
ApplicationBase = AppDomain.CurrentDomain.BaseDirectory,
PrivateBinPath = subDirectory.FullName
};
pluginInfo.Context = AppDomainContext.Create(setupInfo);
try
{
assembly = Assembly.LoadFile(mainFile);
pluginInfo.Context.LoadAssemblyWithReferences(LoadMethod.LoadFrom, mainFile);
}
catch (Exception e)
{
throw new ArtemisPluginException(pluginInfo, "Failed to load the plugins assembly", e);
}
var pluginTypes = assembly.GetTypes().Where(t => typeof(IPlugin).IsAssignableFrom(t)).ToArray();
foreach (var pluginType in pluginTypes)
{
_childKernel.Bind<IPlugin>().To(pluginType).InSingletonScope();
try
{
pluginInfo.Instances.Add((IPlugin) _childKernel.Get(pluginType));
}
catch (Exception e)
{
throw new ArtemisPluginException(pluginInfo, "Failed to instantiate the plugin", e);
}
}
// Get the IPlugin implementation from the main assembly and if there is only one, instantiate it
var mainAssembly = pluginInfo.Context.Domain.GetAssemblies().First(a => a.Location == mainFile);
var pluginTypes = mainAssembly.GetTypes().Where(t => typeof(IPlugin).IsAssignableFrom(t)).ToList();
if (pluginTypes.Count > 1)
throw new ArtemisPluginException(pluginInfo, $"Plugin contains {pluginTypes.Count} implementations of IPlugin, only 1 allowed");
if (pluginTypes.Count == 0)
throw new ArtemisPluginException(pluginInfo, "Plugin contains no implementation of IPlugin");
var pluginType = pluginTypes.Single();
_childKernel.Bind<IPlugin>().To(pluginType).InSingletonScope();
try
{
pluginInfo.Instance = (IPlugin) _childKernel.Get(pluginType);
}
catch (Exception e)
{
throw new ArtemisPluginException(pluginInfo, "Failed to instantiate the plugin", e);
}
_plugins.Add(pluginInfo);
}
catch (Exception e)
{
throw new ArtemisPluginException("Failed to load plugin", e);
}
}
// Activate plugins after they are all loaded
foreach (var pluginInfo in _plugins.Where(p => p.Enabled))
pluginInfo.Instance.EnablePlugin();
});
OnFinishedLoadedPlugins();
@@ -111,11 +126,15 @@ namespace Artemis.Core.Services
if (pluginInfo == null)
return null;
var layerType = pluginInfo.Instances.SingleOrDefault(p => p is ILayerType);
if (layerType == null)
if (!(pluginInfo.Instance is ILayerType layerType))
throw new ArtemisPluginException(pluginInfo, "Plugin is expected to implement exactly one ILayerType");
return (ILayerType) layerType;
return layerType;
}
public IReadOnlyList<IModule> GetModules()
{
return Plugins.Where(p => p.Instance is IModule).Select(p => (IModule) p.Instance).ToList();
}
public void Dispose()
+5 -10
View File
@@ -28,8 +28,7 @@
<bindingRedirect oldVersion="0.0.0.0-4.0.3.0" newVersion="4.0.3.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Security.Cryptography.Primitives" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral" />
<assemblyIdentity name="System.Security.Cryptography.Primitives" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.2.0" newVersion="4.0.2.0" />
</dependentAssembly>
<dependentAssembly>
@@ -53,8 +52,7 @@
<bindingRedirect oldVersion="0.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.CodeAnalysis.CSharp.Scripting" publicKeyToken="31bf3856ad364e35"
culture="neutral" />
<assemblyIdentity name="Microsoft.CodeAnalysis.CSharp.Scripting" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
</dependentAssembly>
<dependentAssembly>
@@ -70,8 +68,7 @@
<bindingRedirect oldVersion="0.0.0.0-2.6.0.0" newVersion="2.6.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Security.Cryptography.Algorithms" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral" />
<assemblyIdentity name="System.Security.Cryptography.Algorithms" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.3.0.0" newVersion="4.3.0.0" />
</dependentAssembly>
<dependentAssembly>
@@ -87,8 +84,7 @@
<bindingRedirect oldVersion="0.0.0.0-2.2.0.0" newVersion="2.2.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral" />
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.4.1" newVersion="4.0.4.1" />
</dependentAssembly>
<dependentAssembly>
@@ -96,8 +92,7 @@
<bindingRedirect oldVersion="0.0.0.0-4.2.1.0" newVersion="4.2.1.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Diagnostics.DiagnosticSource" publicKeyToken="cc7b13ffcd2ddd51"
culture="neutral" />
<assemblyIdentity name="System.Diagnostics.DiagnosticSource" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.3.1" newVersion="4.0.3.1" />
</dependentAssembly>
<dependentAssembly>
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="AppDomainToolkit" version="1.0.4.3" targetFramework="net461" />
<package id="Castle.Core" version="4.4.0" targetFramework="net461" />
<package id="Newtonsoft.Json" version="12.0.1" targetFramework="net461" />
<package id="Ninject" version="3.3.4" targetFramework="net461" />