CodeGeneration/RuntimeCompiler split: enable Roslyn-free production deployments#194
Merged
jeremydmiller merged 1 commit intomainfrom Apr 28, 2026
Merged
Conversation
The Initialize/InitializeSynchronously/WriteCodeFile extension methods on ICodeFile lived in JasperFx.RuntimeCompiler.CodeFileExtensions and used '?? new AssemblyGenerator()' as a silent fallback when no IAssemblyGenerator was registered in DI. That fallback hard-bound every consumer of these methods to the JasperFx.RuntimeCompiler package — and through it, to Roslyn — even when the consumer pre-generated all code in Static mode and never actually needed runtime compilation. This commit: 1. Adds new equivalents in JasperFx.CodeGeneration.CodeFileExtensions (lives in the JasperFx package) that REQUIRE an IAssemblyGenerator to be registered in DI when the codegen path is taken. If none is registered and runtime compilation is needed, throws a clear InvalidOperationException directing the caller to either install JasperFx.RuntimeCompiler + register AssemblyGenerator OR pre-generate code with TypeLoadMode.Static. No more silent magic dependency. 2. Marks the originals in JasperFx.RuntimeCompiler.CodeFileExtensions as [Obsolete] with a migration message pointing to the new namespace. The old methods retain their existing fallback behavior so existing consumers keep working until they migrate. 3. Updates the in-tree GeneratorTarget/WriteCommand.cs (the only consumer in this repo that hit the new ambiguity) to use the new namespace. Effect for downstream consumers (Wolverine, Marten): - Change `using JasperFx.RuntimeCompiler;` → `using JasperFx.CodeGeneration;` in any file that calls `.InitializeSynchronously()` / `.Initialize()` / `.WriteCodeFile()`. - Continue registering IAssemblyGenerator (Wolverine + Marten already do). - Once all such files migrate, the package reference to JasperFx.RuntimeCompiler can be moved to a separate opt-in package (e.g. Wolverine.RuntimeCompilation) — production deployments using Static mode never need to ship Roslyn. Versions: - JasperFx: 1.27.0 → 1.28.0 (additive: new extension methods) - JasperFx.RuntimeCompiler: 4.4.0 → 4.5.0 (additive: [Obsolete] markers, no behavior change for existing callers) Tests: CodegenTests (305) and CoreTests (389) all pass on net9.0. Step toward wolverine#1577 (cold-start optimization, AOT compatibility). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced Apr 28, 2026
jeremydmiller
added a commit
to JasperFx/marten
that referenced
this pull request
Apr 28, 2026
Picks up the latest JasperFx + JasperFx.Events. Also bumps the JasperFx.RuntimeCompiler pin to 4.5.0 so the transitive dep set is consistent. JasperFx 1.28 moved the ICodeFile codegen extension methods (notably InitializeSynchronously) into the JasperFx.CodeGeneration namespace (JasperFx/jasperfx#194) and marked the JasperFx.RuntimeCompiler copies [Obsolete]. The new method requires IAssemblyGenerator to be registered in DI (raising a descriptive InvalidOperationException otherwise) -- a deliberate AOT-prep step. Marten currently passes a null IServiceProvider to InitializeSynchronously at four call sites in ProviderGraph, DocumentStore.CompiledQueryCollection, and SecondaryStoreConfig, so the new method's runtime path would throw. Disambiguate by fully-qualifying those four calls to the JasperFx.RuntimeCompiler overload. The Obsolete warning code (CS0618) is already in NoWarn at the repo root, so the explicit qualification stays clean. Proper migration to the new method (registering IAssemblyGenerator in Marten's DI graph) is tracked under the AOT-mode work for Marten 9.0 (issue #4309). Effectively obsoletes PR #4003: the test additions from that PR have already been brought into master via 8ee1d23 ("Add test reproductions from PR #4155 and #4003"), so the only thing left is this package bump and the runtime-compiler disambiguation. The test use_a_single_tenanted_document_in_multi_tenancy_ancillary_store passes against the new package set. Closes #4003. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Splits the orchestration of code generation from the Roslyn implementation. Moves
Initialize/InitializeSynchronously/WriteCodeFileextensions from theJasperFx.RuntimeCompilernamespace intoJasperFx.CodeGeneration(i.e., into theJasperFxpackage, no Roslyn dependency), and removes the silent "?? new AssemblyGenerator()" fallback that previously hard-bound every consumer to Roslyn.The legacy methods stay in place, marked
[Obsolete]with a clear migration message — existing consumers (Wolverine, Marten, community projects) keep working until they migrate.Why
Discovered while planning Wolverine cold-start optimizations (JasperFx/wolverine#1577). The
?? new AssemblyGenerator()fallback inJasperFx.RuntimeCompiler.CodeFileExtensionsis what hard-binds every consumer to Roslyn. Even thoughIAssemblyGeneratoritself lives inJasperFx.CodeGeneration(the trimming-friendly package), the extension methods that orchestrate compilation lived inJasperFx.RuntimeCompilerand silently constructedAssemblyGeneratorif none was registered in DI.After this PR, downstream packages (Wolverine, Marten) can:
using JasperFx.RuntimeCompiler;→using JasperFx.CodeGeneration;at the four (Wolverine) / three (Marten) call sites<PackageReference Include=\"JasperFx.RuntimeCompiler\" />Wolverine.RuntimeCompilation/Marten.RuntimeCompilation)TypeLoadMode.Staticnever need Roslyn — major cold-start, deployment-size, and AOT-readiness winsChanges
src/JasperFx/CodeGeneration/CodeFileExtensions.cs(new)IAssemblyGeneratorMUST be registered in DI when codegen path is taken — otherwise throwsInvalidOperationExceptionwith actionable guidance.src/JasperFx.RuntimeCompiler/CodeFileExtensions.csInitialize,InitializeSynchronously,WriteCodeFile) now carry[Obsolete]with the migration message. No behavior change — the fallback remains for backward compatibility.src/GeneratorTarget/WriteCommand.csusing JasperFx.RuntimeCompiler;to disambiguate the new (now-ambiguous)Initializeextension call. The only in-tree consumer hit by the ambiguity.src/JasperFx/JasperFx.csprojsrc/JasperFx.RuntimeCompiler/JasperFx.RuntimeCompiler.csproj[Obsolete]markers, no behavior change)Behavior contract
For the new
JasperFx.CodeGeneration.CodeFileExtensions:TypeLoadMode.Static+ types pre-built into application assembly — never touchesIAssemblyGenerator, no DI requirement. Identical to legacy.TypeLoadMode.Static+ missing pre-built types + withincodegencommand — falls into the runtime-compilation path; requires registeredIAssemblyGenerator(it would have done this in the legacy path too, just silently).TypeLoadMode.Auto+ missing pre-built types — same as above.TypeLoadMode.Dynamic— always requires registeredIAssemblyGenerator.The exception thrown when no
IAssemblyGeneratoris registered:Migration guidance for downstream packages
Wolverine
Files to update (
using JasperFx.RuntimeCompiler;→using JasperFx.CodeGeneration;):src/Wolverine/Runtime/Handlers/HandlerGraph.cs:9src/Http/Wolverine.Http/HttpChain.EndpointBuilder.cs:5src/Wolverine.Grpc/WolverineGrpcExtensions.cs:6src/Wolverine/HostBuilderExtensions.cs:20The DI registration at
HostBuilderExtensions.cs:104(services.AddSingleton<IAssemblyGenerator, AssemblyGenerator>()) is the only thing keepingJasperFx.RuntimeCompilerin the Wolverine.dll deployment surface — moving it into a newWolverine.RuntimeCompilationopt-in package is a follow-up PR on the Wolverine side.Marten
Files to update (
using JasperFx.RuntimeCompiler;→using JasperFx.CodeGeneration;):src/Marten/Internal/ProviderGraph.cs:8src/Marten/Internal/SecondaryStoreConfig.cs:10src/Marten/DocumentStore.CompiledQueryCollection.cs:10src/Marten/MartenServiceCollectionExtensions.cs:16Marten also has a direct
new AssemblyGenerator()instantiation atsrc/Marten/Events/CodeGeneration/EventDocumentStorageGenerator.cs:47— that's a separate concern and remains inJasperFx.RuntimeCompiler's scope.Test plan
CodegenTestsnet9.0: 305/305 passCoreTestsnet9.0: 389/389 pass[Obsolete]warnings inside JasperFx itself (test files useJasperFx.RuntimeCompiler.Scenarios, not the obsolete extensions)Versions to release
JasperFx1.28.0JasperFx.RuntimeCompiler4.5.0🤖 Generated with Claude Code