From 397e9f8a2eba6fa2c72afac2d394a8698e4114cf Mon Sep 17 00:00:00 2001 From: Martin Costello Date: Tue, 23 Jan 2024 00:03:14 +0000 Subject: [PATCH] Extend AoT support (#1541) Co-authored-by: Cijo Thomas --- .github/workflows/ci.yml | 7 ++++++ .../CHANGELOG.md | 5 ++++ ...raceEnrichmentProviderBuilderExtensions.cs | 7 ++++++ ...ceEnrichmentServiceCollectionExtensions.cs | 7 ++++++ .../AWSEBSResourceDetector.cs | 4 ++++ .../AWSEC2ResourceDetector.cs | 4 ++++ .../AWSEKSResourceDetector.cs | 4 ++++ .../CHANGELOG.md | 2 ++ .../Models/AWSEC2IdentityDocumentModel.cs | 7 ++++++ .../Models/AWSEKSClusterDataModel.cs | 2 +- .../Models/AWSEKSClusterInformationModel.cs | 5 +++- .../ResourceDetectorUtils.cs | 20 ++++++++++++++++ .../SourceGenerationContext.cs | 24 +++++++++++++++++++ .../AWSXRaySamplerClient.cs | 21 ++++++++++++++-- src/OpenTelemetry.Sampler.AWS/CHANGELOG.md | 3 +++ .../GetSamplingRulesResponse.cs | 2 +- .../GetSamplingTargetsRequest.cs | 2 +- .../GetSamplingTargetsResponse.cs | 2 +- .../SourceGenerationContext.cs | 23 ++++++++++++++++++ ...nTelemetry.AotCompatibility.TestApp.csproj | 6 +++++ 20 files changed, 150 insertions(+), 7 deletions(-) create mode 100644 src/OpenTelemetry.ResourceDetectors.AWS/SourceGenerationContext.cs create mode 100644 src/OpenTelemetry.Sampler.AWS/SourceGenerationContext.cs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 813abdb5a8..c5dc47e66f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,8 +21,10 @@ jobs: build: ['build/**', '.github/**/*.yml', '!.github/workflows/package-*'] shared: ['src/Shared/**'] code: ['**.cs', '.editorconfig'] + aot: ['src/OpenTelemetry.Extensions.Enrichment/**'] aottestapp: ['test/OpenTelemetry.AotCompatibility.TestApp/**'] aspnet: ['*/OpenTelemetry.Instrumentation.AspNet*/**', 'examples/AspNet/**', '!**/*.md'] + aws: ['*/OpenTelemetry.*.AWS*/**', '!**/*.md'] azure: ['*/OpenTelemetry.ResourceDetectors.Azure*/**', '!**/*.md'] eventcounters: ['*/OpenTelemetry.Instrumentation.EventCounters*/**', 'examples/event-counters/**', '!**/*.md'] extensions: ['*/OpenTelemetry.Extensions/**', '*/OpenTelemetry.Extensions.Tests/**', '!**/*.md'] @@ -35,6 +37,7 @@ jobs: processdetector: ['*/OpenTelemetry.ResourceDetectors.Process/**', '*/OpenTelemetry.ResourceDetectors.Process.Tests/**', '!**/*.md'] processruntime: ['*/OpenTelemetry.ResourceDetectors.ProcessRuntime/**', '*/OpenTelemetry.ResourceDetectors.ProcessRuntime.Tests/**', '!**/*.md'] redis: ['*/OpenTelemetry.Instrumentation.StackExchangeRedis*/**', 'examples/redis/**', '!**/*.md'] + resourcedetectors: ['*/OpenTelemetry.ResourceDetectors.*/**', '!**/*.md'] runtime: ['*/OpenTelemetry.Instrumentation.Runtime*/**', 'examples/runtime-instrumentation/**', '!**/*.md'] wcf: ['*/OpenTelemetry.Instrumentation.Wcf*/**', 'examples/wcf/**', '!**/*.md'] solution: [ @@ -343,10 +346,14 @@ jobs: if: | contains(needs.detect-changes.outputs.changes, 'eventcounters') || contains(needs.detect-changes.outputs.changes, 'runtime') + || contains(needs.detect-changes.outputs.changes, 'aws') || contains(needs.detect-changes.outputs.changes, 'azure') + || contains(needs.detect-changes.outputs.changes, 'extensions') || contains(needs.detect-changes.outputs.changes, 'host') || contains(needs.detect-changes.outputs.changes, 'processdetector') || contains(needs.detect-changes.outputs.changes, 'processruntime') + || contains(needs.detect-changes.outputs.changes, 'resourcedetectors') + || contains(needs.detect-changes.outputs.changes, 'aot') || contains(needs.detect-changes.outputs.changes, 'aottestapp') || contains(needs.detect-changes.outputs.changes, 'build') || contains(needs.detect-changes.outputs.changes, 'redis') diff --git a/src/OpenTelemetry.Extensions.Enrichment/CHANGELOG.md b/src/OpenTelemetry.Extensions.Enrichment/CHANGELOG.md index 825c32f0d0..642075cb3f 100644 --- a/src/OpenTelemetry.Extensions.Enrichment/CHANGELOG.md +++ b/src/OpenTelemetry.Extensions.Enrichment/CHANGELOG.md @@ -1 +1,6 @@ # Changelog + +## Unreleased + +* Make Extensions.Enrichment AoT compatible. + ([#1541](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1541)) diff --git a/src/OpenTelemetry.Extensions.Enrichment/TraceEnrichmentProviderBuilderExtensions.cs b/src/OpenTelemetry.Extensions.Enrichment/TraceEnrichmentProviderBuilderExtensions.cs index 8596ce52ad..d0f412ed06 100644 --- a/src/OpenTelemetry.Extensions.Enrichment/TraceEnrichmentProviderBuilderExtensions.cs +++ b/src/OpenTelemetry.Extensions.Enrichment/TraceEnrichmentProviderBuilderExtensions.cs @@ -2,6 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 using System; +#if NET6_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; +#endif using Microsoft.Extensions.DependencyInjection; using OpenTelemetry.Internal; using OpenTelemetry.Trace; @@ -23,7 +26,11 @@ public static class TraceEnrichmentProviderBuilderExtensions /// /// Add this enricher *before* exporter related Activity processors. /// +#if NET6_0_OR_GREATER + public static TracerProviderBuilder AddTraceEnricher<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(this TracerProviderBuilder builder) +#else public static TracerProviderBuilder AddTraceEnricher(this TracerProviderBuilder builder) +#endif where T : TraceEnricher { Guard.ThrowIfNull(builder); diff --git a/src/OpenTelemetry.Extensions.Enrichment/TraceEnrichmentServiceCollectionExtensions.cs b/src/OpenTelemetry.Extensions.Enrichment/TraceEnrichmentServiceCollectionExtensions.cs index 9a20e64e6a..b78210a488 100644 --- a/src/OpenTelemetry.Extensions.Enrichment/TraceEnrichmentServiceCollectionExtensions.cs +++ b/src/OpenTelemetry.Extensions.Enrichment/TraceEnrichmentServiceCollectionExtensions.cs @@ -2,6 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 using System; +#if NET6_0_OR_GREATER +using System.Diagnostics.CodeAnalysis; +#endif using System.Linq; using Microsoft.Extensions.DependencyInjection.Extensions; using OpenTelemetry.Extensions.Enrichment; @@ -25,7 +28,11 @@ public static class TraceEnrichmentServiceCollectionExtensions /// /// Add this enricher *before* exporter related Activity processors. /// +#if NET6_0_OR_GREATER + public static IServiceCollection AddTraceEnricher<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(this IServiceCollection services) +#else public static IServiceCollection AddTraceEnricher(this IServiceCollection services) +#endif where T : TraceEnricher { Guard.ThrowIfNull(services); diff --git a/src/OpenTelemetry.ResourceDetectors.AWS/AWSEBSResourceDetector.cs b/src/OpenTelemetry.ResourceDetectors.AWS/AWSEBSResourceDetector.cs index 5bd0a07b2d..442ffe406c 100644 --- a/src/OpenTelemetry.ResourceDetectors.AWS/AWSEBSResourceDetector.cs +++ b/src/OpenTelemetry.ResourceDetectors.AWS/AWSEBSResourceDetector.cs @@ -87,6 +87,10 @@ internal static List> ExtractResourceAttributes(AWS internal static AWSEBSMetadataModel? GetEBSMetadata(string filePath) { +#if NET6_0_OR_GREATER + return ResourceDetectorUtils.DeserializeFromFile(filePath, SourceGenerationContext.Default.AWSEBSMetadataModel); +#else return ResourceDetectorUtils.DeserializeFromFile(filePath); +#endif } } diff --git a/src/OpenTelemetry.ResourceDetectors.AWS/AWSEC2ResourceDetector.cs b/src/OpenTelemetry.ResourceDetectors.AWS/AWSEC2ResourceDetector.cs index 615f44ab0f..dc75368995 100644 --- a/src/OpenTelemetry.ResourceDetectors.AWS/AWSEC2ResourceDetector.cs +++ b/src/OpenTelemetry.ResourceDetectors.AWS/AWSEC2ResourceDetector.cs @@ -83,7 +83,11 @@ internal static List> ExtractResourceAttributes(AWS internal static AWSEC2IdentityDocumentModel? DeserializeResponse(string response) { +#if NET6_0_OR_GREATER + return ResourceDetectorUtils.DeserializeFromString(response, SourceGenerationContext.Default.AWSEC2IdentityDocumentModel); +#else return ResourceDetectorUtils.DeserializeFromString(response); +#endif } private static string GetAWSEC2Token() diff --git a/src/OpenTelemetry.ResourceDetectors.AWS/AWSEKSResourceDetector.cs b/src/OpenTelemetry.ResourceDetectors.AWS/AWSEKSResourceDetector.cs index 82e4ea0170..a1c560da08 100644 --- a/src/OpenTelemetry.ResourceDetectors.AWS/AWSEKSResourceDetector.cs +++ b/src/OpenTelemetry.ResourceDetectors.AWS/AWSEKSResourceDetector.cs @@ -116,7 +116,11 @@ internal static List> ExtractResourceAttributes(str internal static AWSEKSClusterInformationModel? DeserializeResponse(string response) { +#if NET6_0_OR_GREATER + return ResourceDetectorUtils.DeserializeFromString(response, SourceGenerationContext.Default.AWSEKSClusterInformationModel); +#else return ResourceDetectorUtils.DeserializeFromString(response); +#endif } private static string? GetEKSClusterName(string credentials, HttpClientHandler? httpClientHandler) diff --git a/src/OpenTelemetry.ResourceDetectors.AWS/CHANGELOG.md b/src/OpenTelemetry.ResourceDetectors.AWS/CHANGELOG.md index 20b95715c6..d8e4dd31be 100644 --- a/src/OpenTelemetry.ResourceDetectors.AWS/CHANGELOG.md +++ b/src/OpenTelemetry.ResourceDetectors.AWS/CHANGELOG.md @@ -8,6 +8,8 @@ ([#1350](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1350)) * BREAKING: All Resource Detector classes marked as `sealed`. ([#1510](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1510)) +* Make OpenTelemetry.ResourceDetectors.AWS native AoT compatible. + ([#1541](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1541)) ## 1.3.0-beta.1 diff --git a/src/OpenTelemetry.ResourceDetectors.AWS/Models/AWSEC2IdentityDocumentModel.cs b/src/OpenTelemetry.ResourceDetectors.AWS/Models/AWSEC2IdentityDocumentModel.cs index bc88556a5b..4bd2f4dfef 100644 --- a/src/OpenTelemetry.ResourceDetectors.AWS/Models/AWSEC2IdentityDocumentModel.cs +++ b/src/OpenTelemetry.ResourceDetectors.AWS/Models/AWSEC2IdentityDocumentModel.cs @@ -1,17 +1,24 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using System.Text.Json.Serialization; + namespace OpenTelemetry.ResourceDetectors.AWS.Models; internal class AWSEC2IdentityDocumentModel { + [JsonPropertyName("accountId")] public string? AccountId { get; set; } + [JsonPropertyName("availabilityZone")] public string? AvailabilityZone { get; set; } + [JsonPropertyName("region")] public string? Region { get; set; } + [JsonPropertyName("instanceId")] public string? InstanceId { get; set; } + [JsonPropertyName("instanceType")] public string? InstanceType { get; set; } } diff --git a/src/OpenTelemetry.ResourceDetectors.AWS/Models/AWSEKSClusterDataModel.cs b/src/OpenTelemetry.ResourceDetectors.AWS/Models/AWSEKSClusterDataModel.cs index 7dfaeed930..0284548085 100644 --- a/src/OpenTelemetry.ResourceDetectors.AWS/Models/AWSEKSClusterDataModel.cs +++ b/src/OpenTelemetry.ResourceDetectors.AWS/Models/AWSEKSClusterDataModel.cs @@ -5,7 +5,7 @@ namespace OpenTelemetry.ResourceDetectors.AWS.Models; -internal class AWSEKSClusterDataModel +internal sealed class AWSEKSClusterDataModel { [JsonPropertyName("cluster.name")] public string? ClusterName { get; set; } diff --git a/src/OpenTelemetry.ResourceDetectors.AWS/Models/AWSEKSClusterInformationModel.cs b/src/OpenTelemetry.ResourceDetectors.AWS/Models/AWSEKSClusterInformationModel.cs index 7a9e6f9bac..b732eb7795 100644 --- a/src/OpenTelemetry.ResourceDetectors.AWS/Models/AWSEKSClusterInformationModel.cs +++ b/src/OpenTelemetry.ResourceDetectors.AWS/Models/AWSEKSClusterInformationModel.cs @@ -1,9 +1,12 @@ // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 +using System.Text.Json.Serialization; + namespace OpenTelemetry.ResourceDetectors.AWS.Models; -internal class AWSEKSClusterInformationModel +internal sealed class AWSEKSClusterInformationModel { + [JsonPropertyName("data")] public AWSEKSClusterDataModel? Data { get; set; } } diff --git a/src/OpenTelemetry.ResourceDetectors.AWS/ResourceDetectorUtils.cs b/src/OpenTelemetry.ResourceDetectors.AWS/ResourceDetectorUtils.cs index 6b3a76815c..331f8d86dd 100644 --- a/src/OpenTelemetry.ResourceDetectors.AWS/ResourceDetectorUtils.cs +++ b/src/OpenTelemetry.ResourceDetectors.AWS/ResourceDetectorUtils.cs @@ -7,6 +7,9 @@ using System.Net.Http; using System.Text; using System.Text.Json; +#if !NETFRAMEWORK +using System.Text.Json.Serialization.Metadata; +#endif using System.Threading.Tasks; namespace OpenTelemetry.ResourceDetectors.AWS; @@ -16,7 +19,9 @@ namespace OpenTelemetry.ResourceDetectors.AWS; /// internal static class ResourceDetectorUtils { +#if !NET6_0_OR_GREATER private static readonly JsonSerializerOptions JsonSerializerOptions = new(JsonSerializerDefaults.Web); +#endif internal static async Task SendOutRequest(string url, string method, KeyValuePair? header, HttpClientHandler? handler = null) { @@ -40,6 +45,20 @@ internal static async Task SendOutRequest(string url, string method, Key } } +#if NET6_0_OR_GREATER + internal static T? DeserializeFromFile(string filePath, JsonTypeInfo jsonTypeInfo) + { + using (var stream = GetStream(filePath)) + { + return JsonSerializer.Deserialize(stream, jsonTypeInfo); + } + } + + internal static T? DeserializeFromString(string json, JsonTypeInfo jsonTypeInfo) + { + return JsonSerializer.Deserialize(json, jsonTypeInfo); + } +#else internal static T? DeserializeFromFile(string filePath) { using (var stream = GetStream(filePath)) @@ -52,6 +71,7 @@ internal static async Task SendOutRequest(string url, string method, Key { return JsonSerializer.Deserialize(json, JsonSerializerOptions); } +#endif internal static Stream GetStream(string filePath) { diff --git a/src/OpenTelemetry.ResourceDetectors.AWS/SourceGenerationContext.cs b/src/OpenTelemetry.ResourceDetectors.AWS/SourceGenerationContext.cs new file mode 100644 index 0000000000..003f5b1d5d --- /dev/null +++ b/src/OpenTelemetry.ResourceDetectors.AWS/SourceGenerationContext.cs @@ -0,0 +1,24 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#if NET6_0_OR_GREATER +using System.Text.Json.Serialization; +using OpenTelemetry.ResourceDetectors.AWS.Models; + +namespace OpenTelemetry.ResourceDetectors.AWS; + +/// +/// "Source Generation" is feature added to System.Text.Json in .NET 6.0. +/// This is a performance optimization that avoids runtime reflection when performing serialization. +/// Serialization metadata will be computed at compile-time and included in the assembly. +/// . +/// . +/// . +/// +[JsonSerializable(typeof(AWSEBSMetadataModel))] +[JsonSerializable(typeof(AWSEC2IdentityDocumentModel))] +[JsonSerializable(typeof(AWSEKSClusterInformationModel))] +internal sealed partial class SourceGenerationContext : JsonSerializerContext +{ +} +#endif diff --git a/src/OpenTelemetry.Sampler.AWS/AWSXRaySamplerClient.cs b/src/OpenTelemetry.Sampler.AWS/AWSXRaySamplerClient.cs index b911038376..0b16ac5a89 100644 --- a/src/OpenTelemetry.Sampler.AWS/AWSXRaySamplerClient.cs +++ b/src/OpenTelemetry.Sampler.AWS/AWSXRaySamplerClient.cs @@ -38,7 +38,13 @@ public async Task> GetSamplingRules() try { - GetSamplingRulesResponse? getSamplingRulesResponse = JsonSerializer.Deserialize(responseJson); + GetSamplingRulesResponse? getSamplingRulesResponse = JsonSerializer +#if NET6_0_OR_GREATER + .Deserialize(responseJson, SourceGenerationContext.Default.GetSamplingRulesResponse); +#else + .Deserialize(responseJson); +#endif + if (getSamplingRulesResponse is not null) { if (getSamplingRulesResponse.SamplingRuleRecords is not null) @@ -66,7 +72,14 @@ public async Task> GetSamplingRules() public async Task GetSamplingTargets(GetSamplingTargetsRequest getSamplingTargetsRequest) { - var content = new StringContent(JsonSerializer.Serialize(getSamplingTargetsRequest), Encoding.UTF8, this.jsonContentType); + var json = JsonSerializer +#if NET6_0_OR_GREATER + .Serialize(getSamplingTargetsRequest, SourceGenerationContext.Default.GetSamplingTargetsRequest); +#else + .Serialize(getSamplingTargetsRequest); +#endif + + var content = new StringContent(json, Encoding.UTF8, this.jsonContentType); using var request = new HttpRequestMessage(HttpMethod.Post, this.getSamplingTargetsEndpoint) { @@ -78,7 +91,11 @@ public async Task> GetSamplingRules() try { GetSamplingTargetsResponse? getSamplingTargetsResponse = JsonSerializer +#if NET6_0_OR_GREATER + .Deserialize(responseJson, SourceGenerationContext.Default.GetSamplingTargetsResponse); +#else .Deserialize(responseJson); +#endif return getSamplingTargetsResponse; } diff --git a/src/OpenTelemetry.Sampler.AWS/CHANGELOG.md b/src/OpenTelemetry.Sampler.AWS/CHANGELOG.md index a28f47d696..0d912d6711 100644 --- a/src/OpenTelemetry.Sampler.AWS/CHANGELOG.md +++ b/src/OpenTelemetry.Sampler.AWS/CHANGELOG.md @@ -10,3 +10,6 @@ Initial release of `OpenTelemetry.Sampler.AWS`. * Update OpenTelemetry SDK version to `1.7.0`. ([#1486](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1486)) + +* Make OpenTelemetry.Sampler.AWS native AoT compatible. + ([#1541](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/pull/1541)) diff --git a/src/OpenTelemetry.Sampler.AWS/GetSamplingRulesResponse.cs b/src/OpenTelemetry.Sampler.AWS/GetSamplingRulesResponse.cs index 44d1dd2743..e43a88c1ac 100644 --- a/src/OpenTelemetry.Sampler.AWS/GetSamplingRulesResponse.cs +++ b/src/OpenTelemetry.Sampler.AWS/GetSamplingRulesResponse.cs @@ -6,7 +6,7 @@ namespace OpenTelemetry.Sampler.AWS; -internal class GetSamplingRulesResponse +internal sealed class GetSamplingRulesResponse { [JsonPropertyName("NextToken")] public string? NextToken { get; set; } diff --git a/src/OpenTelemetry.Sampler.AWS/GetSamplingTargetsRequest.cs b/src/OpenTelemetry.Sampler.AWS/GetSamplingTargetsRequest.cs index d24c96bd51..9702995e91 100644 --- a/src/OpenTelemetry.Sampler.AWS/GetSamplingTargetsRequest.cs +++ b/src/OpenTelemetry.Sampler.AWS/GetSamplingTargetsRequest.cs @@ -6,7 +6,7 @@ namespace OpenTelemetry.Sampler.AWS; -internal class GetSamplingTargetsRequest +internal sealed class GetSamplingTargetsRequest { public GetSamplingTargetsRequest(List documents) { diff --git a/src/OpenTelemetry.Sampler.AWS/GetSamplingTargetsResponse.cs b/src/OpenTelemetry.Sampler.AWS/GetSamplingTargetsResponse.cs index 7ec53f577b..772d37dca0 100644 --- a/src/OpenTelemetry.Sampler.AWS/GetSamplingTargetsResponse.cs +++ b/src/OpenTelemetry.Sampler.AWS/GetSamplingTargetsResponse.cs @@ -6,7 +6,7 @@ namespace OpenTelemetry.Sampler.AWS; -internal class GetSamplingTargetsResponse +internal sealed class GetSamplingTargetsResponse { public GetSamplingTargetsResponse( double lastRuleModification, diff --git a/src/OpenTelemetry.Sampler.AWS/SourceGenerationContext.cs b/src/OpenTelemetry.Sampler.AWS/SourceGenerationContext.cs new file mode 100644 index 0000000000..239c7dcfcc --- /dev/null +++ b/src/OpenTelemetry.Sampler.AWS/SourceGenerationContext.cs @@ -0,0 +1,23 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +#if NET6_0_OR_GREATER +using System.Text.Json.Serialization; + +namespace OpenTelemetry.Sampler.AWS; + +/// +/// "Source Generation" is feature added to System.Text.Json in .NET 6.0. +/// This is a performance optimization that avoids runtime reflection when performing serialization. +/// Serialization metadata will be computed at compile-time and included in the assembly. +/// . +/// . +/// . +/// +[JsonSerializable(typeof(GetSamplingRulesResponse))] +[JsonSerializable(typeof(GetSamplingTargetsRequest))] +[JsonSerializable(typeof(GetSamplingTargetsResponse))] +internal sealed partial class SourceGenerationContext : JsonSerializerContext +{ +} +#endif diff --git a/test/OpenTelemetry.AotCompatibility.TestApp/OpenTelemetry.AotCompatibility.TestApp.csproj b/test/OpenTelemetry.AotCompatibility.TestApp/OpenTelemetry.AotCompatibility.TestApp.csproj index f2e95fd1b0..911c7baf57 100644 --- a/test/OpenTelemetry.AotCompatibility.TestApp/OpenTelemetry.AotCompatibility.TestApp.csproj +++ b/test/OpenTelemetry.AotCompatibility.TestApp/OpenTelemetry.AotCompatibility.TestApp.csproj @@ -13,13 +13,19 @@ When adding projects here please also update the verify-aot-compat job in .github\workflows\ci.yml so that it runs for the project being added. --> + + + + + +