Skip to content

Commit a9a6097

Browse files
committed
Enable AOT compatibility analyzers and fix issues
Replace reflection-based patterns with AOT-compatible alternatives across the CLI codebase: - **Source-generated JSON serialization**: Replace `JsonSerializer.Serialize<T>`/`Deserialize<T>` calls with source-generated `JsonSerializerContext` types throughout workload, tool list, SDK check, template, and manifest commands. This eliminates reliance on reflection-based `DefaultJsonTypeInfoResolver`. - **Replace `Assembly.GetExecutingAssembly().Location`** with `AppContext.BaseDirectory` or `Environment.ProcessPath` in `VBCSCompilerServer`, `InstallerBase`, `InstallClientElevationContext`, `SignCheck`, and `ToolPackageDownloaderBase`, since `Assembly.Location` returns empty in single-file/AOT deployments. - **Replace `XmlSerializer` with manual XML parsing**: Use `XDocument`/`XmlReader` in `ToolConfigurationDeserializer` and `WorkloadUnixFilePermissionsFileList` to avoid runtime code generation. - **AOT-compatible logging registration**: Change `CliTemplateEngineHost` to use `Services.TryAddEnumerable` instead of the generic `AddConsoleFormatter<T>` overload that relies on `MakeGenericType`. - **Enable AOT compatibility analyzers**: Add `<IsAotCompatible>true</IsAotCompatible>` to `dotnet.csproj`, `Microsoft.DotNet.Configurer`, `Microsoft.DotNet.InternalAbstractions`, `Microsoft.TemplateEngine.Cli`, `Microsoft.DotNet.ProjectTools`, `Microsoft.DotNet.TemplateLocator`, `Microsoft.Win32.Msi`, and `Microsoft.DotNet.SdkResolver` project files. - **Suppress IL3000 warnings** with `#pragma` in locations where `Assembly.Location` is unavoidable (`MSBuildForwardingApp`, `BuiltInTemplatePackageProvider`, `OptionalWorkloadProvider`).
1 parent 7659dfc commit a9a6097

File tree

50 files changed

+319
-143
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+319
-143
lines changed

src/Cli/Microsoft.DotNet.Configurer/Microsoft.DotNet.Configurer.csproj

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,16 @@
55
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
66
<StrongNameKeyId>MicrosoftAspNetCore</StrongNameKeyId>
77
<SignAssembly>true</SignAssembly>
8-
<PublicSign Condition=" '$([MSBuild]::IsOSPlatform(`Windows`))' == 'false' ">true</PublicSign>
8+
<PublicSign Condition="'$([MSBuild]::IsOSPlatform(`Windows`))' == 'false'">true</PublicSign>
99
<RepositoryType>git</RepositoryType>
1010
<DefineConstants Condition="'$(IncludeAspNetCoreRuntime)' == 'false'">$(DefineConstants);EXCLUDE_ASPNETCORE</DefineConstants>
1111
<IsPackable>true</IsPackable>
12+
13+
<!--
14+
Mark as Native AOT compatible. This will enable relevant analyzers.
15+
https://learn.microsoft.com/dotnet/core/deploying/native-aot/#aot-compatibility-analyzers
16+
-->
17+
<IsAotCompatible>true</IsAotCompatible>
1218
</PropertyGroup>
1319

1420
<ItemGroup>

src/Cli/Microsoft.DotNet.InternalAbstractions/Microsoft.DotNet.InternalAbstractions.csproj

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,15 @@
77
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
88
<StrongNameKeyId>MicrosoftAspNetCore</StrongNameKeyId>
99
<SignAssembly>true</SignAssembly>
10-
<PublicSign Condition=" '$([MSBuild]::IsOSPlatform(`Windows`))' == 'false' ">true</PublicSign>
10+
<PublicSign Condition="'$([MSBuild]::IsOSPlatform(`Windows`))' == 'false'">true</PublicSign>
1111
<RepositoryType>git</RepositoryType>
1212
<IsPackable>true</IsPackable>
13+
14+
<!--
15+
Mark as Native AOT compatible. This will enable relevant analyzers.
16+
https://learn.microsoft.com/dotnet/core/deploying/native-aot/#aot-compatibility-analyzers
17+
-->
18+
<IsAotCompatible Condition="'$(TargetFramework)' != 'net472'">true</IsAotCompatible>
1319
</PropertyGroup>
1420

1521
<ItemGroup>

src/Cli/Microsoft.TemplateEngine.Cli/CliTemplateEngineHost.cs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using Microsoft.Extensions.DependencyInjection;
5+
using Microsoft.Extensions.DependencyInjection.Extensions;
46
using Microsoft.Extensions.Logging;
57
using Microsoft.Extensions.Logging.Console;
68
using Microsoft.TemplateEngine.Abstractions;
@@ -25,14 +27,17 @@ public CliTemplateEngineHost(
2527
builtIns,
2628
fallbackHostNames,
2729
loggerFactory: Microsoft.Extensions.Logging.LoggerFactory.Create(builder =>
28-
builder
29-
.SetMinimumLevel(logLevel)
30-
.AddConsole(config => config.FormatterName = nameof(CliConsoleFormatter))
31-
.AddConsoleFormatter<CliConsoleFormatter, ConsoleFormatterOptions>(config =>
32-
{
33-
config.IncludeScopes = true;
34-
config.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff";
35-
})))
30+
{
31+
builder
32+
.SetMinimumLevel(logLevel)
33+
.AddConsole(config => config.FormatterName = nameof(CliConsoleFormatter));
34+
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ConsoleFormatter, CliConsoleFormatter>());
35+
builder.Services.Configure<ConsoleFormatterOptions>(nameof(CliConsoleFormatter), config =>
36+
{
37+
config.IncludeScopes = true;
38+
config.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff";
39+
});
40+
}))
3641
{
3742
string workingPath = FileSystem.GetCurrentDirectory();
3843
IsCustomOutputPath = outputPath != null;

src/Cli/Microsoft.TemplateEngine.Cli/Microsoft.TemplateEngine.Cli.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@
99
<DefineConstants>$(DefineConstants);WCWIDTH_VISIBILITY_INTERNAL</DefineConstants>
1010
<StrongNameKeyId>MicrosoftAspNetCore</StrongNameKeyId>
1111
<SignAssembly>true</SignAssembly>
12+
13+
<!--
14+
Mark as Native AOT compatible. This will enable relevant analyzers.
15+
https://learn.microsoft.com/dotnet/core/deploying/native-aot/#aot-compatibility-analyzers
16+
-->
17+
<IsAotCompatible>true</IsAotCompatible>
1218
</PropertyGroup>
1319

1420
<ItemGroup>

src/Cli/dotnet/BuildServer/VBCSCompilerServer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ internal class VBCSCompilerServer(ICommandFactory commandFactory = null) : IBuil
1717
private static readonly string s_shutdownArg = "-shutdown";
1818

1919
internal static readonly string VBCSCompilerPath = Path.Combine(
20-
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
20+
AppContext.BaseDirectory,
2121
"Roslyn",
2222
"bincore",
2323
"VBCSCompiler.dll");

src/Cli/dotnet/CliSchema.cs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
using System.Text.Json;
88
using System.Text.Json.Schema;
99
using System.Text.Json.Serialization;
10-
using System.Text.Json.Serialization.Metadata;
1110
using Microsoft.DotNet.Cli.Telemetry;
1211
using Microsoft.DotNet.Cli.Utils;
1312
using Microsoft.DotNet.Cli.Utils.Extensions;
@@ -21,17 +20,16 @@ internal static class CliSchema
2120
// Using UnsafeRelaxedJsonEscaping because this JSON is not transmitted over the web. Therefore, HTML-sensitive characters are not encoded.
2221
// See: https://learn.microsoft.com/dotnet/api/system.text.encodings.web.javascriptencoder.unsaferelaxedjsonescaping
2322
// Force the newline to be "\n" instead of the default "\r\n" for consistency across platforms (and for testing)
24-
private static readonly JsonSerializerOptions s_jsonSerializerOptions = new()
23+
// Using a source-generated JsonSerializerContext as the TypeInfoResolver so that
24+
// the options are AOT compatible and have a resolver set (avoiding https://github.com/dotnet/aspnetcore/issues/55692).
25+
private static readonly CliSchemaJsonSerializerContext s_jsonContext = new(new JsonSerializerOptions
2526
{
2627
WriteIndented = true,
2728
NewLine = "\n",
2829
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
2930
RespectNullableAnnotations = true,
3031
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
31-
// needed to workaround https://github.com/dotnet/aspnetcore/issues/55692, but will need to be removed when
32-
// we tackle AOT in favor of the source-generated JsonTypeInfo stuff
33-
TypeInfoResolver = new DefaultJsonTypeInfoResolver()
34-
};
32+
});
3533

3634
public record ArgumentDetails(string? description, int order, bool hidden, string? helpName, string valueType, bool hasDefaultValue, object? defaultValue, ArityDetails arity);
3735
public record ArityDetails(int minimum, int? maximum);
@@ -70,7 +68,7 @@ public static void PrintCliSchema(CommandResult commandResult, TextWriter output
7068
{
7169
var command = commandResult.Command;
7270
RootCommandDetails transportStructure = CreateRootCommandDetails(command);
73-
var result = JsonSerializer.Serialize(transportStructure, s_jsonSerializerOptions);
71+
var result = JsonSerializer.Serialize(transportStructure, s_jsonContext.RootCommandDetails);
7472
outputWriter.Write(result.AsSpan());
7573
outputWriter.Flush();
7674
var commandString = CommandHierarchyAsString(commandResult);
@@ -80,8 +78,8 @@ public static void PrintCliSchema(CommandResult commandResult, TextWriter output
8078

8179
public static object GetJsonSchema()
8280
{
83-
var node = s_jsonSerializerOptions.GetJsonSchemaAsNode(typeof(RootCommandDetails), new JsonSchemaExporterOptions());
84-
return node.ToJsonString(s_jsonSerializerOptions);
81+
var node = s_jsonContext.Options.GetJsonSchemaAsNode(typeof(RootCommandDetails), new JsonSchemaExporterOptions());
82+
return node.ToJsonString(s_jsonContext.Options);
8583
}
8684

8785
private static ArityDetails CreateArityDetails(ArgumentArity arity)
@@ -222,3 +220,6 @@ private static string CommandHierarchyAsString(CommandResult commandResult)
222220
return string.Join(" ", commands.AsEnumerable().Reverse());
223221
}
224222
}
223+
224+
[JsonSerializable(typeof(CliSchema.RootCommandDetails))]
225+
internal partial class CliSchemaJsonSerializerContext : JsonSerializerContext;

src/Cli/dotnet/Commands/MSBuild/MSBuildForwardingApp.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ private static MSBuildArgs ConcatTelemetryLogger(MSBuildArgs msbuildArgs)
2727
Type loggerType = typeof(MSBuildLogger);
2828
Type forwardingLoggerType = typeof(MSBuildForwardingLogger);
2929

30+
#pragma warning disable IL3000 // Avoid accessing Assembly file path when publishing as a single file
3031
msbuildArgs.OtherMSBuildArgs.Add($"-distributedlogger:{loggerType.FullName},{loggerType.GetTypeInfo().Assembly.Location}*{forwardingLoggerType.FullName},{forwardingLoggerType.GetTypeInfo().Assembly.Location}");
32+
#pragma warning restore IL3000
3133
return msbuildArgs;
3234
}
3335
catch (Exception)

