diff --git a/components/AppServices/CommunityToolkit.AppServices.SourceGenerators/AppServiceGenerator.Helpers.cs b/components/AppServices/CommunityToolkit.AppServices.SourceGenerators/AppServiceGenerator.Helpers.cs index 94c333763..ed86c952a 100644 --- a/components/AppServices/CommunityToolkit.AppServices.SourceGenerators/AppServiceGenerator.Helpers.cs +++ b/components/AppServices/CommunityToolkit.AppServices.SourceGenerators/AppServiceGenerator.Helpers.cs @@ -1,8 +1,9 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; namespace CommunityToolkit.AppServices.SourceGenerators; @@ -18,10 +19,27 @@ private static class Helpers /// Gets whether the current target is a UWP application. /// /// The input instance to inspect. + /// The analyzer options to use to get info on the target application. /// Whether the current target is a UWP application. - public static bool IsUwpTarget(Compilation compilation) + public static bool IsUwpTarget(Compilation compilation, AnalyzerConfigOptions analyzerOptions) { - return compilation.Options.OutputKind == OutputKind.WindowsRuntimeApplication; + // If the application type is a Windows Runtime application, then it's for sure a UWP app + if (compilation.Options.OutputKind == OutputKind.WindowsRuntimeApplication) + { + return true; + } + + // Otherwise, the application is UWP if "UseUwpTools" is set + if (analyzerOptions.TryGetValue("build_property.UseUwpTools", out string? propertyValue)) + { + if (bool.TryParse(propertyValue, out bool useUwpTools)) + { + return true; + } + } + + // The app is definitely not a UWP app + return false; } } } diff --git a/components/AppServices/CommunityToolkit.AppServices.SourceGenerators/AppServiceGenerator.cs b/components/AppServices/CommunityToolkit.AppServices.SourceGenerators/AppServiceGenerator.cs index 466f313bc..f85e2a73b 100644 --- a/components/AppServices/CommunityToolkit.AppServices.SourceGenerators/AppServiceGenerator.cs +++ b/components/AppServices/CommunityToolkit.AppServices.SourceGenerators/AppServiceGenerator.cs @@ -24,13 +24,12 @@ public void Initialize(IncrementalGeneratorInitializationContext context) { // Get all app service class implementations, and only enable this branch if the target is not a UWP app (the component) IncrementalValuesProvider<(HierarchyInfo Hierarchy, AppServiceInfo Info)> appServiceComponentInfo = - context.SyntaxProvider - .CreateSyntaxProvider( + context.CreateSyntaxProviderWithOptions( static (node, _) => node is ClassDeclarationSyntax classDeclaration && classDeclaration.HasOrPotentiallyHasBaseTypes(), static (context, token) => { // Only retrieve host info if the target is not a UWP application - if (Helpers.IsUwpTarget(context.SemanticModel.Compilation)) + if (Helpers.IsUwpTarget(context.SemanticModel.Compilation, context.GlobalOptions)) { return default; } @@ -80,14 +79,13 @@ public void Initialize(IncrementalGeneratorInitializationContext context) // Gather all interfaces, and only enable this branch if the target is a UWP app (the host) IncrementalValuesProvider<(HierarchyInfo Hierarchy, AppServiceInfo Info)> appServiceHostInfo = - context.SyntaxProvider - .ForAttributeWithMetadataName( + context.ForAttributeWithMetadataNameAndOptions( "CommunityToolkit.AppServices.AppServiceAttribute", static (node, _) => node is InterfaceDeclarationSyntax, static (context, token) => { // Only retrieve host info if the target is a UWP application - if (!Helpers.IsUwpTarget(context.SemanticModel.Compilation)) + if (!Helpers.IsUwpTarget(context.SemanticModel.Compilation, context.GlobalOptions)) { return default; } diff --git a/components/AppServices/CommunityToolkit.AppServices.SourceGenerators/Extensions/GeneratorAttributeSyntaxContextWithOptions.cs b/components/AppServices/CommunityToolkit.AppServices.SourceGenerators/Extensions/GeneratorAttributeSyntaxContextWithOptions.cs new file mode 100644 index 000000000..7f7596e01 --- /dev/null +++ b/components/AppServices/CommunityToolkit.AppServices.SourceGenerators/Extensions/GeneratorAttributeSyntaxContextWithOptions.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace CommunityToolkit.AppServices.SourceGenerators.Extensions; + +/// +/// +/// +/// The original value. +/// The original value. +internal readonly struct GeneratorAttributeSyntaxContextWithOptions( + GeneratorAttributeSyntaxContext syntaxContext, + AnalyzerConfigOptions globalOptions) +{ + /// + public SyntaxNode TargetNode { get; } = syntaxContext.TargetNode; + + /// + public ISymbol TargetSymbol { get; } = syntaxContext.TargetSymbol; + + /// + public SemanticModel SemanticModel { get; } = syntaxContext.SemanticModel; + + /// + public ImmutableArray Attributes { get; } = syntaxContext.Attributes; + + /// + public AnalyzerConfigOptions GlobalOptions { get; } = globalOptions; +} diff --git a/components/AppServices/CommunityToolkit.AppServices.SourceGenerators/Extensions/GeneratorSyntaxContextWithOptions.cs b/components/AppServices/CommunityToolkit.AppServices.SourceGenerators/Extensions/GeneratorSyntaxContextWithOptions.cs new file mode 100644 index 000000000..141087948 --- /dev/null +++ b/components/AppServices/CommunityToolkit.AppServices.SourceGenerators/Extensions/GeneratorSyntaxContextWithOptions.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace CommunityToolkit.AppServices.SourceGenerators.Extensions; + +/// +/// +/// +/// The original value. +/// The original value. +internal readonly struct GeneratorSyntaxContextWithOptions( + GeneratorSyntaxContext syntaxContext, + AnalyzerConfigOptions globalOptions) +{ + /// + public SyntaxNode Node { get; } = syntaxContext.Node; + + /// + public SemanticModel SemanticModel { get; } = syntaxContext.SemanticModel; + + /// + public AnalyzerConfigOptions GlobalOptions { get; } = globalOptions; +} diff --git a/components/AppServices/CommunityToolkit.AppServices.SourceGenerators/Extensions/IncrementalGeneratorInitializationContextExtensions.cs b/components/AppServices/CommunityToolkit.AppServices.SourceGenerators/Extensions/IncrementalGeneratorInitializationContextExtensions.cs new file mode 100644 index 000000000..d8e900d9b --- /dev/null +++ b/components/AppServices/CommunityToolkit.AppServices.SourceGenerators/Extensions/IncrementalGeneratorInitializationContextExtensions.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace CommunityToolkit.AppServices.SourceGenerators.Extensions; + +/// +/// Extension methods for . +/// +internal static class IncrementalGeneratorInitializationContextExtensions +{ + /// + public static IncrementalValuesProvider ForAttributeWithMetadataNameAndOptions( + this IncrementalGeneratorInitializationContext context, + string fullyQualifiedMetadataName, + Func predicate, + Func transform) + { + // Invoke 'ForAttributeWithMetadataName' normally, but just return the context directly + IncrementalValuesProvider syntaxContext = context.SyntaxProvider.ForAttributeWithMetadataName( + fullyQualifiedMetadataName, + predicate, + static (context, token) => context); + + // Do the same for the analyzer config options + IncrementalValueProvider configOptions = context.AnalyzerConfigOptionsProvider.Select(static (provider, token) => provider.GlobalOptions); + + // Merge the two and invoke the provided transform on these two values. Neither value + // is equatable, meaning the pipeline will always re-run until this point. This is + // intentional: we don't want any symbols or other expensive objects to be kept alive + // across incremental steps, especially if they could cause entire compilations to be + // rooted, which would significantly increase memory use and introduce more GC pauses. + // In this specific case, flowing non equatable values in a pipeline is therefore fine. + return syntaxContext.Combine(configOptions).Select((input, token) => transform(new GeneratorAttributeSyntaxContextWithOptions(input.Left, input.Right), token)); + } + + /// + public static IncrementalValuesProvider CreateSyntaxProviderWithOptions( + this IncrementalGeneratorInitializationContext context, + Func predicate, + Func transform) + { + // Invoke 'ForAttributeWithMetadataName' normally, but just return the context directly + IncrementalValuesProvider syntaxContext = context.SyntaxProvider.CreateSyntaxProvider( + predicate, + static (context, token) => context); + + // Do the same for the analyzer config options + IncrementalValueProvider configOptions = context.AnalyzerConfigOptionsProvider.Select(static (provider, token) => provider.GlobalOptions); + + // Merge the two and invoke the provided transform, like the extension above + return syntaxContext.Combine(configOptions).Select((input, token) => transform(new GeneratorSyntaxContextWithOptions(input.Left, input.Right), token)); + } +} diff --git a/components/AppServices/src/AppServiceComponent.cs b/components/AppServices/src/AppServiceComponent.cs index 3e70a95f0..2692f96fe 100644 --- a/components/AppServices/src/AppServiceComponent.cs +++ b/components/AppServices/src/AppServiceComponent.cs @@ -14,7 +14,7 @@ using System.Diagnostics.CodeAnalysis; using CommunityToolkit.AppServices.Helpers; -#pragma warning disable CA2213, CA1063 +#pragma warning disable CA2213, CA1063, CsWinRT1028 namespace CommunityToolkit.AppServices; diff --git a/components/AppServices/src/CommunityToolkit.AppServices.csproj b/components/AppServices/src/CommunityToolkit.AppServices.csproj index e0f0267bc..ed88531c1 100644 --- a/components/AppServices/src/CommunityToolkit.AppServices.csproj +++ b/components/AppServices/src/CommunityToolkit.AppServices.csproj @@ -36,7 +36,7 @@ - + @@ -49,13 +49,8 @@ - - - - - - - + + diff --git a/components/AppServices/src/CommunityToolkit.AppServices.targets b/components/AppServices/src/CommunityToolkit.AppServices.targets index 78089e0a2..21ddc5cf2 100644 --- a/components/AppServices/src/CommunityToolkit.AppServices.targets +++ b/components/AppServices/src/CommunityToolkit.AppServices.targets @@ -1,5 +1,10 @@ + + + + +