Eliminate ExtensionLoader assembly scanning via source-generated extension manifest (GH-2902)#2918
Conversation
…H-2902) Wolverine no longer probes the application's bin directory (AssemblyFinder. FindAssemblies) and speculatively Assembly.Loads candidates to find [WolverineModule] extensions at startup. Discovery now reads the compile-time JasperFx.Generated.DiscoveredExtensions manifest emitted by the JasperFx.SourceGenerator ExtensionDiscoveryGenerator. - IWolverineExtension (and IAsyncWolverineExtension) now extend JasperFx.IJasperFxExtension so implementers are seen by the generator's marker scan. - The JasperFx.SourceGenerator analyzer is referenced from WolverineFx with PrivateAssets="none" so it flows transitively to the app's own assembly and every extension/transport assembly; each emits its DiscoveredExtensions manifest. The generator self-gates to eligible ([JasperFxAssembly]/ [WolverineModule]-marked or executable) assemblies. - ExtensionLoader is deleted. WolverineOptions.DiscoverAndApplyExtensions() replaces it: it applies ONLY each assembly's declared [WolverineModule<T>] type (exact parity with the old scan), which excludes the framework-internal IWolverineExtension helpers (DisableExternalTransports, the ConfigureWolverine lambda extension, ...) that the manifest's marker scan also lists and that must never be auto-activated. - Because the manifest reader only sees loaded assemblies, discovery first walks the application's reference graph (Assembly.GetReferencedAssemblies) and loads the [WolverineModule] assemblies it reaches — NOT the old bin-directory glob — so reference-and-go modules like WolverineFx.RuntimeCompilation still auto-activate. [WolverineModule] assemblies are still added to handler discovery via IncludeExtensionAssemblies. Adds regression coverage (extension_discovery_via_manifest) asserting the declared module extension is applied and framework-internal helpers never are, and updates docs/guide/extensions.md to describe the [WolverineModule]/ [JasperFxAssembly] attributes and the source-generator-based discovery. Full wolverine.slnx Release build clean; all 1713 CoreTests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…st (GH-2902 CI fix) The reference-graph walk (Assembly.GetReferencedAssemblies) could not see a referenced [WolverineModule] assembly whose types the application never uses directly: the C# compiler prunes such references from assembly metadata. WolverineFx.RuntimeCompilation is exactly that case — you reference the package for its [WolverineModule] auto-activation, not to use its types — so its source-generated DiscoveredExtensions manifest was never read, and TypeLoadMode.Dynamic apps failed at startup with "no IAssemblyGenerator (Roslyn) is registered". That broke MessageRoutingTests and other suites in CI (and would affect real reference-and-auto-activate apps too, not just tests). Load the non-framework assemblies from the runtime's resolved deployment list (TRUSTED_PLATFORM_ASSEMBLIES) instead, which includes those pruned-but-deployed assemblies. This is still NOT the old AssemblyFinder Directory.EnumerateFiles bin probe — it reads the runtime's own resolved assembly list rather than recursively scanning the filesystem. The reference-graph walk is kept as a fallback for hosts that do not surface that list (e.g. single-file deployments). Validated: previously-failing MessageRoutingTests (25) now pass; CoreTests (1713) pass; full wolverine.slnx Release build clean (0 warnings). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Brought the branch up to date with Note on history: branch protection blocks force-push, so I merged CI failure root cause: every failing suite (e.g. Fix ( Validated locally: previously-failing |
Closes #2902.
What
Replaces Wolverine's runtime assembly scan for
IWolverineExtensiontypes with the compile-time extension manifest from JasperFx (jasperfx#371), and removesExtensionLoaderentirely. Startup no longer probes the application's bin directory (AssemblyFinder.FindAssemblies) or speculativelyAssembly.Loads candidates.Changes
IWolverineExtension : IJasperFxExtension(andIAsyncWolverineExtensiontoo) so implementers are seen by the generator's marker scan.JasperFx.SourceGeneratorflows transitively from theWolverineFxpackage (PrivateAssets="none"), so the app's own assembly and every extension/transport assembly emit aJasperFx.Generated.DiscoveredExtensionsmanifest. The generator self-gates to eligible assemblies ([JasperFxAssembly]/[WolverineModule]-marked or executable), so it is a no-op where it has nothing to emit.ExtensionLoaderdeleted. NewWolverineOptions.DiscoverAndApplyExtensions()reads the manifest from loaded assemblies and applies extensions early (while theIServiceCollectionis still mutable, matching historical timing).[WolverineModule]assemblies are still added to handler discovery viaIncludeExtensionAssemblies(covers pure handler modules likeOrderExtensionthat declare a bare[assembly: WolverineModule]and emit no manifest entry).Two design points worth a look
Discovery is scoped to each assembly's declared
[WolverineModule<T>]type — not everyIWolverineExtensionthe marker scan lists. The manifest's marker scan also picks up framework-internal helpers in the[JasperFxAssembly]-marked core (DisableExternalTransports,DisablePersistence,UseSoloDurabilityMode, theConfigureWolverinelambda extension). Those are applied explicitly/via DI and must never be auto-activated — auto-applyingDisableExternalTransportswould stub every transport, andLambdaWolverineExtensionhas no parameterless ctor. Gating on the declaring assembly's declared module type reproduces the exact set the old scan applied.Reference-graph module loading instead of the bin-directory glob. The manifest reader only sees loaded assemblies, but a referenced module (e.g.
WolverineFx.RuntimeCompilation) may not be loaded at bootstrap. Discovery first walks the application's reference graph (Assembly.GetReferencedAssemblies, skipping framework prefixes) and loads the[WolverineModule]assemblies it reaches — not the oldDirectory.EnumerateFilesprobe — so "reference the package and it auto-activates" still works.Acceptance criteria
IWolverineExtensionextendsIJasperFxExtension; extensions discovered via the generated manifest are applied identically to today (same set, sameConfigureordering/idempotency).AssemblyFinder.FindAssembliesbin-directory probe on startup.JasperFx.SourceGeneratoranalyzer is referenced so app + extension assemblies emit manifests.[RequiresUnreferencedCode]AssemblyFinderscan is gone from the extension path.extension_discovery_via_manifest, plus the existingModule1/OrderExtension/RuntimeCompilationfixtures via the default-app suites).Verification
wolverine.slnxRelease build: 0 warnings / 0 errors.🤖 Generated with Claude Code