diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index 30ca45be..04e1fe9a 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -62,3 +62,8 @@ jobs:
if: ${{ success() || failure() }}
run: ./build.sh test-command-line
shell: bash
+
+ - name: smoke-test-aot
+ if: ${{ success() || failure() }}
+ run: ./build.sh smoke-test-aot
+ shell: bash
diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json
index 1bf08491..5c6665e6 100644
--- a/.nuke/build.schema.json
+++ b/.nuke/build.schema.json
@@ -29,6 +29,7 @@
"NugetPack",
"NugetPush",
"Restore",
+ "SmokeTestAot",
"SmokeTestCommands",
"Test",
"TestCodegen",
diff --git a/build/Build.cs b/build/Build.cs
index e485db06..084a57d8 100644
--- a/build/Build.cs
+++ b/build/Build.cs
@@ -50,7 +50,7 @@ partial class Build : NukeBuild
.EnableNoRestore());
});
- Target Test => _ => _.DependsOn(TestCore, TestCodegen, TestCommandLine, TestEvents);
+ Target Test => _ => _.DependsOn(TestCore, TestCodegen, TestCommandLine, TestEvents, SmokeTestAot);
Target TestCore => _ => _
.DependsOn(Compile)
@@ -107,6 +107,27 @@ partial class Build : NukeBuild
DotNet("run --framework net9.0 -- codegen preview --start", Solution.TestHarnesses.GeneratorTarget.Directory);
});
+ ///
+ /// AOT-clean consumer smoke test (jasperfx#213). The JasperFx.AotSmoke
+ /// project sets IsAotCompatible=true + promotes IL2026 / IL3050 / IL2046
+ /// / IL2070 / IL2075 (the full AOT analyzer set) to errors and exercises
+ /// a representative slice of the AOT-clean JasperFx + JasperFx.Events
+ /// surface. The build fails if a previously-AOT-clean API gains an
+ /// annotation, or if Program.cs is changed to call into a reflective
+ /// surface. Also runs the program to confirm runtime behavior is intact.
+ ///
+ Target SmokeTestAot => _ => _
+ .DependsOn(Compile)
+ .Executes(() =>
+ {
+ DotNetBuild(s => s
+ .SetProjectFile(Solution.TestHarnesses.JasperFx_AotSmoke)
+ .SetConfiguration(Configuration)
+ .EnableNoRestore());
+
+ DotNet("run --framework net10.0 --no-build", Solution.TestHarnesses.JasperFx_AotSmoke.Directory);
+ });
+
AbsolutePath ArtifactsDirectory => RootDirectory / "artifacts";
Target NugetPack => _ => _
diff --git a/jasperfx.sln b/jasperfx.sln
index 772efdb8..f18d4ee8 100644
--- a/jasperfx.sln
+++ b/jasperfx.sln
@@ -73,6 +73,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JasperFx.SourceGeneration",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommandLineBenchmarks", "src\CommandLineBenchmarks\CommandLineBenchmarks.csproj", "{8D7BF9A9-0345-4FBA-9972-1F8413006DC2}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JasperFx.AotSmoke", "src\JasperFx.AotSmoke\JasperFx.AotSmoke.csproj", "{8FB8216F-216F-480F-9519-A5893F7F3151}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -389,6 +391,18 @@ Global
{8D7BF9A9-0345-4FBA-9972-1F8413006DC2}.Release|x64.Build.0 = Release|Any CPU
{8D7BF9A9-0345-4FBA-9972-1F8413006DC2}.Release|x86.ActiveCfg = Release|Any CPU
{8D7BF9A9-0345-4FBA-9972-1F8413006DC2}.Release|x86.Build.0 = Release|Any CPU
+ {8FB8216F-216F-480F-9519-A5893F7F3151}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8FB8216F-216F-480F-9519-A5893F7F3151}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8FB8216F-216F-480F-9519-A5893F7F3151}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {8FB8216F-216F-480F-9519-A5893F7F3151}.Debug|x64.Build.0 = Debug|Any CPU
+ {8FB8216F-216F-480F-9519-A5893F7F3151}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {8FB8216F-216F-480F-9519-A5893F7F3151}.Debug|x86.Build.0 = Debug|Any CPU
+ {8FB8216F-216F-480F-9519-A5893F7F3151}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8FB8216F-216F-480F-9519-A5893F7F3151}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8FB8216F-216F-480F-9519-A5893F7F3151}.Release|x64.ActiveCfg = Release|Any CPU
+ {8FB8216F-216F-480F-9519-A5893F7F3151}.Release|x64.Build.0 = Release|Any CPU
+ {8FB8216F-216F-480F-9519-A5893F7F3151}.Release|x86.ActiveCfg = Release|Any CPU
+ {8FB8216F-216F-480F-9519-A5893F7F3151}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -408,5 +422,6 @@ Global
{F0759B24-D9E2-4E42-B352-6C6B8138FD8C} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{37E2EECE-FF24-4D39-96B6-BED9BCE8D1D4} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{8D7BF9A9-0345-4FBA-9972-1F8413006DC2} = {A1BBEFB2-1FDB-4A32-8D00-DD5AA8E1E43A}
+ {8FB8216F-216F-480F-9519-A5893F7F3151} = {A1BBEFB2-1FDB-4A32-8D00-DD5AA8E1E43A}
EndGlobalSection
EndGlobal
diff --git a/src/JasperFx.AotSmoke/JasperFx.AotSmoke.csproj b/src/JasperFx.AotSmoke/JasperFx.AotSmoke.csproj
new file mode 100644
index 00000000..c380d156
--- /dev/null
+++ b/src/JasperFx.AotSmoke/JasperFx.AotSmoke.csproj
@@ -0,0 +1,34 @@
+
+
+
+ Exe
+ enable
+ enable
+ false
+
+
+ true
+ full
+ IL2026;IL2046;IL2055;IL2065;IL2067;IL2070;IL2072;IL2075;IL2090;IL2091;IL2111;IL3050;IL3051
+
+
+
+
+
+
+
+
diff --git a/src/JasperFx.AotSmoke/Program.cs b/src/JasperFx.AotSmoke/Program.cs
new file mode 100644
index 00000000..0f58ddc3
--- /dev/null
+++ b/src/JasperFx.AotSmoke/Program.cs
@@ -0,0 +1,74 @@
+// AOT smoke test (jasperfx#213).
+//
+// This program touches a representative cross-section of the AOT-clean
+// JasperFx + JasperFx.Events surface. The csproj sets IsAotCompatible=true
+// and promotes the AOT analyzer warning codes to errors, so any change that
+// adds [RequiresDynamicCode] / [RequiresUnreferencedCode] to an API exercised
+// here — or any change to this file that calls into a reflective JasperFx
+// surface — fails the build in CI.
+//
+// Intentionally *not* exercised here (those carry AOT annotations by design):
+// - CommandFactory / CommandExecutor / CommandLineHostingExtensions
+// (reflective command discovery; AOT-clean path is the source-generated
+// DiscoveredCommands manifest, which is itself the smoke test for the
+// JasperFx.SourceGenerator's CommandLine output)
+// - GenericFactoryCache.BuildAs (the delegate-factory overloads are
+// annotated [RequiresDynamicCode] because the default factory calls
+// MakeGenericType; an AOT consumer supplies its own AOT-safe factory)
+// - SnapshotGate.Read / Write (System.Text.Json without a generation
+// context — Marten and Wolverine consumers wrap these with their own
+// STJ context or pre-serialized strings)
+
+using JasperFx.CodeGeneration.Snapshots;
+using JasperFx.Events;
+
+// --- SnapshotGate.ComputeHash / Verify ----------------------------------
+// Pure functions that compute SHA-256 over a canonical-input string and
+// compare fingerprints. The AOT-clean substrate the codegen-snapshot
+// contract (#243) is built on.
+
+const string sampleInput = "marten-version=9.0.0␞store-name=AppDb";
+
+string hashA = SnapshotGate.ComputeHash(sampleInput);
+string hashB = SnapshotGate.ComputeHash(sampleInput);
+if (hashA != hashB)
+{
+ Console.Error.WriteLine($"SnapshotGate.ComputeHash is non-deterministic: '{hashA}' vs '{hashB}'.");
+ return 1;
+}
+
+var live = new SnapshotFingerprint(
+ ProductName: "marten",
+ ProductVersion: "9.0.0-alpha.1",
+ JasperFxVersion: "2.0.0-alpha.10",
+ ConfigHash: hashA,
+ SchemaVersion: SnapshotGate.CurrentSchemaVersion);
+
+SnapshotVerdict firstBoot = SnapshotGate.Verify(live, persisted: null);
+SnapshotVerdict accept = SnapshotGate.Verify(live, persisted: live);
+SnapshotVerdict reject = SnapshotGate.Verify(live, persisted: live with { ConfigHash = "deadbeef" });
+
+if (firstBoot != SnapshotVerdict.FirstBoot ||
+ accept != SnapshotVerdict.Accept ||
+ reject != SnapshotVerdict.RejectAndRegenerate)
+{
+ Console.Error.WriteLine(
+ $"SnapshotGate.Verify regression: firstBoot={firstBoot}, accept={accept}, reject={reject}.");
+ return 1;
+}
+
+// --- JasperFx.Events.Event.For ---------------------------------------
+// Generic factory for IEvent; AOT-clean for closed-over T.
+
+IEvent evt = Event.For(new SampleEvent("hello"));
+IEvent tenantEvt = Event.For("tenant-a", new SampleEvent("hello"));
+if (evt.Data.Message != tenantEvt.Data.Message)
+{
+ Console.Error.WriteLine("Event.For regression.");
+ return 1;
+}
+
+Console.WriteLine($"JasperFx AOT smoke OK — ConfigHash={hashA[..16]}…");
+return 0;
+
+internal readonly record struct SampleEvent(string Message);
diff --git a/src/JasperFx/CommandLine/ActivatorCommandCreator.cs b/src/JasperFx/CommandLine/ActivatorCommandCreator.cs
index e74e02e4..d308918c 100644
--- a/src/JasperFx/CommandLine/ActivatorCommandCreator.cs
+++ b/src/JasperFx/CommandLine/ActivatorCommandCreator.cs
@@ -1,4 +1,5 @@
using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
using JasperFx.Core.Reflection;
using Spectre.Console;
@@ -11,6 +12,7 @@ public ActivatorCommandCreator()
Debug.WriteLine("What?");
}
+ [RequiresUnreferencedCode("Activator.CreateInstance(Type) requires the public parameterless constructor of commandType to survive trimming.")]
public IJasperFxCommand CreateCommand(Type commandType)
{
try
@@ -25,6 +27,7 @@ public IJasperFxCommand CreateCommand(Type commandType)
}
}
+ [RequiresUnreferencedCode("Activator.CreateInstance(Type) requires the public parameterless constructor of modelType to survive trimming.")]
public object CreateModel(Type modelType)
{
return Activator.CreateInstance(modelType)!;
diff --git a/src/JasperFx/CommandLine/CommandExecutor.cs b/src/JasperFx/CommandLine/CommandExecutor.cs
index 473dd9a8..8050aac9 100644
--- a/src/JasperFx/CommandLine/CommandExecutor.cs
+++ b/src/JasperFx/CommandLine/CommandExecutor.cs
@@ -1,4 +1,5 @@
-using JasperFx.CommandLine.Parsing;
+using System.Diagnostics.CodeAnalysis;
+using JasperFx.CommandLine.Parsing;
using JasperFx.Core;
using Spectre.Console;
@@ -26,6 +27,8 @@ public CommandExecutor() : this(new CommandFactory())
public ICommandFactory Factory { get; }
+ [UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode",
+ Justification = "Spectre.Console.AnsiConsole.WriteException is only invoked on the error-display path; the outer try/catch falls back to Console.Write on Exception, so AOT consumers degrade gracefully.")]
private static async Task execute(CommandRun run)
{
bool success;
@@ -70,6 +73,8 @@ private static async Task execute(CommandRun run)
/// line arguments
///
///
+ [RequiresUnreferencedCode("Registers T via reflection and dispatches through CommandFactory.BuildRun, which depends on the command type's public constructor + input-type properties surviving trimming.")]
+ [RequiresDynamicCode("Enumerable command-argument parsing closes generic List via MakeGenericType.")]
public static int ExecuteCommand(string[] args, string? optsFile = null) where T : IJasperFxCommand
{
var factory = new CommandFactory();
@@ -94,6 +99,8 @@ public static int ExecuteCommand(string[] args, string? optsFile = null) wher
/// line arguments
///
///
+ [RequiresUnreferencedCode("Registers T via reflection and dispatches through CommandFactory.BuildRun, which depends on the command type's public constructor + input-type properties surviving trimming.")]
+ [RequiresDynamicCode("Enumerable command-argument parsing closes generic List via MakeGenericType.")]
public static Task ExecuteCommandAsync(string[] args, string? optsFile = null) where T : IJasperFxCommand
{
var factory = new CommandFactory();
@@ -172,6 +179,8 @@ internal static IEnumerable ReadOptions(string? optionsFile)
///
///
///
+ [RequiresUnreferencedCode("Dispatches through ICommandFactory.BuildRun and reflective command instantiation.")]
+ [RequiresDynamicCode("Enumerable command-argument parsing closes generic List via MakeGenericType.")]
public int Execute(string commandLine)
{
return ExecuteAsync(commandLine).GetAwaiter().GetResult();
@@ -182,6 +191,8 @@ public int Execute(string commandLine)
///
///
///
+ [RequiresUnreferencedCode("Dispatches through ICommandFactory.BuildRun and reflective command instantiation.")]
+ [RequiresDynamicCode("Enumerable command-argument parsing closes generic List via MakeGenericType.")]
public int Execute(string[] args)
{
return ExecuteAsync(args).GetAwaiter().GetResult();
@@ -192,6 +203,8 @@ public int Execute(string[] args)
///
///
///
+ [RequiresUnreferencedCode("Dispatches through ICommandFactory.BuildRun and reflective command instantiation.")]
+ [RequiresDynamicCode("Enumerable command-argument parsing closes generic List via MakeGenericType.")]
public Task ExecuteAsync(string commandLine)
{
commandLine = applyOptions(commandLine);
@@ -205,6 +218,8 @@ public Task ExecuteAsync(string commandLine)
///
///
///
+ [RequiresUnreferencedCode("Dispatches through ICommandFactory.BuildRun and reflective command instantiation.")]
+ [RequiresDynamicCode("Enumerable command-argument parsing closes generic List via MakeGenericType.")]
public Task ExecuteAsync(string[] args)
{
var run = Factory.BuildRun(ReadOptions(OptionsFile).Concat(args));
diff --git a/src/JasperFx/CommandLine/CommandFactory.cs b/src/JasperFx/CommandLine/CommandFactory.cs
index 02c87b53..05c4261c 100644
--- a/src/JasperFx/CommandLine/CommandFactory.cs
+++ b/src/JasperFx/CommandLine/CommandFactory.cs
@@ -1,4 +1,5 @@
-using System.Reflection;
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
using System.Text.RegularExpressions;
using JasperFx.CommandLine.Help;
using JasperFx.CommandLine.Parsing;
@@ -64,12 +65,16 @@ public Type? DefaultCommand
}
}
+ [RequiresUnreferencedCode("CommandFactory dispatches to commands and inputs via reflection; their public constructors and properties must survive trimming. AOT-publishing apps should consume commands through the source-generated manifest.")]
+ [RequiresDynamicCode("Command input parsing closes generic List via MakeGenericType for enumerable arguments / flags.")]
public CommandRun BuildRun(string commandLine)
{
var args = StringTokenizer.Tokenize(commandLine);
return BuildRun(args);
}
+ [RequiresUnreferencedCode("CommandFactory dispatches to commands and inputs via reflection; their public constructors and properties must survive trimming. AOT-publishing apps should consume commands through the source-generated manifest.")]
+ [RequiresDynamicCode("Command input parsing closes generic List via MakeGenericType for enumerable arguments / flags.")]
public CommandRun BuildRun(IEnumerable args)
{
if (!args.Any())
@@ -116,6 +121,7 @@ public CommandRun BuildRun(IEnumerable args)
/// Add all the IJasperFxCommand classes in the given assembly to the command runner
///
///
+ [RequiresUnreferencedCode("Scans assembly.GetExportedTypes() for IJasperFxCommand. AOT-publishing apps should rely on the source-generated command manifest emitted by JasperFx.SourceGenerator.")]
public void RegisterCommands(Assembly assembly)
{
foreach (var type in assembly
@@ -129,15 +135,17 @@ public void RegisterCommands(Assembly assembly)
{
_extensionTypes.Add(attribute.ExtensionType);
}
-
+
}
}
+ [RequiresUnreferencedCode("BuildAllCommands dispatches each registered command type through ICommandCreator.CreateCommand, which instantiates the type reflectively.")]
public IEnumerable BuildAllCommands()
{
return _commandTypes.Select(x => _commandCreator.CreateCommand(x));
}
+ [RequiresUnreferencedCode("Activator.CreateInstance(extensionType) requires public parameterless constructor of each extension to survive trimming.")]
public void ApplyExtensions(IHostBuilder builder)
{
if (builder is PreBuiltHostBuilder)
@@ -151,6 +159,7 @@ public void ApplyExtensions(IHostBuilder builder)
}
}
+ [RequiresUnreferencedCode("Activator.CreateInstance(extensionType) requires public parameterless constructor of each extension to survive trimming.")]
public void ApplyExtensions(IServiceCollection services)
{
try
@@ -196,6 +205,8 @@ public CommandRun InvalidCommandRun(string commandName)
};
}
+ [RequiresUnreferencedCode("Resolves the command type reflectively via ICommandCreator and walks its UsageGraph (which reads MemberInfo via reflection).")]
+ [RequiresDynamicCode("Enumerable argument / flag parsing closes generic List via MakeGenericType.")]
private CommandRun buildRun(Queue queue, string commandName)
{
try
@@ -242,6 +253,8 @@ private CommandRun buildRun(Queue queue, string commandName)
return HelpRun(commandName);
}
+ [RequiresUnreferencedCode("Builds a temporary command instance to pre-populate input. Inherits trim requirements from ActivatorCommandCreator.CreateCommand.")]
+ [RequiresDynamicCode("UsageGraph input building closes generic List for enumerable arguments.")]
private object? tryBeforeBuild(Queue queue, string commandName)
{
var commandType = _commandTypes[commandName];
@@ -266,6 +279,7 @@ private CommandRun buildRun(Queue queue, string commandName)
/// Add a single command type to the command runner
///
///
+ [RequiresUnreferencedCode("Registers T for later reflective instantiation via ICommandCreator; T's public constructors and input-type properties must survive trimming.")]
public void RegisterCommand()
{
RegisterCommand(typeof(T));
@@ -274,6 +288,7 @@ public void RegisterCommand()
///
/// Add a single command type to the command runner
///
+ [RequiresUnreferencedCode("Registers a command type for later reflective instantiation via ICommandCreator; its public constructors and input-type properties must survive trimming.")]
public void RegisterCommand(Type type)
{
if (!IsJasperFxCommandType(type))
@@ -296,17 +311,22 @@ public static bool IsJasperFxCommandType(Type type)
}
+ [RequiresUnreferencedCode("Resolves the command type reflectively via ICommandCreator. The type's public constructor must survive trimming.")]
public IJasperFxCommand Build(string commandName)
{
return _commandCreator.CreateCommand(_commandTypes[commandName.ToLower()]);
}
+ [RequiresUnreferencedCode("Resolves the command and its UsageGraph reflectively via ICommandCreator. The command type's public constructor and input properties must survive trimming.")]
+ [RequiresDynamicCode("UsageGraph input building closes generic List for enumerable arguments.")]
public CommandRun HelpRun(string commandName)
{
return HelpRun(new Queue(new[] { commandName }));
}
+ [RequiresUnreferencedCode("Resolves the command and its UsageGraph reflectively via ICommandCreator. The command type's public constructor and input properties must survive trimming.")]
+ [RequiresDynamicCode("UsageGraph input building closes generic List for enumerable arguments.")]
public virtual CommandRun HelpRun(Queue queue)
{
var input = (HelpInput)new HelpCommand().Usages.BuildInput(queue, _commandCreator);
@@ -372,6 +392,13 @@ public void SetAppName(string appName)
/// Automatically discover any JasperFx commands in assemblies marked as
/// [assembly: JasperFxCommandAssembly]. Also
///
+ ///
+ /// Tries the source-generated DiscoveredCommands manifest first
+ /// (AOT/trim-clean path); falls back to +
+ /// if no manifest is found. The trim/AOT
+ /// warnings are attached because the fallback path scans assemblies.
+ ///
+ [RequiresUnreferencedCode("Falls back to AssemblyFinder + assembly.GetExportedTypes() scanning if no source-generated command manifest is present. AOT-publishing apps should emit the manifest via JasperFx.SourceGenerator.")]
public void RegisterCommandsFromExtensionAssemblies()
{
// Check for source-generated command manifest first to avoid assembly scanning
@@ -404,6 +431,22 @@ public void RegisterCommandsFromExtensionAssemblies()
/// Attempt to use a source-generated command manifest to register commands
/// without runtime assembly scanning. Returns true if a manifest was found and used.
///
+ ///
+ /// The lookup uses well-known string identifiers
+ /// (JasperFx.Generated.DiscoveredCommands + CommandTypes) that
+ /// the trimmer cannot statically prove are reachable. The
+ /// attributes document that
+ /// consuming apps emit the manifest via the JasperFx.SourceGenerator
+ /// analyzer — when that source generator runs, the type and property are
+ /// produced as ordinary code in the consuming assembly and survive
+ /// trimming naturally. Apps that do not include the generator simply
+ /// fall through to , which carries its own
+ /// [RequiresUnreferencedCode].
+ ///
+ [UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode",
+ Justification = "JasperFx.Generated.DiscoveredCommands is emitted by JasperFx.SourceGenerator into the consuming app as ordinary code; the lookup degrades safely to the reflective fallback if the generator is not enabled.")]
+ [UnconditionalSuppressMessage("Trimming", "IL2075:DynamicallyAccessedMembers",
+ Justification = "Same as IL2026 — DiscoveredCommands.CommandTypes is generated source code in the consuming assembly.")]
internal bool TryRegisterFromGeneratedManifest()
{
// Look for the generated DiscoveredCommands class in all loaded assemblies
diff --git a/src/JasperFx/CommandLine/DependencyInjectionCommandCreator.cs b/src/JasperFx/CommandLine/DependencyInjectionCommandCreator.cs
index 5b12a5fb..d3f387ad 100644
--- a/src/JasperFx/CommandLine/DependencyInjectionCommandCreator.cs
+++ b/src/JasperFx/CommandLine/DependencyInjectionCommandCreator.cs
@@ -1,4 +1,5 @@
-using JasperFx.CommandLine.Help;
+using System.Diagnostics.CodeAnalysis;
+using JasperFx.CommandLine.Help;
using JasperFx.Core;
using JasperFx.Core.Reflection;
using Microsoft.Extensions.DependencyInjection;
@@ -13,16 +14,18 @@ public DependencyInjectionCommandCreator(IServiceProvider serviceProvider)
_serviceProvider = serviceProvider;
}
+ [RequiresUnreferencedCode("Inspects commandType.GetProperties() for [InjectService] and uses ActivatorUtilities.CreateInstance; public constructors and properties of commandType must survive trimming.")]
public IJasperFxCommand CreateCommand(Type commandType)
{
if (commandType.GetProperties().Any(x => x.HasAttribute()))
{
return new WrappedJasperFxCommand(_serviceProvider, commandType);
}
-
+
return (ActivatorUtilities.CreateInstance(_serviceProvider, commandType) as IJasperFxCommand)!;
}
+ [RequiresUnreferencedCode("Activator.CreateInstance(Type) requires the public parameterless constructor of modelType to survive trimming.")]
public object CreateModel(Type modelType)
{
return Activator.CreateInstance(modelType)!;
diff --git a/src/JasperFx/CommandLine/Descriptions/DescribeCommand.cs b/src/JasperFx/CommandLine/Descriptions/DescribeCommand.cs
index 4040df8f..1f2b414b 100644
--- a/src/JasperFx/CommandLine/Descriptions/DescribeCommand.cs
+++ b/src/JasperFx/CommandLine/Descriptions/DescribeCommand.cs
@@ -1,3 +1,4 @@
+using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using JasperFx.CommandLine.TextualDisplays;
using JasperFx.Core;
@@ -148,7 +149,9 @@ public class ReferencedAssemblies : SystemPartBase
public ReferencedAssemblies() : base("Referenced Assemblies", new Uri("system://assemblies"))
{
}
-
+
+ [UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode",
+ Justification = "Diagnostic-only describe command. Trimmed assemblies simply drop from the listed output; nothing branches on the value.")]
public override Task WriteToConsole()
{
var description = new TextualDisplay("Referenced Assemblies");
diff --git a/src/JasperFx/CommandLine/Descriptions/DescriptionExtensions.cs b/src/JasperFx/CommandLine/Descriptions/DescriptionExtensions.cs
index 335d641a..b182a2a7 100644
--- a/src/JasperFx/CommandLine/Descriptions/DescriptionExtensions.cs
+++ b/src/JasperFx/CommandLine/Descriptions/DescriptionExtensions.cs
@@ -1,3 +1,4 @@
+using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection;
using Spectre.Console;
@@ -20,7 +21,7 @@ public static void AddSystemPart(this IServiceCollection services, ISystemPart d
///
///
///
- public static void AddSystemPart(this IServiceCollection services) where T : class, ISystemPart
+ public static void AddSystemPart<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(this IServiceCollection services) where T : class, ISystemPart
{
services.AddSingleton();
}
diff --git a/src/JasperFx/CommandLine/Help/UsageGraph.cs b/src/JasperFx/CommandLine/Help/UsageGraph.cs
index dbddceff..dfba5447 100644
--- a/src/JasperFx/CommandLine/Help/UsageGraph.cs
+++ b/src/JasperFx/CommandLine/Help/UsageGraph.cs
@@ -1,4 +1,5 @@
-using System.Linq.Expressions;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq.Expressions;
using System.Reflection;
using JasperFx.CommandLine.Parsing;
using JasperFx.Core;
@@ -14,6 +15,8 @@ public class UsageGraph
private readonly IList _usages = new List();
private readonly Lazy> _validUsages;
+ [RequiresUnreferencedCode("Walks commandType to derive its input type and reads input-type properties / fields via InputParser.GetHandlers. The source-generated handler registry (GeneratedParserRegistry) is used when present.")]
+ [RequiresDynamicCode("InputParser.BuildHandler closes generic List for enumerable arguments / flags when falling back from the generated registry.")]
public UsageGraph(Type commandType)
{
_inputType = commandType.FindInterfaceThatCloses(typeof(IJasperFxCommand<>))!.GetTypeInfo()
@@ -63,6 +66,7 @@ public IEnumerable Flags
public string Description { get; private set; }
+ [RequiresUnreferencedCode("Delegates to ICommandCreator.CreateModel which instantiates _inputType via reflection.")]
public object BuildInput(Queue tokens, ICommandCreator creator)
{
var model = creator.CreateModel(_inputType);
diff --git a/src/JasperFx/CommandLine/ICommandCreator.cs b/src/JasperFx/CommandLine/ICommandCreator.cs
index 754e3227..0894d47a 100644
--- a/src/JasperFx/CommandLine/ICommandCreator.cs
+++ b/src/JasperFx/CommandLine/ICommandCreator.cs
@@ -1,11 +1,26 @@
-namespace JasperFx.CommandLine;
+using System.Diagnostics.CodeAnalysis;
+
+namespace JasperFx.CommandLine;
///
/// Service locator for command types. The default just uses Activator.CreateInstance().
-/// Can be used to plug in IoC construction in JasperFx applications
+/// Can be used to plug in IoC construction in JasperFx applications.
///
+///
+/// Implementations resolve concrete and input model
+/// types reflectively. The annotations propagate the requirement that callers
+/// either supply types whose constructors / properties survive trimming, or
+/// opt in via the published [RequiresUnreferencedCode] annotation.
+/// AOT/trim-clean apps consume commands through the source-generated manifest
+/// (see ) rather
+/// than reflective scanning, so this interface's annotations are the precise
+/// punch-list AOT consumers see when they call into the reflective path.
+///
public interface ICommandCreator
{
+ [RequiresUnreferencedCode("CreateCommand instantiates commandType via reflection (Activator.CreateInstance or DI). Public constructors and InjectService properties must survive trimming.")]
IJasperFxCommand CreateCommand(Type commandType);
+
+ [RequiresUnreferencedCode("CreateModel instantiates modelType via reflection (Activator.CreateInstance). Public parameterless constructor must survive trimming.")]
object CreateModel(Type modelType);
}
\ No newline at end of file
diff --git a/src/JasperFx/CommandLine/ICommandFactory.cs b/src/JasperFx/CommandLine/ICommandFactory.cs
index dc30b2dc..fd2a92d7 100644
--- a/src/JasperFx/CommandLine/ICommandFactory.cs
+++ b/src/JasperFx/CommandLine/ICommandFactory.cs
@@ -1,19 +1,36 @@
-using System.Reflection;
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
using Microsoft.Extensions.Hosting;
namespace JasperFx.CommandLine;
///
/// Interface that JasperFx uses to build command runs during execution. Can be used for custom
-/// command activation
+/// command activation.
///
+///
+/// The annotations propagate the reflective surface to implementations (default
+/// ) and to consumers (,
+/// ). AOT/trim-clean apps short-circuit
+/// the reflective discovery path via
+/// and the JasperFx.SourceGenerator-emitted DiscoveredCommands manifest.
+///
public interface ICommandFactory
{
+ [RequiresUnreferencedCode("BuildRun resolves command types reflectively via ICommandCreator; their public constructors and input-type properties must survive trimming.")]
+ [RequiresDynamicCode("Command input parsing closes generic List via MakeGenericType for enumerable arguments / flags.")]
CommandRun BuildRun(string commandLine);
+
+ [RequiresUnreferencedCode("BuildRun resolves command types reflectively via ICommandCreator; their public constructors and input-type properties must survive trimming.")]
+ [RequiresDynamicCode("Command input parsing closes generic List via MakeGenericType for enumerable arguments / flags.")]
CommandRun BuildRun(IEnumerable args);
+
+ [RequiresUnreferencedCode("Scans assembly.GetExportedTypes() for IJasperFxCommand. AOT-publishing apps should rely on the source-generated DiscoveredCommands manifest emitted by JasperFx.SourceGenerator.")]
void RegisterCommands(Assembly assembly);
+ [RequiresUnreferencedCode("BuildAllCommands dispatches each registered command type through ICommandCreator.CreateCommand, which instantiates the type reflectively.")]
IEnumerable BuildAllCommands();
+ [RequiresUnreferencedCode("Activator.CreateInstance(extensionType) requires public parameterless constructor of each extension to survive trimming.")]
void ApplyExtensions(IHostBuilder builder);
}
\ No newline at end of file
diff --git a/src/JasperFx/CommandLine/Internal/Conversion/ArrayConversion.cs b/src/JasperFx/CommandLine/Internal/Conversion/ArrayConversion.cs
index ebcc91aa..0fee02c9 100644
--- a/src/JasperFx/CommandLine/Internal/Conversion/ArrayConversion.cs
+++ b/src/JasperFx/CommandLine/Internal/Conversion/ArrayConversion.cs
@@ -1,3 +1,4 @@
+using System.Diagnostics.CodeAnalysis;
using JasperFx.Core;
namespace JasperFx.CommandLine.Internal.Conversion;
@@ -11,6 +12,8 @@ public ArrayConversion(Conversions conversions)
_conversions = conversions;
}
+ [UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode",
+ Justification = "Array.CreateInstance is only invoked through Conversions, which is itself reached only from annotated CommandLine entry points (CommandFactory.BuildRun, InputParser.GetHandlers / BuildHandler). AOT consumers see the annotation at those entry points.")]
public Func? ConverterFor(Type type)
{
if (!type.IsArray)
diff --git a/src/JasperFx/CommandLine/Internal/Conversion/StringConverterProvider.cs b/src/JasperFx/CommandLine/Internal/Conversion/StringConverterProvider.cs
index 85e1f4d4..a654a7fd 100644
--- a/src/JasperFx/CommandLine/Internal/Conversion/StringConverterProvider.cs
+++ b/src/JasperFx/CommandLine/Internal/Conversion/StringConverterProvider.cs
@@ -1,10 +1,13 @@
-using System.Linq.Expressions;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq.Expressions;
using JasperFx.Core.Reflection;
namespace JasperFx.CommandLine.Internal.Conversion;
public class StringConverterProvider : IConversionProvider
{
+ [UnconditionalSuppressMessage("Trimming", "IL2070:DynamicallyAccessedMembers",
+ Justification = "Looks up a public constructor that takes a single string. Only invoked through Conversions, reached from annotated CommandLine entry points (CommandFactory.BuildRun, InputParser.GetHandlers / BuildHandler). AOT consumers see the annotation at those entry points; types reached through input-model property scanning are preserved via the input type itself.")]
public Func? ConverterFor(Type type)
{
if (!type.IsConcrete())
diff --git a/src/JasperFx/CommandLine/JasperFxAsyncCommand.cs b/src/JasperFx/CommandLine/JasperFxAsyncCommand.cs
index 54cd2fb0..bc663de8 100644
--- a/src/JasperFx/CommandLine/JasperFxAsyncCommand.cs
+++ b/src/JasperFx/CommandLine/JasperFxAsyncCommand.cs
@@ -1,4 +1,5 @@
-using JasperFx.CommandLine.Help;
+using System.Diagnostics.CodeAnalysis;
+using JasperFx.CommandLine.Help;
namespace JasperFx.CommandLine;
@@ -8,6 +9,10 @@ namespace JasperFx.CommandLine;
///
public abstract class JasperFxAsyncCommand : IJasperFxCommand
{
+ [UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode",
+ Justification = "JasperFxAsyncCommand instances are constructed by ICommandCreator (annotated) from a Type that survives the trim graph because the command was reachable through CommandFactory.RegisterCommand[s]. The UsageGraph reads members of T (the user input type) — if T is reachable as the command's input, its members are preserved by the entry-point annotations.")]
+ [UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode",
+ Justification = "Same as IL2026 — the UsageGraph closes List for enumerable arguments via InputParser.BuildHandler, reached only through annotated CommandFactory entry points.")]
protected JasperFxAsyncCommand()
{
Usages = new UsageGraph(GetType());
diff --git a/src/JasperFx/CommandLine/JasperFxCommand.cs b/src/JasperFx/CommandLine/JasperFxCommand.cs
index 79b31582..e8ef65c5 100644
--- a/src/JasperFx/CommandLine/JasperFxCommand.cs
+++ b/src/JasperFx/CommandLine/JasperFxCommand.cs
@@ -1,4 +1,5 @@
-using JasperFx.CommandLine.Help;
+using System.Diagnostics.CodeAnalysis;
+using JasperFx.CommandLine.Help;
namespace JasperFx.CommandLine;
@@ -8,6 +9,10 @@ namespace JasperFx.CommandLine;
///
public abstract class JasperFxCommand : IJasperFxCommand
{
+ [UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode",
+ Justification = "JasperFxCommand instances are constructed by ICommandCreator (annotated) from a Type that survives the trim graph because the command was reachable through CommandFactory.RegisterCommand[s]. The UsageGraph reads members of T (the user input type) — if T is reachable as the command's input, its members are preserved by the entry-point annotations.")]
+ [UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode",
+ Justification = "Same as IL2026 — the UsageGraph closes List for enumerable arguments via InputParser.BuildHandler, reached only through annotated CommandFactory entry points.")]
protected JasperFxCommand()
{
Usages = new UsageGraph(GetType());
diff --git a/src/JasperFx/CommandLine/OaktonShims.cs b/src/JasperFx/CommandLine/OaktonShims.cs
index 45dc1733..cd2480ec 100644
--- a/src/JasperFx/CommandLine/OaktonShims.cs
+++ b/src/JasperFx/CommandLine/OaktonShims.cs
@@ -1,6 +1,7 @@
+using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using JasperFx;
using JasperFx.CommandLine.Commands;
@@ -47,6 +48,7 @@ public static class CommandLineHostingExtensions
///
///
[Obsolete("Prefer ApplyJasperFxExtensions")]
+ [RequiresUnreferencedCode("Scans extension assemblies for JasperFx commands. Apps targeting trim/AOT should pre-register commands via the source-generated DiscoveredCommands manifest.")]
public static IHostBuilder ApplyOaktonExtensions(this IHostBuilder builder)
{
return builder.ApplyJasperFxExtensions();
@@ -62,6 +64,8 @@ public static IHostBuilder ApplyOaktonExtensions(this IHostBuilder builder)
/// Optionally configure an expected "opts" file
///
[Obsolete("Prefer RunJasperFxCommands")]
+ [RequiresUnreferencedCode("Dispatches to commands resolved reflectively from the entry/extension assemblies. Apps targeting trim/AOT should pre-register commands via the source-generated DiscoveredCommands manifest.")]
+ [RequiresDynamicCode("Command input parsing closes generic List via MakeGenericType for enumerable arguments / flags.")]
public static Task RunOaktonCommands(this IHostBuilder builder, string[] args, string? optionsFile = null)
{
return builder.RunJasperFxCommands(args, optionsFile);
@@ -77,6 +81,8 @@ public static Task RunOaktonCommands(this IHostBuilder builder, string[] ar
/// Optionally configure an expected "opts" file
///
[Obsolete("Prefer RunJasperFxCommands")]
+ [RequiresUnreferencedCode("Dispatches to commands resolved reflectively from the entry/extension assemblies. Apps targeting trim/AOT should pre-register commands via the source-generated DiscoveredCommands manifest.")]
+ [RequiresDynamicCode("Command input parsing closes generic List via MakeGenericType for enumerable arguments / flags.")]
public static Task RunOaktonCommands(this IHost host, string[] args, string? optionsFile = null)
{
return host.RunJasperFxCommands(args, optionsFile);
diff --git a/src/JasperFx/CommandLine/Parsing/EnumerableArgument.cs b/src/JasperFx/CommandLine/Parsing/EnumerableArgument.cs
index b6a61b2a..0ee34e4d 100644
--- a/src/JasperFx/CommandLine/Parsing/EnumerableArgument.cs
+++ b/src/JasperFx/CommandLine/Parsing/EnumerableArgument.cs
@@ -1,4 +1,5 @@
using System.Collections;
+using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using JasperFx.CommandLine.Internal.Conversion;
using JasperFx.Core.Reflection;
@@ -16,6 +17,10 @@ public EnumerableArgument(MemberInfo member, Conversions conversions) : base(mem
_converter = conversions.FindConverter(member.GetMemberType()!.DetermineElementType()!)!;
}
+ [UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode",
+ Justification = "Reachable only through InputParser.BuildHandler, which carries RequiresUnreferencedCode + RequiresDynamicCode and is itself reached from the annotated CommandFactory.BuildRun / RegisterCommand entry points. The List closure is intrinsic and the trimmer keeps the closed generic when List is preserved.")]
+ [UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode",
+ Justification = "Same as IL2026 — the CloseAndBuildAs call closes List via MakeGenericType, which AOT consumers see through the annotated entry points.")]
public override bool Handle(object input, Queue tokens)
{
var elementType = _member.GetMemberType()!.GetGenericArguments().First();
diff --git a/src/JasperFx/CommandLine/Parsing/EnumerableFlag.cs b/src/JasperFx/CommandLine/Parsing/EnumerableFlag.cs
index c3483185..666d1fa5 100644
--- a/src/JasperFx/CommandLine/Parsing/EnumerableFlag.cs
+++ b/src/JasperFx/CommandLine/Parsing/EnumerableFlag.cs
@@ -1,4 +1,5 @@
using System.Collections;
+using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using JasperFx.CommandLine.Internal.Conversion;
using JasperFx.Core.Reflection;
@@ -15,6 +16,10 @@ public EnumerableFlag(MemberInfo member, Conversions conversions)
_member = member;
}
+ [UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode",
+ Justification = "Reachable only through InputParser.BuildHandler, which carries RequiresUnreferencedCode + RequiresDynamicCode and is itself reached from the annotated CommandFactory.BuildRun / RegisterCommand entry points. The List closure is intrinsic and the trimmer keeps the closed generic when List is preserved.")]
+ [UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode",
+ Justification = "Same as IL2026 — the CloseAndBuildAs call closes List via MakeGenericType, which AOT consumers see through the annotated entry points.")]
public override bool Handle(object input, Queue tokens)
{
var elementType = _member.GetMemberType()!.DetermineElementType()!;
diff --git a/src/JasperFx/CommandLine/Parsing/InputParser.cs b/src/JasperFx/CommandLine/Parsing/InputParser.cs
index 02759a96..ec1e4333 100644
--- a/src/JasperFx/CommandLine/Parsing/InputParser.cs
+++ b/src/JasperFx/CommandLine/Parsing/InputParser.cs
@@ -1,3 +1,4 @@
+using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Text.RegularExpressions;
using JasperFx.CommandLine.Internal.Conversion;
@@ -18,6 +19,8 @@ public static class InputParser
private static readonly Conversions _converter = new();
+ [RequiresUnreferencedCode("Reads inputType.GetProperties() + GetFields(); public properties (writable) and instance fields of inputType must survive trimming.")]
+ [RequiresDynamicCode("BuildHandler closes generic List for enumerable arguments / flags.")]
public static List GetHandlers(Type inputType)
{
var properties = inputType.GetProperties().Where(prop => prop.CanWrite);
@@ -30,6 +33,8 @@ public static List GetHandlers(Type inputType)
.Select(BuildHandler).ToList();
}
+ [RequiresUnreferencedCode("Reflects over member's declaring type to build a token handler; trimmer may remove members the handler depends on.")]
+ [RequiresDynamicCode("Closes generic List via MakeGenericType for enumerable arguments and flags.")]
public static ITokenHandler BuildHandler(MemberInfo member)
{
var memberType = member.GetMemberType();
diff --git a/src/JasperFx/CommandLineHostingExtensions.cs b/src/JasperFx/CommandLineHostingExtensions.cs
index a36ecbf2..6211868e 100644
--- a/src/JasperFx/CommandLineHostingExtensions.cs
+++ b/src/JasperFx/CommandLineHostingExtensions.cs
@@ -1,3 +1,4 @@
+using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using JasperFx.CommandLine;
using JasperFx.CommandLine.Commands;
@@ -19,6 +20,7 @@ public static class CommandLineHostingExtensions
///
///
///
+ [RequiresUnreferencedCode("Scans extension assemblies for JasperFx commands and instantiates extension types via Activator.CreateInstance. Apps targeting trim/AOT should emit the JasperFx.Generated.DiscoveredCommands manifest via JasperFx.SourceGenerator.")]
public static IHostBuilder ApplyJasperFxExtensions(this IHostBuilder builder)
{
var factory = new CommandFactory();
@@ -37,6 +39,8 @@ public static IHostBuilder ApplyJasperFxExtensions(this IHostBuilder builder)
///
/// Optionally configure an expected "opts" file
///
+ [RequiresUnreferencedCode("Dispatches to commands resolved reflectively from the entry/extension assemblies. Apps targeting trim/AOT should pre-register commands via the source-generated DiscoveredCommands manifest.")]
+ [RequiresDynamicCode("Command input parsing closes generic List via MakeGenericType for enumerable arguments / flags.")]
public static Task RunJasperFxCommands(this IHostBuilder builder, string[] args, string? optionsFile = null)
{
return execute(builder, Assembly.GetEntryAssembly(), args, optionsFile);
@@ -51,6 +55,8 @@ public static Task RunJasperFxCommands(this IHostBuilder builder, string[]
///
/// Optionally configure an expected "opts" file
///
+ [RequiresUnreferencedCode("Dispatches to commands resolved reflectively from the entry/extension assemblies. Apps targeting trim/AOT should pre-register commands via the source-generated DiscoveredCommands manifest.")]
+ [RequiresDynamicCode("Command input parsing closes generic List via MakeGenericType for enumerable arguments / flags.")]
public static int RunJasperFxCommandsSynchronously(this IHostBuilder builder, string[] args, string? optionsFile = null)
{
return execute(builder, Assembly.GetEntryAssembly(), args, optionsFile).GetAwaiter().GetResult();
@@ -65,6 +71,8 @@ public static int RunJasperFxCommandsSynchronously(this IHostBuilder builder, st
///
/// Optionally configure an expected "opts" file
///
+ [RequiresUnreferencedCode("Dispatches to commands resolved reflectively from the entry/extension assemblies. Apps targeting trim/AOT should pre-register commands via the source-generated DiscoveredCommands manifest.")]
+ [RequiresDynamicCode("Command input parsing closes generic List via MakeGenericType for enumerable arguments / flags.")]
public static Task RunJasperFxCommands(this IHost host, string[] args, string? optionsFile = null)
{
return execute(new PreBuiltHostBuilder(host), Assembly.GetEntryAssembly(), args, optionsFile);
@@ -79,6 +87,8 @@ public static Task RunJasperFxCommands(this IHost host, string[] args, stri
///
/// Optionally configure an expected "opts" file
///
+ [RequiresUnreferencedCode("Dispatches to commands resolved reflectively from the entry/extension assemblies. Apps targeting trim/AOT should pre-register commands via the source-generated DiscoveredCommands manifest.")]
+ [RequiresDynamicCode("Command input parsing closes generic List via MakeGenericType for enumerable arguments / flags.")]
public static int RunJasperFxCommandsSynchronously(this IHost host, string[] args, string? optionsFile = null)
{
return execute(new PreBuiltHostBuilder(host), Assembly.GetEntryAssembly(), args, optionsFile).GetAwaiter().GetResult();
@@ -103,6 +113,7 @@ internal static string[] ApplyArgumentDefaults(this string[] args, string? optio
return args;
}
+ [RequiresUnreferencedCode("Scans the entry/extension assemblies for IJasperFxCommand types via Assembly.GetExportedTypes().")]
internal static void ApplyFactoryDefaults(this CommandFactory factory, Assembly? applicationAssembly)
{
factory.RegisterCommands(typeof(RunCommand).GetTypeInfo().Assembly);
@@ -115,6 +126,8 @@ internal static void ApplyFactoryDefaults(this CommandFactory factory, Assembly?
factory.RegisterCommandsFromExtensionAssemblies();
}
+ [RequiresUnreferencedCode("Builds a CommandExecutor that resolves commands reflectively.")]
+ [RequiresDynamicCode("CommandExecutor.ExecuteAsync closes generic List via MakeGenericType for enumerable arguments.")]
private static Task execute(IHostBuilder runtimeSource, Assembly? applicationAssembly, string[] args,
string? optionsFile)
{
@@ -124,6 +137,7 @@ private static Task execute(IHostBuilder runtimeSource, Assembly? applicati
return commandExecutor.ExecuteAsync(args);
}
+ [RequiresUnreferencedCode("Builds the default ActivatorCommandCreator-backed factory and walks command-instance properties via reflection.")]
private static CommandExecutor buildExecutor(IHostBuilder source, Assembly? applicationAssembly)
{
if (JasperFxEnvironment.AutoStartHost && source is PreBuiltHostBuilder b)
@@ -169,6 +183,8 @@ private static CommandExecutor buildExecutor(IHostBuilder source, Assembly? appl
/// An already built IHost
///
///
+ [RequiresUnreferencedCode("Dispatches to commands resolved reflectively from the entry/extension assemblies. Apps targeting trim/AOT should pre-register commands via the source-generated DiscoveredCommands manifest.")]
+ [RequiresDynamicCode("Command input parsing closes generic List via MakeGenericType for enumerable arguments / flags.")]
public static Task RunJasperFxCommands(this IHost host, string[] args)
{
// Workaround for IISExpress / VS2019 erroneously putting crap arguments