Open azchohfi opened 2 years ago
@azchohfi Not sure what the solution for this is tbh. Any pointers?
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.
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
@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.
Oh, interesting. I'll try that!
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.
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.
While waiting for native support for Spectre trimming, it is possible to use <TrimMode>partial</TrimMode>
in the project properties.
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.
@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.
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.
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
Please upvote :+1: this issue if you are interested in it.