Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 37 additions & 7 deletions docs/guide/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,10 +246,6 @@ await app.Services.ApplyAsyncWolverineExtensions();

## Wolverine Plugin Modules

::: warning
This functionality will likely be eliminated in Wolverine 3.0.
:::

::: tip
Use this sparingly, but it might be advantageous for adding extra instrumentation or extra middleware
:::
Expand All @@ -269,11 +265,45 @@ with this attribute to automatically add that extension to Wolverine:
<sup><a href='https://github.com/JasperFx/wolverine/blob/main/src/Testing/Module1/Properties/AssemblyInfo.cs#L29-L32' title='Snippet source file'>snippet source</a> | <a href='#snippet-sample_using_wolverine_module_to_load_extension' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->

You can also use the non-generic `[assembly: WolverineModule]` form to mark an assembly purely as a *handler*
module — its handlers are discovered, but no `IWolverineExtension` is applied.

### How discovery works (source generator, not runtime scanning)

::: tip
Module discovery moved to a compile-time source generator in Wolverine 6.0 (GH-2902). Earlier versions
probed the application's bin directory at startup (`AssemblyFinder`), which was slower and not AOT/trim-friendly.
:::

The `[WolverineModule]` and `[WolverineModule<T>]` attributes both derive from JasperFx's `[JasperFxAssembly]`
attribute. The **`JasperFx.SourceGenerator`** analyzer — which the `WolverineFx` package flows transitively to
every project that references it (directly or through any Wolverine extension/transport package) — sees these
assembly attributes at **compile time** and emits a small `JasperFx.Generated.DiscoveredExtensions` manifest into
the assembly listing the declared extension type(s). At startup Wolverine reads those generated manifests from the
loaded assemblies instead of scanning the filesystem and speculatively `Assembly.Load`-ing candidates.

A few consequences worth knowing:

* **Only the type declared in `[WolverineModule<T>]` (or `[WolverineModule(typeof(T))]`) is auto-applied.** Other
`IWolverineExtension` implementations sitting in the same assembly are *not* activated automatically — register
those explicitly with `opts.Include<T>()` or `services.AddWolverineExtension<T>()`.
* The extension assembly must be **compiled against WolverineFx 6.0+** so that the analyzer runs and emits its
manifest. A module built against an older Wolverine will not be auto-discovered; reference it and call its
configuration explicitly instead.
* Wolverine walks the application's **reference graph** to make sure referenced module assemblies (for example
`WolverineFx.RuntimeCompilation`) are loaded before reading their manifests, so "reference the package and it
just activates" still works. This deliberately does *not* glob the bin directory the way the old scan did.

## Disabling Assembly Scanning

Some Wolverine users have seen rare issues with the assembly scanning cratering an application with out of memory
exceptions in the case of an application directory being the same as the root of a Docker container. *If* you experience
that issue, or just want a faster start up time, you can disable the automatic extension discovery using this syntax:
::: info
As of Wolverine 6.0 there is no runtime bin-directory assembly scan for extensions — discovery is driven by the
compile-time manifest described above. `ExtensionDiscovery.ManualOnly` remains the switch to turn automatic module
discovery off entirely.
:::

If you want a marginally faster start up, or you simply want full control over which extensions are applied, you can
disable automatic extension discovery (the reference-graph walk and manifest read) with this syntax:

<!-- snippet: sample_disabling_assembly_scanning -->
<a id='snippet-sample_disabling_assembly_scanning'></a>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using System.Linq;
using JasperFx;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Module1;
using Wolverine.ComplianceTests;
using Wolverine.Tracking;
using Xunit;

namespace CoreTests.Configuration;

// Auto-discovery of [WolverineModule<T>] extensions now flows through the compile-time manifest
// emitted by JasperFx.SourceGenerator (the JasperFx.Generated.DiscoveredExtensions class) instead
// of the old runtime ExtensionLoader + AssemblyFinder filesystem scan. See GH-2902.
public class extension_discovery_via_manifest
{
private static Task<IHost> startHostAsync()
{
// Default ExtensionDiscovery.Automatic; conventional *handler* discovery is off only to keep
// the host light — it does not affect extension discovery.
return Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder()
.UseWolverine(opts => opts.DisableConventionalDiscovery())
.StartAsync();
}

[Fact]
public async Task declared_module_extension_is_discovered_from_the_manifest_and_applied()
{
using var host = await startHostAsync();

// Module1 declares [assembly: WolverineModule<Module1Extension>]; Module1Extension.Configure
// registers IModuleService. Its presence proves the manifest-driven discovery applied it.
host.GetRuntime().Options.AppliedExtensions
.ShouldContain(x => x is Module1Extension);

host.Services.GetRequiredService<IServiceContainer>()
.HasRegistrationFor<IModuleService>().ShouldBeTrue();
}

[Fact]
public async Task framework_internal_extensions_are_never_auto_applied()
{
using var host = await startHostAsync();
var options = host.GetRuntime().Options;

// The manifest's marker-interface scan also lists Wolverine's own internal IWolverineExtension
// helpers (these are applied explicitly / via DI, never auto-discovered). Discovery is gated to
// each assembly's declared [WolverineModule] type, so none of them may leak in here. Auto-applying
// DisableExternalTransports in particular would silently stub every external transport.
options.ExternalTransportsAreStubbed.ShouldBeFalse();

var applied = options.AppliedExtensions.Select(x => x.GetType().Name).ToArray();
applied.ShouldNotContain("DisableExternalTransports");
applied.ShouldNotContain("DisablePersistence");
applied.ShouldNotContain("UseSoloDurabilityMode");
applied.ShouldNotContain("LambdaWolverineExtension");
}
}
100 changes: 0 additions & 100 deletions src/Wolverine/ExtensionLoader.cs

This file was deleted.

2 changes: 1 addition & 1 deletion src/Wolverine/HostBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ internal static IServiceCollection AddWolverine(this IServiceCollection services
options.Services = services;
if (discovery == ExtensionDiscovery.Automatic)
{
ExtensionLoader.ApplyExtensions(options);
options.DiscoverAndApplyExtensions();
}

if (options.ApplicationAssembly != null)
Expand Down
6 changes: 4 additions & 2 deletions src/Wolverine/IWolverineExtension.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using JasperFx;

namespace Wolverine;

#region sample_iwolverineextension
/// <summary>
/// Use to create loadable extensions to Wolverine applications
/// </summary>
public interface IWolverineExtension
public interface IWolverineExtension : IJasperFxExtension
{
/// <summary>
/// Make any alterations to the WolverineOptions for the application
Expand All @@ -19,7 +21,7 @@ public interface IWolverineExtension
/// Loadable extensions to Wolverine applications that may require
/// IoC services or asynchronous operations
/// </summary>
public interface IAsyncWolverineExtension
public interface IAsyncWolverineExtension : IJasperFxExtension
{
ValueTask Configure(WolverineOptions options);
}
Expand Down
14 changes: 11 additions & 3 deletions src/Wolverine/Wolverine.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,17 @@
<!-- JasperFx.RuntimeCompiler (Roslyn) is intentionally NOT referenced by core
WolverineFx. It ships only in the opt-in WolverineFx.RuntimeCompilation package
so TypeLoadMode.Static / AOT apps don't carry Roslyn. See #2876. -->
<PackageReference Include="JasperFx.SourceGenerator"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
<!--
JasperFx.SourceGenerator hosts the ExtensionDiscoveryGenerator that emits the
compile-time JasperFx.Generated.DiscoveredExtensions manifest Wolverine reads at
startup instead of scanning the filesystem for [WolverineModule] assemblies (#2902).
PrivateAssets="none" flows the analyzer transitively to every WolverineFx consumer —
the app's own executable assembly and every extension/transport assembly — so they
all emit their manifest. The generator self-gates to eligible assemblies
([JasperFxAssembly]/[WolverineModule]-marked or executable), so it is a no-op anywhere
it has nothing to emit.
-->
<PackageReference Include="JasperFx.SourceGenerator" PrivateAssets="none" />
</ItemGroup>

<ItemGroup>
Expand Down
Loading
Loading