Skip to content

feat(plugin): add SquidStd.Plugin loader package#65

Merged
tgiachi merged 9 commits into
developfrom
feature/plugin-loader
Jul 6, 2026
Merged

feat(plugin): add SquidStd.Plugin loader package#65
tgiachi merged 9 commits into
developfrom
feature/plugin-loader

Conversation

@tgiachi

@tgiachi tgiachi commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Summary

  • New package SquidStd.Plugin: the loader for SquidStd.Plugin.Abstractions. Single entry point on the bootstrap:
    bootstrap.UsePlugins(plugins =>
    {
        plugins.Add<WebPlugin>();          // internal, by type
        plugins.Add(new MetricsPlugin());  // internal, by instance
        plugins.FromDirectory("plugins");  // external assemblies (*.dll)
    });
  • Internal and external plugins are dependency-sorted together (PluginMetadata.Dependencies, case-insensitive, stable topological sort with actionable errors for duplicates, missing dependencies and cycles) and each Configure(container, context) runs before the bootstrap starts, hence before the configuration load - plugins can register config sections, services and Lua modules.
  • External assemblies load into the default AssemblyLoadContext (trusted plugins: no unload, no version isolation - documented in XML docs and site docs). Everything fails fast with PluginLoadException.
  • PluginContext now carries standard keys (PluginContextKeys.RootDirectory/AppName).
  • Prerequisite: BootstrapStateType is now public in SquidStd.Core.Types.Bootstrap and ISquidStdBootstrap exposes read-only State (used by the pre-start guard).
  • Sample SquidStd.Samples.Plugins boots through the loader; new docs article "SquidStd.Plugin" + toc + READMEs.

Test plan

  • 21 new tests (state transitions, builder, resolver incl. cycle/dup/missing-dep, scanner with Roslyn-emitted assemblies at run time, UsePlugins end-to-end on the real bootstrap)
  • All non-Docker tests green locally (1125/1125; the 41 Testcontainers tests are broken machine-wide by a host kernel upgrade pending reboot - unrelated, CI validates the full suite)
  • Release builds clean (XML doc validation)

tgiachi added 9 commits July 6, 2026 10:06
Moves BootstrapStateType from SquidStd.Services.Core (internal) to
SquidStd.Core.Types.Bootstrap (public) and adds a read-only State
property so external packages can guard pre-start-only operations.
… context keys

New loader package skeleton referencing Core and Plugin.Abstractions:
PluginCollectionBuilder (internal plugins by type or instance, external
directories), PluginLoadException, standard PluginContextKeys, and
host-agnostic wording in the abstractions docs.
Topological sort across the whole plugin set with fail-fast errors for
duplicate ids, missing dependencies, and cycles (the message names the
cycle members). Plugins with no dependency relation keep registration
order.
Scans a directory for *.dll files, loads them into the default
AssemblyLoadContext (trusted plugins, shared type identity), and
instantiates every concrete ISquidStdPlugin. Assemblies without plugins
are skipped with a debug log; load and instantiation failures and a
missing directory fail fast with PluginLoadException. Tests emit plugin
assemblies at run time with Roslyn, so no binary fixtures are committed.
UsePlugins collects internal and external plugins, sorts them across the
whole set, and configures each against the bootstrap container before
startup, populating the standard PluginContext keys. Guarded to the
Created bootstrap state. The plugins sample now boots through the loader.
Package README with the UsePlugins example, loading rules, context keys
and the trusted-plugin contract; docs article and toc entry; pointer from
the abstractions README.
Comment on lines +53 to +60
foreach (var directory in builder.Directories)
{
var resolved = Path.IsPathRooted(directory)
? directory
: Path.Combine(bootstrap.Options.RootDirectory, directory);

plugins.AddRange(PluginAssemblyScanner.Scan(resolved));
}
{
var resolved = Path.IsPathRooted(directory)
? directory
: Path.Combine(bootstrap.Options.RootDirectory, directory);
Comment on lines +50 to +53
catch (Exception ex)
{
throw new PluginLoadException($"Failed to load plugin assembly '{dllPath}'.", ex);
}
Comment on lines +68 to +74
catch (Exception ex)
{
throw new PluginLoadException(
$"Failed to instantiate plugin type '{pluginType.FullName}' from '{dllPath}'.",
ex
);
}
Comment on lines +17 to +23
foreach (var plugin in plugins)
{
if (!byId.TryAdd(plugin.Metadata.Id, plugin))
{
throw new PluginLoadException($"Duplicate plugin id '{plugin.Metadata.Id}'.");
}
}
[Fact]
public void Scan_MissingDirectory_Throws()
{
var missing = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N"));

try
{
File.WriteAllBytes(Path.Combine(directory.FullName, "broken.dll"), [1, 2, 3, 4]);
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
);

var path = Path.Combine(directory, fileName);
@tgiachi
tgiachi merged commit 3717bee into develop Jul 6, 2026
3 checks passed
@tgiachi
tgiachi deleted the feature/plugin-loader branch July 6, 2026 09:24
@tgiachi tgiachi mentioned this pull request Jul 6, 2026
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants