Skip to content

Commit c821763

Browse files
authored
Merge pull request #203 from dusrdev/master
Fix NativeAOT delegate commands by preserving reflection metadata
2 parents dd5a11c + 689a122 commit c821763

File tree

5 files changed

+229
-1
lines changed

5 files changed

+229
-1
lines changed

ConsoleAppFramework.sln

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,40 +36,114 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleAppFramework.Abstrac
3636
EndProject
3737
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FilterShareProject", "sandbox\FilterShareProject\FilterShareProject.csproj", "{2A1E8ED1-CEB9-47CB-8497-A0C4F5A8F025}"
3838
EndProject
39+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NativeAotTrimming", "tests\NativeAotTrimming\NativeAotTrimming.csproj", "{B14EB164-AC1E-4B0C-9CEB-312B233195E5}"
40+
EndProject
3941
Global
4042
GlobalSection(SolutionConfigurationPlatforms) = preSolution
4143
Debug|Any CPU = Debug|Any CPU
44+
Debug|x64 = Debug|x64
45+
Debug|x86 = Debug|x86
4246
Release|Any CPU = Release|Any CPU
47+
Release|x64 = Release|x64
48+
Release|x86 = Release|x86
4349
EndGlobalSection
4450
GlobalSection(ProjectConfigurationPlatforms) = postSolution
4551
{09BEEA7B-B6D3-4011-BCAB-6DF976713695}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
4652
{09BEEA7B-B6D3-4011-BCAB-6DF976713695}.Debug|Any CPU.Build.0 = Debug|Any CPU
53+
{09BEEA7B-B6D3-4011-BCAB-6DF976713695}.Debug|x64.ActiveCfg = Debug|Any CPU
54+
{09BEEA7B-B6D3-4011-BCAB-6DF976713695}.Debug|x64.Build.0 = Debug|Any CPU
55+
{09BEEA7B-B6D3-4011-BCAB-6DF976713695}.Debug|x86.ActiveCfg = Debug|Any CPU
56+
{09BEEA7B-B6D3-4011-BCAB-6DF976713695}.Debug|x86.Build.0 = Debug|Any CPU
4757
{09BEEA7B-B6D3-4011-BCAB-6DF976713695}.Release|Any CPU.ActiveCfg = Release|Any CPU
4858
{09BEEA7B-B6D3-4011-BCAB-6DF976713695}.Release|Any CPU.Build.0 = Release|Any CPU
59+
{09BEEA7B-B6D3-4011-BCAB-6DF976713695}.Release|x64.ActiveCfg = Release|Any CPU
60+
{09BEEA7B-B6D3-4011-BCAB-6DF976713695}.Release|x64.Build.0 = Release|Any CPU
61+
{09BEEA7B-B6D3-4011-BCAB-6DF976713695}.Release|x86.ActiveCfg = Release|Any CPU
62+
{09BEEA7B-B6D3-4011-BCAB-6DF976713695}.Release|x86.Build.0 = Release|Any CPU
4963
{ACDA48BA-0BFE-4917-B335-7836DAA5929A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
5064
{ACDA48BA-0BFE-4917-B335-7836DAA5929A}.Debug|Any CPU.Build.0 = Debug|Any CPU
65+
{ACDA48BA-0BFE-4917-B335-7836DAA5929A}.Debug|x64.ActiveCfg = Debug|Any CPU
66+
{ACDA48BA-0BFE-4917-B335-7836DAA5929A}.Debug|x64.Build.0 = Debug|Any CPU
67+
{ACDA48BA-0BFE-4917-B335-7836DAA5929A}.Debug|x86.ActiveCfg = Debug|Any CPU
68+
{ACDA48BA-0BFE-4917-B335-7836DAA5929A}.Debug|x86.Build.0 = Debug|Any CPU
5169
{ACDA48BA-0BFE-4917-B335-7836DAA5929A}.Release|Any CPU.ActiveCfg = Release|Any CPU
5270
{ACDA48BA-0BFE-4917-B335-7836DAA5929A}.Release|Any CPU.Build.0 = Release|Any CPU
71+
{ACDA48BA-0BFE-4917-B335-7836DAA5929A}.Release|x64.ActiveCfg = Release|Any CPU
72+
{ACDA48BA-0BFE-4917-B335-7836DAA5929A}.Release|x64.Build.0 = Release|Any CPU
73+
{ACDA48BA-0BFE-4917-B335-7836DAA5929A}.Release|x86.ActiveCfg = Release|Any CPU
74+
{ACDA48BA-0BFE-4917-B335-7836DAA5929A}.Release|x86.Build.0 = Release|Any CPU
5375
{F558E4F2-1AB0-4634-B613-69DFE79894AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
5476
{F558E4F2-1AB0-4634-B613-69DFE79894AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
77+
{F558E4F2-1AB0-4634-B613-69DFE79894AF}.Debug|x64.ActiveCfg = Debug|Any CPU
78+
{F558E4F2-1AB0-4634-B613-69DFE79894AF}.Debug|x64.Build.0 = Debug|Any CPU
79+
{F558E4F2-1AB0-4634-B613-69DFE79894AF}.Debug|x86.ActiveCfg = Debug|Any CPU
80+
{F558E4F2-1AB0-4634-B613-69DFE79894AF}.Debug|x86.Build.0 = Debug|Any CPU
5581
{F558E4F2-1AB0-4634-B613-69DFE79894AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
5682
{F558E4F2-1AB0-4634-B613-69DFE79894AF}.Release|Any CPU.Build.0 = Release|Any CPU
83+
{F558E4F2-1AB0-4634-B613-69DFE79894AF}.Release|x64.ActiveCfg = Release|Any CPU
84+
{F558E4F2-1AB0-4634-B613-69DFE79894AF}.Release|x64.Build.0 = Release|Any CPU
85+
{F558E4F2-1AB0-4634-B613-69DFE79894AF}.Release|x86.ActiveCfg = Release|Any CPU
86+
{F558E4F2-1AB0-4634-B613-69DFE79894AF}.Release|x86.Build.0 = Release|Any CPU
5787
{C54F7FE8-650A-4DC7-877F-0DE929351800}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
5888
{C54F7FE8-650A-4DC7-877F-0DE929351800}.Debug|Any CPU.Build.0 = Debug|Any CPU
89+
{C54F7FE8-650A-4DC7-877F-0DE929351800}.Debug|x64.ActiveCfg = Debug|Any CPU
90+
{C54F7FE8-650A-4DC7-877F-0DE929351800}.Debug|x64.Build.0 = Debug|Any CPU
91+
{C54F7FE8-650A-4DC7-877F-0DE929351800}.Debug|x86.ActiveCfg = Debug|Any CPU
92+
{C54F7FE8-650A-4DC7-877F-0DE929351800}.Debug|x86.Build.0 = Debug|Any CPU
5993
{C54F7FE8-650A-4DC7-877F-0DE929351800}.Release|Any CPU.ActiveCfg = Release|Any CPU
6094
{C54F7FE8-650A-4DC7-877F-0DE929351800}.Release|Any CPU.Build.0 = Release|Any CPU
95+
{C54F7FE8-650A-4DC7-877F-0DE929351800}.Release|x64.ActiveCfg = Release|Any CPU
96+
{C54F7FE8-650A-4DC7-877F-0DE929351800}.Release|x64.Build.0 = Release|Any CPU
97+
{C54F7FE8-650A-4DC7-877F-0DE929351800}.Release|x86.ActiveCfg = Release|Any CPU
98+
{C54F7FE8-650A-4DC7-877F-0DE929351800}.Release|x86.Build.0 = Release|Any CPU
6199
{EC1A3299-6597-4AD2-92DE-EDF309875A97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
62100
{EC1A3299-6597-4AD2-92DE-EDF309875A97}.Debug|Any CPU.Build.0 = Debug|Any CPU
101+
{EC1A3299-6597-4AD2-92DE-EDF309875A97}.Debug|x64.ActiveCfg = Debug|Any CPU
102+
{EC1A3299-6597-4AD2-92DE-EDF309875A97}.Debug|x64.Build.0 = Debug|Any CPU
103+
{EC1A3299-6597-4AD2-92DE-EDF309875A97}.Debug|x86.ActiveCfg = Debug|Any CPU
104+
{EC1A3299-6597-4AD2-92DE-EDF309875A97}.Debug|x86.Build.0 = Debug|Any CPU
63105
{EC1A3299-6597-4AD2-92DE-EDF309875A97}.Release|Any CPU.ActiveCfg = Release|Any CPU
64106
{EC1A3299-6597-4AD2-92DE-EDF309875A97}.Release|Any CPU.Build.0 = Release|Any CPU
107+
{EC1A3299-6597-4AD2-92DE-EDF309875A97}.Release|x64.ActiveCfg = Release|Any CPU
108+
{EC1A3299-6597-4AD2-92DE-EDF309875A97}.Release|x64.Build.0 = Release|Any CPU
109+
{EC1A3299-6597-4AD2-92DE-EDF309875A97}.Release|x86.ActiveCfg = Release|Any CPU
110+
{EC1A3299-6597-4AD2-92DE-EDF309875A97}.Release|x86.Build.0 = Release|Any CPU
65111
{855B0D28-DC69-470B-B3D9-481EE52737AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
66112
{855B0D28-DC69-470B-B3D9-481EE52737AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
113+
{855B0D28-DC69-470B-B3D9-481EE52737AA}.Debug|x64.ActiveCfg = Debug|Any CPU
114+
{855B0D28-DC69-470B-B3D9-481EE52737AA}.Debug|x64.Build.0 = Debug|Any CPU
115+
{855B0D28-DC69-470B-B3D9-481EE52737AA}.Debug|x86.ActiveCfg = Debug|Any CPU
116+
{855B0D28-DC69-470B-B3D9-481EE52737AA}.Debug|x86.Build.0 = Debug|Any CPU
67117
{855B0D28-DC69-470B-B3D9-481EE52737AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
68118
{855B0D28-DC69-470B-B3D9-481EE52737AA}.Release|Any CPU.Build.0 = Release|Any CPU
119+
{855B0D28-DC69-470B-B3D9-481EE52737AA}.Release|x64.ActiveCfg = Release|Any CPU
120+
{855B0D28-DC69-470B-B3D9-481EE52737AA}.Release|x64.Build.0 = Release|Any CPU
121+
{855B0D28-DC69-470B-B3D9-481EE52737AA}.Release|x86.ActiveCfg = Release|Any CPU
122+
{855B0D28-DC69-470B-B3D9-481EE52737AA}.Release|x86.Build.0 = Release|Any CPU
69123
{2A1E8ED1-CEB9-47CB-8497-A0C4F5A8F025}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
70124
{2A1E8ED1-CEB9-47CB-8497-A0C4F5A8F025}.Debug|Any CPU.Build.0 = Debug|Any CPU
125+
{2A1E8ED1-CEB9-47CB-8497-A0C4F5A8F025}.Debug|x64.ActiveCfg = Debug|Any CPU
126+
{2A1E8ED1-CEB9-47CB-8497-A0C4F5A8F025}.Debug|x64.Build.0 = Debug|Any CPU
127+
{2A1E8ED1-CEB9-47CB-8497-A0C4F5A8F025}.Debug|x86.ActiveCfg = Debug|Any CPU
128+
{2A1E8ED1-CEB9-47CB-8497-A0C4F5A8F025}.Debug|x86.Build.0 = Debug|Any CPU
71129
{2A1E8ED1-CEB9-47CB-8497-A0C4F5A8F025}.Release|Any CPU.ActiveCfg = Release|Any CPU
72130
{2A1E8ED1-CEB9-47CB-8497-A0C4F5A8F025}.Release|Any CPU.Build.0 = Release|Any CPU
131+
{2A1E8ED1-CEB9-47CB-8497-A0C4F5A8F025}.Release|x64.ActiveCfg = Release|Any CPU
132+
{2A1E8ED1-CEB9-47CB-8497-A0C4F5A8F025}.Release|x64.Build.0 = Release|Any CPU
133+
{2A1E8ED1-CEB9-47CB-8497-A0C4F5A8F025}.Release|x86.ActiveCfg = Release|Any CPU
134+
{2A1E8ED1-CEB9-47CB-8497-A0C4F5A8F025}.Release|x86.Build.0 = Release|Any CPU
135+
{B14EB164-AC1E-4B0C-9CEB-312B233195E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
136+
{B14EB164-AC1E-4B0C-9CEB-312B233195E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
137+
{B14EB164-AC1E-4B0C-9CEB-312B233195E5}.Debug|x64.ActiveCfg = Debug|Any CPU
138+
{B14EB164-AC1E-4B0C-9CEB-312B233195E5}.Debug|x64.Build.0 = Debug|Any CPU
139+
{B14EB164-AC1E-4B0C-9CEB-312B233195E5}.Debug|x86.ActiveCfg = Debug|Any CPU
140+
{B14EB164-AC1E-4B0C-9CEB-312B233195E5}.Debug|x86.Build.0 = Debug|Any CPU
141+
{B14EB164-AC1E-4B0C-9CEB-312B233195E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
142+
{B14EB164-AC1E-4B0C-9CEB-312B233195E5}.Release|Any CPU.Build.0 = Release|Any CPU
143+
{B14EB164-AC1E-4B0C-9CEB-312B233195E5}.Release|x64.ActiveCfg = Release|Any CPU
144+
{B14EB164-AC1E-4B0C-9CEB-312B233195E5}.Release|x64.Build.0 = Release|Any CPU
145+
{B14EB164-AC1E-4B0C-9CEB-312B233195E5}.Release|x86.ActiveCfg = Release|Any CPU
146+
{B14EB164-AC1E-4B0C-9CEB-312B233195E5}.Release|x86.Build.0 = Release|Any CPU
73147
EndGlobalSection
74148
GlobalSection(SolutionProperties) = preSolution
75149
HideSolutionNode = FALSE
@@ -82,6 +156,7 @@ Global
82156
{EC1A3299-6597-4AD2-92DE-EDF309875A97} = {A2CF2984-E8E2-48FC-B5A1-58D74A2467E6}
83157
{855B0D28-DC69-470B-B3D9-481EE52737AA} = {1F399F98-7439-4F05-847B-CC1267B4B7F2}
84158
{2A1E8ED1-CEB9-47CB-8497-A0C4F5A8F025} = {A2CF2984-E8E2-48FC-B5A1-58D74A2467E6}
159+
{B14EB164-AC1E-4B0C-9CEB-312B233195E5} = {AAD2D900-C305-4449-A9FC-6C7696FFEDFA}
85160
EndGlobalSection
86161
GlobalSection(ExtensibilityGlobals) = postSolution
87162
SolutionGuid = {7F3E353A-C125-4020-8481-11DC6496358C}

src/ConsoleAppFramework/Emitter.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public void EmitRun(SourceBuilder sb, CommandWithId commandWithId, bool isRunAsy
2525
}
2626
var returnType = isRunAsync ? "async Task" : "void";
2727
var accessibility = !emitForBuilder ? "public static" : "private";
28-
methodName = methodName ?? (isRunAsync ? "RunAsync" : "Run");
28+
methodName ??= (isRunAsync ? "RunAsync" : "Run");
2929
var unsafeCode = (command.MethodKind == MethodKind.FunctionPointer) ? "unsafe " : "";
3030

3131
var commandMethodType = command.BuildDelegateSignature(commandWithId.BuildCustomDelegateTypeName(), out var delegateType);
@@ -49,6 +49,21 @@ public void EmitRun(SourceBuilder sb, CommandWithId commandWithId, bool isRunAsy
4949
: emitForBuilder ? "__ExternalCancellationToken__"
5050
: "CancellationToken.None";
5151

52+
string? dynamicDependencyAttribute = null;
53+
if (command.CommandMethodInfo == null &&
54+
command.Symbol.Value is IMethodSymbol dynamicDependencyMethod &&
55+
dynamicDependencyMethod.ContainingType != null)
56+
{
57+
var docCommentId = dynamicDependencyMethod.GetDocumentationCommentId();
58+
var parameterPartIndex = docCommentId?.IndexOf('(') ?? -1;
59+
var memberSignature = parameterPartIndex >= 0
60+
? dynamicDependencyMethod.Name + docCommentId!.Substring(parameterPartIndex)
61+
: dynamicDependencyMethod.Name;
62+
63+
var containingType = dynamicDependencyMethod.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
64+
dynamicDependencyAttribute = $"[global::System.Diagnostics.CodeAnalysis.DynamicDependency(\"{memberSignature}\", typeof({containingType}))]";
65+
}
66+
5267
if (!emitForBuilder)
5368
{
5469
sb.AppendLine("/// <summary>");
@@ -63,6 +78,10 @@ public void EmitRun(SourceBuilder sb, CommandWithId commandWithId, bool isRunAsy
6378
}
6479

6580
// method signature
81+
if (dynamicDependencyAttribute != null)
82+
{
83+
sb.AppendLine(dynamicDependencyAttribute);
84+
}
6685
using (sb.BeginBlock($"{accessibility} {unsafeCode}{returnType} {methodName}(string[] args{commandDepthEscapeIndex}{commandMethodType}{filterCancellationToken})"))
6786
{
6887
using (command.HasFilter ? sb.Nop : sb.BeginBlock("try"))
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using System.Diagnostics;
2+
using System.Runtime.InteropServices;
3+
4+
namespace ConsoleAppFramework.GeneratorTests;
5+
6+
public class NativeAotTrimmingTests
7+
{
8+
[Fact]
9+
public void NativeAotTrimmingSample_PublishesAndRuns()
10+
{
11+
var publishDir = Directory.CreateTempSubdirectory("caf-nativeaot").FullName;
12+
13+
var currentDir = AppContext.BaseDirectory;
14+
var testsDir = $"tests{Path.DirectorySeparatorChar}";
15+
var root = currentDir[..(currentDir.IndexOf(testsDir) + testsDir.Length)];
16+
17+
var publish = StartProcess("dotnet", [
18+
"publish",
19+
Path.Combine(root, "NativeAotTrimming", "NativeAotTrimming.csproj"),
20+
"-c", "Release",
21+
"-o", publishDir
22+
]);
23+
publish.WaitForExit();
24+
var publishStdOut = publish.StandardOutput.ReadToEnd();
25+
var publishStdErr = publish.StandardError.ReadToEnd();
26+
27+
publish.ExitCode.ShouldBe(0, $"dotnet publish failed:{Environment.NewLine}{publishStdOut}{publishStdErr}");
28+
29+
var exeName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
30+
? "NativeAotTrimming.exe"
31+
: "NativeAotTrimming";
32+
var exePath = Path.Combine(publishDir, exeName);
33+
34+
File.Exists(exePath).ShouldBeTrue($"Expected published executable at {exePath}");
35+
36+
var app = StartProcess(exePath, [], workingDirectory: publishDir);
37+
app.WaitForExit();
38+
var appStdOut = app.StandardOutput.ReadToEnd();
39+
var appStdErr = app.StandardError.ReadToEnd();
40+
41+
app.ExitCode.ShouldBe(0, $"App should execute successfully after AOT trimming:{Environment.NewLine}stdout:{appStdOut}{Environment.NewLine}stderr:{appStdErr}");
42+
}
43+
44+
private static Process StartProcess(string fileName, IReadOnlyCollection<string> arguments, string? workingDirectory = null)
45+
{
46+
var psi = new ProcessStartInfo
47+
{
48+
FileName = fileName,
49+
RedirectStandardOutput = true,
50+
RedirectStandardError = true,
51+
UseShellExecute = false,
52+
WorkingDirectory = workingDirectory ?? Environment.CurrentDirectory
53+
};
54+
55+
foreach (var arg in arguments)
56+
{
57+
psi.ArgumentList.Add(arg);
58+
}
59+
60+
return Process.Start(psi)!;
61+
}
62+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net9.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
<TrimMode>Full</TrimMode>
9+
<PublishAot>true</PublishAot>
10+
<PublishTrimmed>true</PublishTrimmed>
11+
<PublishSingleFile>true</PublishSingleFile>
12+
<SelfContained>true</SelfContained>
13+
<IlcGenerateCompleteTypeMetadata>false</IlcGenerateCompleteTypeMetadata>
14+
</PropertyGroup>
15+
16+
<ItemGroup>
17+
<ProjectReference Include="..\..\src\ConsoleAppFramework\ConsoleAppFramework.csproj">
18+
<OutputItemType>Analyzer</OutputItemType>
19+
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
20+
</ProjectReference>
21+
</ItemGroup>
22+
23+
</Project>

tests/NativeAotTrimming/Program.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using ConsoleAppFramework;
2+
using System.ComponentModel.DataAnnotations;
3+
4+
var app = ConsoleApp.Create();
5+
6+
app.UseFilter<LoggingFilter>();
7+
app.Add("", Commands.Root);
8+
9+
string[] runArgs =
10+
[
11+
"input.txt",
12+
"--count", "3",
13+
"--quiet"
14+
];
15+
16+
await app.RunAsync(runArgs);
17+
18+
internal sealed class LoggingFilter(ConsoleAppFilter next) : ConsoleAppFilter(next)
19+
{
20+
public override async Task InvokeAsync(ConsoleAppContext context, CancellationToken cancellationToken)
21+
{
22+
try
23+
{
24+
await Next.InvokeAsync(context, cancellationToken);
25+
}
26+
catch (Exception ex)
27+
{
28+
Console.WriteLine($"Unhandled exception: {ex.Message}");
29+
throw;
30+
}
31+
}
32+
}
33+
34+
internal static class Commands
35+
{
36+
public static async Task<int> Root(
37+
[Argument] string path,
38+
[Range(1, 10)] int count = 1,
39+
bool quiet = false,
40+
CancellationToken cancellationToken = default)
41+
{
42+
await Task.Delay(10, cancellationToken);
43+
if (!quiet)
44+
{
45+
Console.WriteLine($"Processing {path} with count {count}");
46+
}
47+
return 0;
48+
}
49+
}

0 commit comments

Comments
 (0)