spectreconsole / spectre.console

A .NET library that makes it easier to create beautiful console applications.
https://spectreconsole.net
MIT License
9.17k stars 472 forks source link

Multiple errors when trying to ILLink/Trim a console app that uses Spectre. #955

Open azchohfi opened 2 years ago

azchohfi commented 2 years ago

Information

Describe the bug When trying to use Spectre on a console app that targets .NET6, and trying to build a self-contained library, I get multiple ILLink errors, such as IL2070, IL2072, IL2067, IL2072, IL2026, and IL2087.

To Reproduce Reference Spectre.Console 0.44.0 from a console app, use something from Spetre, and build with PublishTrimmed=True, PublishSingleFile=True, "--self-contained -r win-x64 -f net6.0". Multiple errors.

Expected behavior Builds and works fine.

Additional context

/_/src/Spectre.Console/Cli/Internal/CommandBinder.cs(10,37): Trim analysis error IL2070: Spectre.Console.Cli.CommandBinder.Bind(CommandTree,Type,ITypeResolver): 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicConstructors' in call to 'System.Type.GetConstructors()'. The parameter 'settingsType' of method 'Spectre.Console.Cli.CommandBinder.Bind(CommandTree,Type,ITypeResolver)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.
/_/src/Spectre.Console/Cli/Internal/Binding/CommandConstructorBinder.cs(34,9): Trim analysis error IL2072: Spectre.Console.Cli.CommandConstructorBinder.CreateSettings(CommandValueLookup,ConstructorInfo,ITypeResolver): '#0' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicConstructors' in call to 'System.Activator.CreateInstance(Type,Object[])'. The return value of method 'System.Reflection.MemberInfo.DeclaringType.get' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.
/_/src/Spectre.Console/Cli/Internal/Modelling/CommandParameter.cs(76,17): Trim analysis error IL2072: Spectre.Console.Cli.CommandParameter.Assign(CommandSettings,ITypeResolver,Object): '#0' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicParameterlessConstructor' in call to 'System.Activator.CreateInstance(Type)'. The return value of method 'Spectre.Console.Cli.PairDeconstructorAttribute.Type.get' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.
/_/src/Spectre.Console/Cli/Internal/Modelling/CommandParameter.cs(123,17): Trim analysis error IL2072: Spectre.Console.Cli.CommandParameter.Assign(CommandSettings,ITypeResolver,Object): '#0' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicParameterlessConstructor' in call to 'System.Activator.CreateInstance(Type)'. The return value of method 'Spectre.Console.Cli.CommandParameter.ParameterType.get' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.
/_/src/Spectre.Console/Cli/Internal/Binding/CommandPropertyBinder.cs(34,9): Trim analysis error IL2067: Spectre.Console.Cli.CommandPropertyBinder.CreateSettings(ITypeResolver,Type): '#0' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicParameterlessConstructor' in call to 'System.Activator.CreateInstance(Type)'. The parameter 'settingsType' of method 'Spectre.Console.Cli.CommandPropertyBinder.CreateSettings(ITypeResolver,Type)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.
/_/src/Spectre.Console/Cli/Internal/Binding/CommandValueBinder.cs(97,13): Trim analysis error IL2072: Spectre.Console.Cli.CommandValueBinder.GetFlag(CommandParameter,Object): '#0' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicParameterlessConstructor' in call to 'System.Activator.CreateInstance(Type)'. The return value of method 'Spectre.Console.Cli.CommandParameter.ParameterType.get' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.
/_/src/Spectre.Console/Cli/Internal/Binding/CommandValueBinder.cs(48,13): Trim analysis error IL2072: Spectre.Console.Cli.CommandValueBinder.GetLookup(CommandParameter,ITypeResolver,Object): '#0' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicParameterlessConstructor' in call to 'System.Activator.CreateInstance(Type)'. The return value of method 'Spectre.Console.Cli.PairDeconstructorAttribute.Type.get' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.
/_/src/Spectre.Console/Cli/Internal/Binding/CommandValueResolver.cs(133,17): Trim analysis error IL2026: Spectre.Console.Cli.CommandValueResolver.GetConverter(CommandValueLookup,CommandValueBinder,ITypeResolver,CommandParameter): Using member 'System.ComponentModel.TypeDescriptor.GetConverter(Type)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. Generic TypeConverters may require the generic types to be annotated. For example, NullableConverter requires the underlying type to be DynamicallyAccessedMembers All.
/_/src/Spectre.Console/Cli/Internal/Binding/CommandValueResolver.cs(133,17): Trim analysis error IL2072: Spectre.Console.Cli.CommandValueResolver.GetConverter(CommandValueLookup,CommandValueBinder,ITypeResolver,CommandParameter): 'type' argument does not satisfy 'DynamicallyAccessedMemberTypes.All' in call to 'System.ComponentModel.TypeDescriptor.GetConverter(Type)'. The return value of method 'System.Type.GetElementType()' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.
/_/src/Spectre.Console/Cli/Internal/Binding/CommandValueResolver.cs(153,17): Trim analysis error IL2072: Spectre.Console.Cli.CommandValueResolver.GetConverter(CommandValueLookup,CommandValueBinder,ITypeResolver,CommandParameter): 'type' argument does not satisfy 'DynamicallyAccessedMemberTypes.All' in call to 'System.ComponentModel.TypeDescriptor.GetConverter(Type)'. The return value of method 'Spectre.Console.Cli.IFlagValue.Type.get' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.
/_/src/Spectre.Console/Cli/Internal/Binding/CommandValueResolver.cs(153,17): Trim analysis error IL2026: Spectre.Console.Cli.CommandValueResolver.GetConverter(CommandValueLookup,CommandValueBinder,ITypeResolver,CommandParameter): Using member 'System.ComponentModel.TypeDescriptor.GetConverter(Type)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. Generic TypeConverters may require the generic types to be annotated. For example, NullableConverter requires the underlying type to be DynamicallyAccessedMembers All.
/_/src/Spectre.Console/Cli/Internal/Binding/CommandValueResolver.cs(156,13): Trim analysis error IL2026: Spectre.Console.Cli.CommandValueResolver.GetConverter(CommandValueLookup,CommandValueBinder,ITypeResolver,CommandParameter): Using member 'System.ComponentModel.TypeDescriptor.GetConverter(Type)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. Generic TypeConverters may require the generic types to be annotated. For example, NullableConverter requires the underlying type to be DynamicallyAccessedMembers All.
/_/src/Spectre.Console/Cli/Internal/Binding/CommandValueResolver.cs(156,13): Trim analysis error IL2072: Spectre.Console.Cli.CommandValueResolver.GetConverter(CommandValueLookup,CommandValueBinder,ITypeResolver,CommandParameter): 'type' argument does not satisfy 'DynamicallyAccessedMemberTypes.All' in call to 'System.ComponentModel.TypeDescriptor.GetConverter(Type)'. The return value of method 'Spectre.Console.Cli.CommandParameter.ParameterType.get' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.
/_/src/Spectre.Console/Cli/Internal/Binding/CommandValueResolver.cs(34,21): Trim analysis error IL2072: Spectre.Console.Cli.CommandValueResolver.GetParameterValues(CommandTree,ITypeResolver): '#0' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicParameterlessConstructor' in call to 'System.Activator.CreateInstance(Type)'. The return value of method 'Spectre.Console.Cli.CommandParameter.ParameterType.get' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.
/_/src/Spectre.Console/Cli/Internal/Configuration/ConfigurationHelper.cs(21,40): Trim analysis error IL2070: Spectre.Console.Cli.ConfigurationHelper.GetGenericTypeArguments(Type,Type,Type[]&): 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.Interfaces' in call to 'System.Type.GetInterfaces()'. The parameter 'type' of method 'Spectre.Console.Cli.ConfigurationHelper.GetGenericTypeArguments(Type,Type,Type[]&)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.
/_/src/Spectre.Console/Cli/Internal/DefaultPairDeconstructor.cs(63,9): Trim analysis error IL2067: Spectre.Console.Cli.DefaultPairDeconstructor.GetConverter(Type): 'type' argument does not satisfy 'DynamicallyAccessedMemberTypes.All' in call to 'System.ComponentModel.TypeDescriptor.GetConverter(Type)'. The parameter 'type' of method 'Spectre.Console.Cli.DefaultPairDeconstructor.GetConverter(Type)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.
/_/src/Spectre.Console/Cli/Internal/DefaultPairDeconstructor.cs(63,9): Trim analysis error IL2026: Spectre.Console.Cli.DefaultPairDeconstructor.GetConverter(Type): Using member 'System.ComponentModel.TypeDescriptor.GetConverter(Type)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. Generic TypeConverters may require the generic types to be annotated. For example, NullableConverter requires the underlying type to be DynamicallyAccessedMembers All.
/_/src/Spectre.Console/Cli/Internal/DefaultPairDeconstructor.cs(33,17): Trim analysis error IL2067: Spectre.Console.Cli.DefaultPairDeconstructor.Spectre.Console.Cli.IPairDeconstructor.Deconstruct(ITypeResolver,Type,Type,String): '#0' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicParameterlessConstructor' in call to 'System.Activator.CreateInstance(Type)'. The parameter 'valueType' of method 'Spectre.Console.Cli.DefaultPairDeconstructor.Spectre.Console.Cli.IPairDeconstructor.Deconstruct(ITypeResolver,Type,Type,String)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.
/_/src/Spectre.Console/Widgets/Exceptions/ExceptionFormatter.cs(408,77): Trim analysis error IL2070: Spectre.Console.ExceptionFormatter.<TryResolveStateMachineMethod>g__GetDeclaredMethods|13_0(IReflect): 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods', 'DynamicallyAccessedMemberTypes.NonPublicMethods' in call to 'System.Reflection.IReflect.GetMethods(BindingFlags)'. The parameter 'type' of method 'Spectre.Console.ExceptionFormatter.<TryResolveStateMachineMethod>g__GetDeclaredMethods|13_0(IReflect)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.
/_/src/Spectre.Console/Cli/Internal/Composition/Activators.cs(111,37): Trim analysis error IL2070: Spectre.Console.Cli.ReflectionActivator.GetGreediestConstructor(Type): 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicConstructors' in call to 'System.Type.GetConstructors()'. The parameter 'type' of method 'Spectre.Console.Cli.ReflectionActivator.GetGreediestConstructor(Type)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.
/_/src/Spectre.Console/Internal/TypeConverterHelper.cs(26,9): Trim analysis error IL2087: Spectre.Console.TypeConverterHelper.GetTypeConverter<T>(): 'type' argument does not satisfy 'DynamicallyAccessedMemberTypes.All' in call to 'System.ComponentModel.TypeDescriptor.GetConverter(Type)'. The generic parameter 'T' of 'Spectre.Console.TypeConverterHelper.GetTypeConverter<T>()' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.
/_/src/Spectre.Console/Internal/TypeConverterHelper.cs(26,9): Trim analysis error IL2026: Spectre.Console.TypeConverterHelper.GetTypeConverter<T>(): Using member 'System.ComponentModel.TypeDescriptor.GetConverter(Type)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. Generic TypeConverters may require the generic types to be annotated. For example, NullableConverter requires the underlying type to be DynamicallyAccessedMembers All.
/_/src/Spectre.Console/Cli/Internal/TypeResolverAdapter.cs(28,13): Trim analysis error IL2067: Spectre.Console.Cli.TypeResolverAdapter.Resolve(Type): '#0' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicParameterlessConstructor' in call to 'System.Activator.CreateInstance(Type)'. The parameter 'type' of method 'Spectre.Console.Cli.TypeResolverAdapter.Resolve(Type)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.

Please upvote :+1: this issue if you are interested in it.

patriksvensson commented 2 years ago

@azchohfi Not sure what the solution for this is tbh. Any pointers?

azchohfi commented 2 years ago

Oh, absolutely! This is the documentation that helps a lot: https://docs.microsoft.com/dotnet/core/deploying/trimming/trim-self-contained I gave this a quick try yesterday (basically setting EnableTrimAnalyzer=True and bubbling up the annotations). From my initial investigation, it seems like the biggest issue in SpectreConsole.Cli is that the CommandValueResolver.GetConverter method uses TypeDescriptor.GetConverter and Type.GetType. For Spectre.Console, the issue is at TypeConverterHelper.GetTypeConverter, which uses the same two methods. The problem is that the solution is to bubble up the annotations until you reach the public methods, but that would require annotating pretty much everything.... Still, there is probably a better way to crack this (refactor/re-architecture), but it might be a major version bump, which I didn't want to start thinking without proper discussion with the community.

azchohfi commented 2 years ago

Btw, something like this can be used as a good test: https://github.com/dotnet/command-line-api/blob/55dbf39074a7563e25e3409c9feb7afb8c8d2cc4/src/System.CommandLine.Tests/CompilationTests.cs

patriksvensson commented 2 years ago

@azchohfi Thanks for the suggestion. This is something we should consider for the 1.0.0 release of Spectre.Console.Cli.

We've recently (in 0.45.0) moved the CLI parts out to its own NuGet package (Spectre.Console.Cli), so if you only need the console parts (Spectre.Console), you should be able to trim that now.

azchohfi commented 2 years ago

Oh, interesting. I'll try that!

azchohfi commented 2 years ago

This version is MUCH better, but still have the same issues as before on that assembly.

/_/src/Spectre.Console/Internal/TypeConverterHelper.cs(26,9): Trim analysis error IL2087: Spectre.Console.TypeConverterHelper.GetTypeConverter<T>(): 'type' argument does not satisfy 'DynamicallyAccessedMemberTypes.All' in call to 'System.ComponentModel.TypeDescriptor.GetConverter(Type)'. The generic parameter 'T' of 'Spectre.Console.TypeConverterHelper.GetTypeConverter<T>()' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to.
/_/src/Spectre.Console/Internal/TypeConverterHelper.cs(26,9): Trim analysis error IL2026: Spectre.Console.TypeConverterHelper.GetTypeConverter<T>(): Using member 'System.ComponentModel.TypeDescriptor.GetConverter(Type)' which has 'RequiresUnreferencedCodeAttribute' can break functionality when trimming application code. Generic TypeConverters may require the generic types to be annotated. For example, NullableConverter requires the underlying type to be DynamicallyAccessedMembers All.
Gnbrkm41 commented 1 year ago

