AOT pillar B+C re-PR onto main: CommandLine annotations + smoke test (#213)#250
Merged
Conversation
Second deliverable on the AOT compliance pillar (#213). Walks the JasperFx.CommandLine subsystem and either annotates each reflective entry point with [RequiresUnreferencedCode] / [RequiresDynamicCode] or suppresses analyzer warnings with a [UnconditionalSuppressMessage] that names the already-annotated entry point. Public surfaces annotated ICommandCreator.CreateCommand / CreateModel ActivatorCommandCreator — Activator.CreateInstance(Type) DependencyInjectionCommandCreator — ActivatorUtilities + GetProperties ICommandFactory — BuildRun / RegisterCommands / BuildAllCommands / ApplyExtensions CommandFactory — RegisterCommand[s], BuildRun, BuildAllCommands, HelpRun, Build, ApplyExtensions, RegisterCommandsFromExtensionAssemblies CommandExecutor — ExecuteCommand[Async]<T>, Execute / ExecuteAsync CommandLineHostingExtensions — RunJasperFxCommands*, ApplyJasperFxExtensions, ApplyFactoryDefaults OaktonShims — ApplyOaktonExtensions, RunOaktonCommands (legacy aliases) InputParser — GetHandlers / BuildHandler UsageGraph(Type) + BuildInput Suppressed at the call site (justified by entry-point annotations) CommandFactory.TryRegisterFromGeneratedManifest — emits via JasperFx.SourceGenerator CommandExecutor.execute — Spectre WriteException only on error path EnumerableArgument.Handle / EnumerableFlag.Handle — CloseAndBuildAs reachable only from annotated entry points ArrayConversion.ConverterFor — Array.CreateInstance via Conversions StringConverterProvider.ConverterFor — Type.GetConstructor via Conversions DescribeCommand.ReferencedAssemblies.WriteToConsole — diagnostic-only JasperFxCommand<T> / JasperFxAsyncCommand<T> constructors — UsageGraph reads T's members which are preserved via the annotated RegisterCommand entry point Source-generated path preserved CommandFactory.TryRegisterFromGeneratedManifest is the AOT-clean fast path. Apps that include JasperFx.SourceGenerator emit the JasperFx.Generated.DiscoveredCommands manifest as ordinary source, which survives trimming naturally. The reflective fallback (RegisterCommands / RegisterCommandsFromExtensionAssemblies) is exactly what the annotations warn AOT consumers about. Effect on the IL warning punch list Before (after #213 flag-flip in PR #247): JasperFx total per TFM: 236 warnings, of which CommandLine slice: 46 After this PR: JasperFx total per TFM: 188 warnings, of which CommandLine slice: 0 Remaining warnings sit in Core/Reflection (128), Core/IoC (60), CodeGeneration/Services (44), ServiceContainer.cs (32), and a long tail of smaller areas — each is a follow-up issue against #213. CommandLineTests (280), CoreTests (407), CodegenTests (366) all pass on net9.0 + net10.0. Runtime behavior is unchanged; the annotations are analyzer-only.
Third deliverable on the AOT compliance pillar (#213). Pins the AOT-clean cross-section of the JasperFx + JasperFx.Events public surface so any future regression — either a new [RequiresDynamicCode] / [RequiresUnreferencedCode] annotation on a previously-clean API, or an edit to the smoke test itself that calls into a reflective surface — fails the CI build with an analyzer error rather than silently leaking into downstream consumers' AOT publishes. The smoke test consumer src/JasperFx.AotSmoke/JasperFx.AotSmoke.csproj IsAotCompatible=true TrimMode=full WarningsAsErrors=IL2026;IL2046;IL2055;IL2065;IL2067;IL2070;IL2072; IL2075;IL2090;IL2091;IL2111;IL3050;IL3051 Program.cs exercises: - SnapshotGate.ComputeHash + Verify (#243 phase 1 substrate) - SnapshotFingerprint record construction - Event.For<T> (JasperFx.Events generic factory) Intentionally not exercised here — the AOT-pillar plan calls these out as annotated surfaces, not AOT-clean ones: - CommandFactory / CommandExecutor / CommandLineHostingExtensions - GenericFactoryCache.BuildAs<T> (delegate-factory overloads are [RequiresDynamicCode]) - SnapshotGate.Read / Write (System.Text.Json without a generation context) Wiring - build/Build.cs: new SmokeTestAot target (depends on Compile), wired into the umbrella Test target alongside TestCore/Codegen/CommandLine/Events - .github/workflows/dotnet.yml: new "smoke-test-aot" CI step - jasperfx.sln: smoke test project nested under TestHarnesses Verification Build is clean with 0 errors / 0 IL warnings against Program.cs. Smoke test program exits 0 and prints the AOT-clean ConfigHash. Manually verified the WarningsAsErrors policy is doing real work by introducing CommandFactory.RegisterCommand<T>() and confirming the build fails with `error IL2026: Using member 'JasperFx.CommandLine.CommandFactory.RegisterCommand<T>()' which has 'RequiresUnreferencedCodeAttribute' …`
This was referenced May 12, 2026
thechucklingatom
pushed a commit
to thechucklingatom/jasperfx
that referenced
this pull request
May 19, 2026
Carries the AOT pillar Tier 1 work (JasperFx#213) that landed in JasperFx#247 (IsAotCompatible=true on JasperFx + JasperFx.Events) and JasperFx#250 (CommandLine reflective surface annotated + AOT-clean smoke test in CI). Downstream consumers — Weasel 9 (alpha.8 → alpha.11 refresh queued via weasel#273), Polecat 4 (alpha.8 today), Marten 9 — can pick this up to get the AOT analyzer flag and the precise CommandLine punch list. Other packages (JasperFx.RuntimeCompiler, JasperFx.SourceGeneration, JasperFx.Events.SourceGenerator) keep their current alpha — nothing changed in those projects this cycle. NugetPush is `SkipDuplicate` so the re-published artifacts at their existing versions are no-ops.
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.
Combined re-PR of #248 (CommandLine annotations) + #249 (AOT smoke test in CI). Both were marked merged on GitHub but stacked into already-merged feature branches — #248 into
feature/aot-compatible-flag-213and #249 intofeature/commandline-aot-audit-213— so the diff content did not actually land onmain. Only #247 (theIsAotCompatible=trueflip) is onmaintoday.This branch cherry-picks the two original content commits onto current
main:b42018e(was3d5dc31) — Annotate JasperFx.CommandLine reflective surface for AOT ([Pillar] AOT compliance #213). Identical to the body of merged-but-stranded PR Annotate JasperFx.CommandLine reflective surface for AOT (#213) #248.d4077d8(was0d1effd) — Add AOT-clean consumer smoke test in CI ([Pillar] AOT compliance #213). Identical to the body of merged-but-stranded PR Add AOT-clean consumer smoke test in CI (#213) #249.Cherry-pick safety
main(which has Set IsAotCompatible=true on JasperFx + JasperFx.Events (#213) #247'sIsAotCompatible=trueflip)../build.sh testpasses locally onnet9.0+net10.0:TestCore— 407 passedTestCodegen— 366 passedTestCommandLine— 280 passedTestEvents— 255 passedSmokeTestAot— build clean, program exits 0What changes
B — CommandLine annotations
Annotates the
JasperFx.CommandLinereflective entry points with[RequiresUnreferencedCode]/[RequiresDynamicCode], propagates throughICommandFactory/ICommandCreator/UsageGraph, and suppresses the analyzer at downstream call sites with[UnconditionalSuppressMessage]+ justifications. Source-generatedDiscoveredCommandsmanifest path stays AOT-clean.Effect on the IL warning punch list:
C — AOT smoke test consumer in CI
Adds
src/JasperFx.AotSmoke/— tiny console app withIsAotCompatible=true,TrimMode=full, andWarningsAsErrors=IL2026;IL2046;IL2055;IL2065;IL2067;IL2070;IL2072;IL2075;IL2090;IL2091;IL2111;IL3050;IL3051. Program.cs exercisesSnapshotGate.ComputeHash/Verify,SnapshotFingerprint, andEvent.For<T>— a representative AOT-clean cross-section. Wired intobuild/Build.csasSmokeTestAot, folded into the umbrellaTesttarget, and added as a CI step in.github/workflows/dotnet.yml.Manually re-verified the policy is doing real work: temporarily calling
CommandFactory.RegisterCommand<MyCmd>()(annotated[RequiresUnreferencedCode]in commit B) fails the build witherror IL2026.What to do with the orphaned PRs
#248 and #249 can be left as-is (the
MERGEDstate on GitHub reflects the actual git merge that happened, just into a feature branch rather than main). The cross-references from this PR keep the audit trail intact.🤖 Generated with Claude Code