src/Cli/dotnet/Commands/New/BuiltInTemplatePackageProvider.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ private static IEnumerable<string> GetTemplateFolders(IEngineEnvironmentSettings
4444
{
4545
var templateFoldersToInstall = new List<string>();
4646

47+
#pragma warning disable IL3000 // Avoid accessing Assembly file path when publishing as a single file
4748
var sdkDirectory = Path.GetDirectoryName(typeof(Utils.DotnetFiles).Assembly.Location);
49+
#pragma warning restore IL3000
50+
4851
var dotnetRootPath = Path.GetDirectoryName(Path.GetDirectoryName(sdkDirectory));
4952

5053
// First grab templates from dotnet\templates\M.m folders, in ascending order, up to our version

src/Cli/dotnet/Commands/New/OptionalWorkloadProvider.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ public Task<IReadOnlyList<ITemplatePackage>> GetAllTemplatePackagesAsync(Cancell
3333
{
3434
var list = new List<TemplatePackage>();
3535
var optionalWorkloadLocator = new TemplateLocator.TemplateLocator();
36+
#pragma warning disable IL3000 // Avoid accessing Assembly file path when publishing as a single file
3637
var sdkDirectory = Path.GetDirectoryName(typeof(DotnetFiles).Assembly.Location);
38+
#pragma warning restore IL3000 // Avoid accessing Assembly file path when publishing as a single file
3739
var sdkVersion = Path.GetFileName(sdkDirectory);
3840
var dotnetRootPath = Path.GetDirectoryName(Path.GetDirectoryName(sdkDirectory));
3941
string userProfileDir = CliFolderPathCalculator.DotnetUserProfileFolderPath;

src/Cli/dotnet/Commands/Run/CSharpCompilerCommand.cs

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Immutable;
66
using System.Diagnostics;
77
using System.Text.Json;
8+
using System.Text.Json.Serialization;
89
using Microsoft.CodeAnalysis;
910
using Microsoft.CodeAnalysis.CommandLine;
1011
using Microsoft.CodeAnalysis.CSharp;
@@ -21,6 +22,11 @@ namespace Microsoft.DotNet.Cli.Commands.Run;
2122
/// </summary>
2223
internal sealed partial class CSharpCompilerCommand
2324
{
25+
[JsonSerializable(typeof(string))]
26+
private partial class CSharpCompilerCommandJsonSerializerContext : JsonSerializerContext
27+
{
28+
}
29+
2430
private static readonly SearchValues<char> s_additionalShouldSurroundWithQuotes = SearchValues.Create('=', ',');
2531

2632
/// <summary>
@@ -285,25 +291,25 @@ private void PrepareAuxiliaryFiles(out string rspPath)
285291
build_property.EnableAotAnalyzer = true
286292
build_property.EnableSingleFileAnalyzer = true
287293
build_property.EnableTrimAnalyzer = true
288-
build_property.IncludeAllContentForSelfExtract =
289-
build_property.VerifyReferenceTrimCompatibility =
290-
build_property.VerifyReferenceAotCompatibility =
294+
build_property.IncludeAllContentForSelfExtract =
295+
build_property.VerifyReferenceTrimCompatibility =
296+
build_property.VerifyReferenceAotCompatibility =
291297
build_property.TargetFramework = net{TargetFrameworkVersion}
292298
build_property.TargetFrameworkIdentifier = .NETCoreApp
293299
build_property.TargetFrameworkVersion = v{TargetFrameworkVersion}
294-
build_property.TargetPlatformMinVersion =
295-
build_property.UsingMicrosoftNETSdkWeb =
296-
build_property.ProjectTypeGuids =
297-
build_property.InvariantGlobalization =
298-
build_property.PlatformNeutralAssembly =
299-
build_property.EnforceExtendedAnalyzerRules =
300+
build_property.TargetPlatformMinVersion =
301+
build_property.UsingMicrosoftNETSdkWeb =
302+
build_property.ProjectTypeGuids =
303+
build_property.InvariantGlobalization =
304+
build_property.PlatformNeutralAssembly =
305+
build_property.EnforceExtendedAnalyzerRules =
300306
build_property._SupportedPlatformList = Linux,macOS,Windows
301307
build_property.RootNamespace = {fileNameWithoutExtension}
302308
build_property.ProjectDir = {fileDirectory}{Path.DirectorySeparatorChar}
303-
build_property.EnableComHosting =
309+
build_property.EnableComHosting =
304310
build_property.EnableGeneratedComInterfaceComImportInterop = false
305311
build_property.EffectiveAnalysisLevelStyle = {TargetFrameworkVersion}
306-
build_property.EnableCodeStyleSeverity =
312+
build_property.EnableCodeStyleSeverity =
307313
308314
""");
309315
}
@@ -329,11 +335,11 @@ private void PrepareAuxiliaryFiles(out string rspPath)
329335
"tfm": "net{{TargetFrameworkVersion}}",
330336
"framework": {
331337
"name": "Microsoft.NETCore.App",
332-
"version": {{JsonSerializer.Serialize(DefaultRuntimeVersion)}}
338+
"version": {{JsonSerializer.Serialize(DefaultRuntimeVersion, CSharpCompilerCommandJsonSerializerContext.Default.String)}}
333339
},
334340
"configProperties": {
335-
"EntryPointFilePath": {{JsonSerializer.Serialize(EntryPointFileFullPath)}},
336-
"EntryPointFileDirectoryPath": {{JsonSerializer.Serialize(fileDirectory)}},
341+
"EntryPointFilePath": {{JsonSerializer.Serialize(EntryPointFileFullPath, CSharpCompilerCommandJsonSerializerContext.Default.String)}},
342+
"EntryPointFileDirectoryPath": {{JsonSerializer.Serialize(fileDirectory, CSharpCompilerCommandJsonSerializerContext.Default.String)}},
337343
"Microsoft.Extensions.DependencyInjection.VerifyOpenGenericServiceTrimmability": true,
338344
"System.ComponentModel.DefaultValueAttribute.IsSupported": false,
339345
"System.ComponentModel.Design.IDesignerHost.IsSupported": false,

0 commit comments

Comments
 (0)