Skip to content

Commit

Permalink
Initial Trimming Support
Browse files Browse the repository at this point in the history
* Marked Spectre.Console and Spectre.Console.Cli as trimmable to enable warnings
* Marked Demo.cspoj to be PublishedAOT and to include all trim warnings

Spectre.Console
* Marked Spectre.Console methods that used TypeConverter with suppressed messages
* Marked ExceptionFormatter as requiring dynamic code, and added a fallback for AOT scenarios.

Spectre.Console.Cli
* Settings were discovered automatically with reflection. Added a new method for adding commands that allows explicit configuration of the settings to prevent them from being trimmed. Marked the old method as requiring dynamic code and pointed users to the new overload
* Added attributes to the default TypeRegister to mark the types as being instantiated dynamically.
* Suppressed a whole slew of warnings with command bulding and running the commands. With the commands and their settings being marked as having DynamicallyAccessedMembers this, theoretically, should work without error.

Biggest worry is in CommandParameterComparer. The MetadataToken is stripped when published as AOT so it can't be used. A simple equals seems to work the same, but I'm not sure the historical reason MetadataToken is used here.
  • Loading branch information
phil-scott-78 committed Apr 5, 2024
1 parent fc0b553 commit 3ffa521
Show file tree
Hide file tree
Showing 46 changed files with 440 additions and 45 deletions.
1 change: 1 addition & 0 deletions examples/Cli/Demo/Commands/Run/RunCommand.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using Demo.Utilities;
using Spectre.Console.Cli;

Expand Down
5 changes: 4 additions & 1 deletion examples/Cli/Demo/Demo.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
<ExampleGroup>Cli</ExampleGroup>
<ExampleVisible>false</ExampleVisible>
</PropertyGroup>

<PropertyGroup>
<PublishAot>true</PublishAot>
<TrimmerSingleWarn>false</TrimmerSingleWarn>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Spectre.Console.Cli\Spectre.Console.Cli.csproj" />
</ItemGroup>
Expand Down
11 changes: 7 additions & 4 deletions examples/Cli/Demo/Program.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using Demo.Commands;
using Demo.Commands.Add;
using Demo.Commands.Run;
Expand All @@ -6,30 +7,32 @@

namespace Demo;


