Skip to content

AOT pillar B+C re-PR onto main: CommandLine annotations + smoke test (#213)#250

Merged
jeremydmiller merged 2 commits into
mainfrom
feature/aot-pillar-bc-rerun-213
May 12, 2026
Merged

AOT pillar B+C re-PR onto main: CommandLine annotations + smoke test (#213)#250
jeremydmiller merged 2 commits into
mainfrom
feature/aot-pillar-bc-rerun-213

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

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-213 and #249 into feature/commandline-aot-audit-213 — so the diff content did not actually land on main. Only #247 (the IsAotCompatible=true flip) is on main today.

This branch cherry-picks the two original content commits onto current main:

Cherry-pick safety

  • Both cherry-picks applied without conflict against current main (which has Set IsAotCompatible=true on JasperFx + JasperFx.Events (#213) #247's IsAotCompatible=true flip).
  • File counts match the originals: 18 files / +172 / -15 for B, 6 files / +151 / -1 for C.
  • ./build.sh test passes locally on net9.0 + net10.0:
    • TestCore — 407 passed
    • TestCodegen — 366 passed
    • TestCommandLine — 280 passed
    • TestEvents — 255 passed
    • SmokeTestAot — build clean, program exits 0

What changes

B — CommandLine annotations

Annotates the JasperFx.CommandLine reflective entry points with [RequiresUnreferencedCode] / [RequiresDynamicCode], propagates through ICommandFactory / ICommandCreator / UsageGraph, and suppresses the analyzer at downstream call sites with [UnconditionalSuppressMessage] + justifications. Source-generated DiscoveredCommands manifest path stays AOT-clean.

Effect on the IL warning punch list:

Before: JasperFx total per TFM 236, CommandLine slice 46
After:  JasperFx total per TFM 188, CommandLine slice 0

C — AOT smoke test consumer in CI

Adds src/JasperFx.AotSmoke/ — tiny console app with IsAotCompatible=true, TrimMode=full, and WarningsAsErrors=IL2026;IL2046;IL2055;IL2065;IL2067;IL2070;IL2072;IL2075;IL2090;IL2091;IL2111;IL3050;IL3051. Program.cs exercises SnapshotGate.ComputeHash / Verify, SnapshotFingerprint, and Event.For<T> — a representative AOT-clean cross-section. Wired into build/Build.cs as SmokeTestAot, folded into the umbrella Test target, 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 with error IL2026.

What to do with the orphaned PRs

#248 and #249 can be left as-is (the MERGED state 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

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' …`
@jeremydmiller jeremydmiller merged commit eaad669 into main May 12, 2026
1 check passed
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.
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.

1 participant