The size difference between trimmed and untrimmed console application, for me at least is night and day (66k before trimming, 12k after trimming). It would be lovely if Spectre can work with assembly trimming.

For example if I have a command like this:

internal sealed partial class DownloadCommand : AsyncCommand<Settings>
{
    public sealed class Settings : CommandSettings

and configure it like so:

var app = new CommandApp<DownloadCommand>();

Then the application produces a trim warning, and when executed regardless breaks down like so:

Unhandled exception. Spectre.Console.Cli.CommandRuntimeException: Could not get settings type for command of type 'AssetExtractor.DownloadCommand'.
   at Spectre.Console.Cli.ConfiguredCommand.FromType[TCommand](String, Boolean ) in /_/src/Spectre.Console.Cli/Internal/Configuration/ConfiguredCommand.cs:line 53
   at Spectre.Console.Cli.Configurator.SetDefaultCommand[TDefaultCommand]() in /_/src/Spectre.Console.Cli/Internal/Configuration/Configurator.cs:line 31
   at Spectre.Console.Cli.CommandApp`1..ctor(ITypeRegistrar ) in /_/src/Spectre.Console.Cli/CommandAppOfT.cs:line 19
   at AssetExtractor.Program.Main(String[]) in C:\Users\gotos\source\repos\AssetExtractor\AssetExtractor\Program.cs:line 16
   at AssetExtractor.Program.<Main>(String[] )

I assume the problem is hidden somewhere much more deeper though.

CyberSinh commented 1 year ago

While waiting for native support for Spectre trimming, it is possible to use <TrimMode>partial</TrimMode> in the project properties.

ricardoboss commented 11 months ago

In order to use Microsofts ServiceCollection as a type registrar and the ServiceCollectionServiceExtensions for it, the ITypeRegistrar.Register method needs a [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] for the Type implementation parameter.

Otherwise it is an error when trimming is enabled.

ricardoboss commented 9 months ago

@Gnbrkm41

...

I assume the problem is hidden somewhere much more deeper though.

You need to add the following attribute to your Progam.Main method for every command you have:

class Program {
    [DynamicDependency(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicNestedTypes, typeof(YourCommand))]

    public static void Main(string[] args) {
        ...
    }
}

That way the trimming won't throw away your command types or their nested settings classes.

Simonl9l commented 5 months ago

I suggest per https://github.com/spectreconsole/spectre.console/issues/955#issuecomment-1766147427 I would be best to add the attribute to the ITypeRegistrar interface since that is way the underlying DI implementation would need.