diff --git a/src/AcDream.Core/Plugins/LoadedPlugin.cs b/src/AcDream.Core/Plugins/LoadedPlugin.cs index ee20c14..a3f6d24 100644 --- a/src/AcDream.Core/Plugins/LoadedPlugin.cs +++ b/src/AcDream.Core/Plugins/LoadedPlugin.cs @@ -1,11 +1,20 @@ +using System.Runtime.Loader; using AcDream.Plugin.Abstractions; namespace AcDream.Core.Plugins; +/// +/// Outcome of a plugin load attempt. +/// On success, is the instantiated plugin, +/// owns its assembly, and is null. +/// On failure, and are null and +/// describes what went wrong. +/// public sealed record LoadedPlugin( PluginManifest Manifest, IAcDreamPlugin? Plugin, - string? Error) + AssemblyLoadContext? LoadContext, + Exception? Error) { public bool Success => Plugin is not null && Error is null; } diff --git a/src/AcDream.Core/Plugins/PluginAssemblyLoadContext.cs b/src/AcDream.Core/Plugins/PluginAssemblyLoadContext.cs index 9fbc42c..6ce3968 100644 --- a/src/AcDream.Core/Plugins/PluginAssemblyLoadContext.cs +++ b/src/AcDream.Core/Plugins/PluginAssemblyLoadContext.cs @@ -10,6 +10,8 @@ namespace AcDream.Core.Plugins; /// internal sealed class PluginAssemblyLoadContext : AssemblyLoadContext { + private const string AbstractionsAssemblyName = "AcDream.Plugin.Abstractions"; + private readonly AssemblyDependencyResolver _resolver; public PluginAssemblyLoadContext(string pluginDirectory, string pluginEntryPath) @@ -21,7 +23,7 @@ internal sealed class PluginAssemblyLoadContext : AssemblyLoadContext protected override Assembly? Load(AssemblyName assemblyName) { // Share the abstractions assembly with the host — do NOT reload it in the plugin ALC - if (assemblyName.Name == "AcDream.Plugin.Abstractions") + if (assemblyName.Name == AbstractionsAssemblyName) return null; var path = _resolver.ResolveAssemblyToPath(assemblyName); diff --git a/src/AcDream.Core/Plugins/PluginLoader.cs b/src/AcDream.Core/Plugins/PluginLoader.cs index d3743e6..ba2ba07 100644 --- a/src/AcDream.Core/Plugins/PluginLoader.cs +++ b/src/AcDream.Core/Plugins/PluginLoader.cs @@ -5,30 +5,56 @@ namespace AcDream.Core.Plugins; public static class PluginLoader { + /// + /// Load a plugin DLL from into a collectible + /// , find the first type + /// implementing , instantiate it, and call its + /// with the supplied host. Any failure + /// is returned as a failed rather than thrown. + /// public static LoadedPlugin Load(string pluginDirectory, PluginManifest manifest, IPluginHost host) { var dllPath = Path.Combine(pluginDirectory, manifest.EntryDll); if (!File.Exists(dllPath)) - return new LoadedPlugin(manifest, null, $"entry dll not found: {dllPath}"); + return new LoadedPlugin( + manifest, + Plugin: null, + LoadContext: null, + Error: new FileNotFoundException($"entry dll not found: {dllPath}", dllPath)); try { var alc = new PluginAssemblyLoadContext(pluginDirectory, dllPath); var asm = alc.LoadFromAssemblyPath(dllPath); - var pluginType = asm.GetTypes() + IEnumerable types; + try + { + types = asm.GetTypes(); + } + catch (ReflectionTypeLoadException rtle) + { + types = rtle.Types.OfType(); + } + + var pluginType = types .FirstOrDefault(t => !t.IsAbstract && typeof(IAcDreamPlugin).IsAssignableFrom(t)); if (pluginType is null) - return new LoadedPlugin(manifest, null, $"no IAcDreamPlugin implementation found in {manifest.EntryDll}"); + return new LoadedPlugin( + manifest, + Plugin: null, + LoadContext: null, + Error: new InvalidOperationException( + $"no IAcDreamPlugin implementation found in {manifest.EntryDll}")); var instance = (IAcDreamPlugin)Activator.CreateInstance(pluginType)!; instance.Initialize(host); - return new LoadedPlugin(manifest, instance, null); + return new LoadedPlugin(manifest, instance, alc, Error: null); } catch (Exception ex) { - return new LoadedPlugin(manifest, null, $"{ex.GetType().Name}: {ex.Message}"); + return new LoadedPlugin(manifest, Plugin: null, LoadContext: null, Error: ex); } } } diff --git a/tests/AcDream.Core.Tests/Plugins/PluginLoaderTests.cs b/tests/AcDream.Core.Tests/Plugins/PluginLoaderTests.cs index 03a07c7..5947e55 100644 --- a/tests/AcDream.Core.Tests/Plugins/PluginLoaderTests.cs +++ b/tests/AcDream.Core.Tests/Plugins/PluginLoaderTests.cs @@ -97,6 +97,6 @@ public class PluginLoaderTests var loaded = PluginLoader.Load(coreDllDir, manifest, host); Assert.False(loaded.Success); - Assert.Contains("IAcDreamPlugin", loaded.Error ?? ""); + Assert.Contains("IAcDreamPlugin", loaded.Error!.Message); } }