public static class Program
{
public static int Main(string[] args)
{
var app = new CommandApp();
app.Configure(config =>
{
config.PropagateExceptions();
config.SetApplicationName("fake-dotnet");
config.ValidateExamples();
config.AddExample("run", "--no-build");

// Run
config.AddCommand<RunCommand>("run");
config.AddCommand<RunCommand, RunCommand.Settings>("run");

// Add
config.AddBranch<AddSettings>("add", add =>
{
add.SetDescription("Add a package or reference to a .NET project");
add.AddCommand<AddPackageCommand>("package");
add.AddCommand<AddReferenceCommand>("reference");
add.AddCommand<AddPackageCommand, AddPackageCommand.Settings>("package");
add.AddCommand<AddReferenceCommand, AddReferenceCommand.Settings>("reference");
});

// Serve
config.AddCommand<ServeCommand>("serve")
config.AddCommand<ServeCommand, ServeCommand.Settings>("serve")
.WithExample("serve", "-o", "firefox")
.WithExample("serve", "--port", "80", "-o", "firefox");
});
Expand Down
6 changes: 4 additions & 2 deletions examples/Cli/Demo/Utilities/SettingsDumper.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
using System.Diagnostics.CodeAnalysis;
using Spectre.Console;
using Spectre.Console.Cli;

namespace Demo.Utilities;

public static class SettingsDumper
{
public static void Dump(CommandSettings settings)
public static void Dump<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(T settings)
where T : CommandSettings
{
var table = new Table().RoundedBorder();
table.AddColumn("[grey]Name[/]");
table.AddColumn("[grey]Value[/]");

var properties = settings.GetType().GetProperties();
var properties = typeof(T).GetProperties();
foreach (var property in properties)
{
var value = property.GetValue(settings)
Expand Down
3 changes: 2 additions & 1 deletion examples/Cli/Injection/Infrastructure/TypeRegistrar.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection;
using Spectre.Console.Cli;

Expand All @@ -18,7 +19,7 @@ public ITypeResolver Build()
return new TypeResolver(_builder.BuildServiceProvider());
}

public void Register(Type service, Type implementation)
public void Register(Type service, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type implementation)
{
_builder.AddSingleton(service, implementation);
}
Expand Down
3 changes: 3 additions & 0 deletions examples/Cli/Injection/Injection.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
<ExampleVisible>false</ExampleVisible>
</PropertyGroup>

<PropertyGroup>
<PublishAot>true</PublishAot>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
</ItemGroup>
Expand Down
20 changes: 19 additions & 1 deletion src/Spectre.Console.Cli/CommandApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,12 @@ public void Configure(Action<IConfigurator> configuration)
/// </summary>
/// <typeparam name="TCommand">The command type.</typeparam>
/// <returns>A <see cref="DefaultCommandConfigurator"/> that can be used to configure the default command.</returns>
public DefaultCommandConfigurator SetDefaultCommand<TCommand>()
public DefaultCommandConfigurator SetDefaultCommand<
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
#endif
TCommand
>()
where TCommand : class, ICommand
{
return new DefaultCommandConfigurator(GetConfigurator().SetDefaultCommand<TCommand>());
Expand All @@ -63,6 +68,11 @@ public int Run(IEnumerable<string> args)
/// </summary>
/// <param name="args">The arguments.</param>
/// <returns>The exit code from the executed command.</returns>
#if NET6_0_OR_GREATER
// we have a handful of helper classes that we create dynamically, make sure we mark them
// as dynamic dependencies to force their inclusion.
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(FlagValue<>))]
#endif
public async Task<int> RunAsync(IEnumerable<string> args)
{
try
Expand All @@ -73,9 +83,17 @@ public async Task<int> RunAsync(IEnumerable<string> args)
_configurator.AddBranch(CliConstants.Commands.Branch, cli =>
{
cli.HideBranch();

// need to explicitly add the settings for NET6 or greater to support trimming
#if NET6_0_OR_GREATER
cli.AddCommand<VersionCommand, VersionCommand.Settings>(CliConstants.Commands.Version);
cli.AddCommand<XmlDocCommand, XmlDocCommand.Settings>(CliConstants.Commands.XmlDoc);
cli.AddCommand<ExplainCommand, ExplainCommand.Settings>(CliConstants.Commands.Explain);
#else
cli.AddCommand<VersionCommand>(CliConstants.Commands.Version);
cli.AddCommand<XmlDocCommand>(CliConstants.Commands.XmlDoc);
cli.AddCommand<ExplainCommand>(CliConstants.Commands.Explain);
#endif
});

_executed = true;
Expand Down
7 changes: 6 additions & 1 deletion src/Spectre.Console.Cli/CommandAppOfT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ namespace Spectre.Console.Cli;
/// The entry point for a command line application with a default command.
/// </summary>
/// <typeparam name="TDefaultCommand">The type of the default command.</typeparam>
public sealed class CommandApp<TDefaultCommand> : ICommandApp
public sealed class CommandApp<
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
#endif
TDefaultCommand
> : ICommandApp
where TDefaultCommand : class, ICommand
{
private readonly CommandApp _app;
Expand Down
24 changes: 20 additions & 4 deletions src/Spectre.Console.Cli/ConfiguratorExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ public static IConfigurator SetHelpProvider(this IConfigurator configurator, IHe
/// <param name="configurator">The configurator.</param>
/// <typeparam name="T">The type of the help provider to instantiate at runtime and use.</typeparam>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator SetHelpProvider<T>(this IConfigurator configurator)
public static IConfigurator SetHelpProvider<
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
#endif
T>(this IConfigurator configurator)
where T : IHelpProvider
{
if (configurator == null)
Expand Down Expand Up @@ -261,7 +265,11 @@ public static IBranchConfigurator AddBranch(
/// <param name="name">The name of the command branch.</param>
/// <param name="action">The command branch configuration.</param>
/// <returns>A branch configurator that can be used to configure the branch further.</returns>
public static IBranchConfigurator AddBranch<TSettings>(
public static IBranchConfigurator AddBranch<
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
#endif
TSettings>(
this IConfigurator<TSettings> configurator,
string name,
Action<IConfigurator<TSettings>> action)
Expand Down Expand Up @@ -323,7 +331,11 @@ public static ICommandConfigurator AddAsyncDelegate(
/// <param name="name">The name of the command.</param>
/// <param name="func">The delegate to execute as part of command execution.</param>
/// <returns>A command configurator that can be used to configure the command further.</returns>
public static ICommandConfigurator AddDelegate<TSettings>(
public static ICommandConfigurator AddDelegate<
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
#endif
TSettings>(
this IConfigurator<TSettings> configurator,
string name,
Func<CommandContext, int> func)
Expand All @@ -345,7 +357,11 @@ public static ICommandConfigurator AddDelegate<TSettings>(
/// <param name="name">The name of the command.</param>
/// <param name="func">The delegate to execute as part of command execution.</param>
/// <returns>A command configurator that can be used to configure the command further.</returns>
public static ICommandConfigurator AddAsyncDelegate<TSettings>(
public static ICommandConfigurator AddAsyncDelegate<
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
#endif
TSettings>(
this IConfigurator<TSettings> configurator,
string name,
Func<CommandContext, Task<int>> func)
Expand Down
2 changes: 1 addition & 1 deletion src/Spectre.Console.Cli/FlagValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ namespace Spectre.Console.Cli;
/// <summary>
/// Implementation of a flag with an optional value.
/// </summary>
/// <typeparam name="T">The flag's element type.</typeparam>
/// <typeparam name="T">The flag's element type.</typeparam>]
public sealed class FlagValue<T> : IFlagValue
{
/// <summary>
Expand Down
46 changes: 42 additions & 4 deletions src/Spectre.Console.Cli/IConfigurator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ public interface IConfigurator
/// Sets the help provider for the application.
/// </summary>
/// <typeparam name="T">The type of the help provider to instantiate at runtime and use.</typeparam>
public void SetHelpProvider<T>()
public void SetHelpProvider<
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]
#endif
T>()
where T : IHelpProvider;

/// <summary>
Expand All @@ -35,17 +39,41 @@ public void SetHelpProvider<T>()
/// <typeparam name="TCommand">The command type.</typeparam>
/// <param name="name">The name of the command.</param>
/// <returns>A command configurator that can be used to configure the command further.</returns>
#if NET7_0_OR_GREATER
[RequiresDynamicCode(TrimWarnings.AddCommandShouldBeExplicitAboutSettings)]
#endif
ICommandConfigurator AddCommand<TCommand>(string name)
where TCommand : class, ICommand;

#if NET6_0_OR_GREATER
/// <summary>
/// Adds a command.
/// </summary>
/// <typeparam name="TCommand">The command type.</typeparam>
/// <typeparam name="TSettings">The command settings type.</typeparam>
/// <param name="name">The name of the command.</param>
/// <returns>A command configurator that can be used to configure the command further.</returns>
ICommandConfigurator AddCommand<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TCommand,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] TSettings>(string name)
where TCommand : class, ICommand
where TSettings : CommandSettings
;
#endif

/// <summary>
/// Adds a command that executes a delegate.
/// </summary>
/// <typeparam name="TSettings">The command setting type.</typeparam>
/// <param name="name">The name of the command.</param>
/// <param name="func">The delegate to execute as part of command execution.</param>
/// <returns>A command configurator that can be used to configure the command further.</returns>
ICommandConfigurator AddDelegate<TSettings>(string name, Func<CommandContext, TSettings, int> func)
ICommandConfigurator AddDelegate<
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
#endif
TSettings
>(string name, Func<CommandContext, TSettings, int> func)
where TSettings : CommandSettings;

/// <summary>
Expand All @@ -55,7 +83,12 @@ ICommandConfigurator AddDelegate<TSettings>(string name, Func<CommandContext, TS
/// <param name="name">The name of the command.</param>
/// <param name="func">The delegate to execute as part of command execution.</param>
/// <returns>A command configurator that can be used to configure the command further.</returns>
ICommandConfigurator AddAsyncDelegate<TSettings>(string name, Func<CommandContext, TSettings, Task<int>> func)
ICommandConfigurator AddAsyncDelegate<
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
#endif
TSettings
>(string name, Func<CommandContext, TSettings, Task<int>> func)
where TSettings : CommandSettings;

/// <summary>
Expand All @@ -65,6 +98,11 @@ ICommandConfigurator AddAsyncDelegate<TSettings>(string name, Func<CommandContex
/// <param name="name">The name of the command branch.</param>
/// <param name="action">The command branch configurator.</param>
/// <returns>A branch configurator that can be used to configure the branch further.</returns>
IBranchConfigurator AddBranch<TSettings>(string name, Action<IConfigurator<TSettings>> action)
IBranchConfigurator AddBranch<
#if NET6_0_OR_GREATER
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
#endif
TSettings
>(string name, Action<IConfigurator<TSettings>> action)
where TSettings : CommandSettings;
}
Loading

0 comments on commit 3ffa521

Please sign in to comment.