Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for modern .NET to AppServices library #576

Merged
merged 2 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -18,10 +19,27 @@ private static class Helpers
/// Gets whether the current target is a UWP application.
/// </summary>
/// <param name="compilation">The input <see cref="Compilation"/> instance to inspect.</param>
/// <param name="analyzerOptions">The analyzer options to use to get info on the target application.</param>
/// <returns>Whether the current target is a UWP application.</returns>
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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// <inheritdoc cref="GeneratorAttributeSyntaxContext" path="/summary/node()"/>
/// </summary>
/// <param name="syntaxContext">The original <see cref="GeneratorAttributeSyntaxContext"/> value.</param>
/// <param name="globalOptions">The original <see cref="AnalyzerConfigOptions"/> value.</param>
internal readonly struct GeneratorAttributeSyntaxContextWithOptions(
GeneratorAttributeSyntaxContext syntaxContext,
AnalyzerConfigOptions globalOptions)
{
/// <inheritdoc cref="GeneratorAttributeSyntaxContext.TargetNode"/>
public SyntaxNode TargetNode { get; } = syntaxContext.TargetNode;

/// <inheritdoc cref="GeneratorAttributeSyntaxContext.TargetSymbol"/>
public ISymbol TargetSymbol { get; } = syntaxContext.TargetSymbol;

/// <inheritdoc cref="GeneratorAttributeSyntaxContext.SemanticModel"/>
public SemanticModel SemanticModel { get; } = syntaxContext.SemanticModel;

/// <inheritdoc cref="GeneratorAttributeSyntaxContext.Attributes"/>
public ImmutableArray<AttributeData> Attributes { get; } = syntaxContext.Attributes;

/// <inheritdoc cref="AnalyzerConfigOptionsProvider.GlobalOptions"/>
public AnalyzerConfigOptions GlobalOptions { get; } = globalOptions;
}
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// <inheritdoc cref="GeneratorSyntaxContext" path="/summary/node()"/>
/// </summary>
/// <param name="syntaxContext">The original <see cref="GeneratorSyntaxContext"/> value.</param>
/// <param name="globalOptions">The original <see cref="AnalyzerConfigOptions"/> value.</param>
internal readonly struct GeneratorSyntaxContextWithOptions(
GeneratorSyntaxContext syntaxContext,
AnalyzerConfigOptions globalOptions)
{
/// <inheritdoc cref="GeneratorSyntaxContext.Node"/>
public SyntaxNode Node { get; } = syntaxContext.Node;

/// <inheritdoc cref="GeneratorSyntaxContext.SemanticModel"/>
public SemanticModel SemanticModel { get; } = syntaxContext.SemanticModel;

/// <inheritdoc cref="AnalyzerConfigOptionsProvider.GlobalOptions"/>
public AnalyzerConfigOptions GlobalOptions { get; } = globalOptions;
}
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Extension methods for <see cref="IncrementalGeneratorInitializationContext"/>.
/// </summary>
internal static class IncrementalGeneratorInitializationContextExtensions
{
/// <inheritdoc cref="SyntaxValueProvider.ForAttributeWithMetadataName"/>
public static IncrementalValuesProvider<T> ForAttributeWithMetadataNameAndOptions<T>(
this IncrementalGeneratorInitializationContext context,
string fullyQualifiedMetadataName,
Func<SyntaxNode, CancellationToken, bool> predicate,
Func<GeneratorAttributeSyntaxContextWithOptions, CancellationToken, T> transform)
{
// Invoke 'ForAttributeWithMetadataName' normally, but just return the context directly
IncrementalValuesProvider<GeneratorAttributeSyntaxContext> syntaxContext = context.SyntaxProvider.ForAttributeWithMetadataName(
fullyQualifiedMetadataName,
predicate,
static (context, token) => context);

// Do the same for the analyzer config options
IncrementalValueProvider<AnalyzerConfigOptions> 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));
}

/// <inheritdoc cref="SyntaxValueProvider.CreateSyntaxProvider"/>
public static IncrementalValuesProvider<T> CreateSyntaxProviderWithOptions<T>(
this IncrementalGeneratorInitializationContext context,
Func<SyntaxNode, CancellationToken, bool> predicate,
Func<GeneratorSyntaxContextWithOptions, CancellationToken, T> transform)
{
// Invoke 'ForAttributeWithMetadataName' normally, but just return the context directly
IncrementalValuesProvider<GeneratorSyntaxContext> syntaxContext = context.SyntaxProvider.CreateSyntaxProvider(
predicate,
static (context, token) => context);

// Do the same for the analyzer config options
IncrementalValueProvider<AnalyzerConfigOptions> 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));
}
}
2 changes: 1 addition & 1 deletion components/AppServices/src/AppServiceComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
11 changes: 3 additions & 8 deletions components/AppServices/src/CommunityToolkit.AppServices.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
</ItemGroup>

<!-- Add the contracts package reference to access WinRT APIs from .NET Standard -->
<ItemGroup Condition="'$(IsUwp)' != 'true'">
<ItemGroup Condition="'$(IsUwp)' != 'true' AND !$([MSBuild]::IsTargetFrameworkCompatible('net8.0-windows', '$(TargetFramework)'))">
<PackageReference Include="Microsoft.Windows.SDK.Contracts" Version="10.0.22621.2" />
</ItemGroup>

Expand All @@ -49,13 +49,8 @@
<ItemGroup Label="Package">

<!-- Include the custom .targets file to check the source generator -->
<None Include="CommunityToolkit.AppServices.targets" PackagePath="buildTransitive\netstandard2.0" Pack="true" />
<None Include="CommunityToolkit.AppServices.targets" PackagePath="buildTransitive\uap10.0" Pack="true" />
<None Include="CommunityToolkit.AppServices.targets" PackagePath="buildTransitive\net8.0-windows10.0.22621" Pack="true" />

<None Include="CommunityToolkit.AppServices.targets" PackagePath="build\netstandard2.0" Pack="true" />
<None Include="CommunityToolkit.AppServices.targets" PackagePath="build\uap10.0" Pack="true" />
<None Include="CommunityToolkit.AppServices.targets" PackagePath="build\net8-windows10.0.22621" Pack="true" />
<None Include="CommunityToolkit.AppServices.targets" PackagePath="buildTransitive" Pack="true" />
<None Include="CommunityToolkit.AppServices.targets" PackagePath="build" Pack="true" />

<!-- Pack the source generator to the right package folder -->
<None Include="..\CommunityToolkit.AppServices.SourceGenerators\bin\$(Platform)\$(Configuration)\netstandard2.0\CommunityToolkit.AppServices.SourceGenerators.dll" PackagePath="analyzers\dotnet\cs" Pack="true" Visible="false" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
<Project>

<!-- Allow the source generators to detect whether an app on modern .NET is UWP or not -->
<ItemGroup>
<CompilerVisibleProperty Include="UseUwpTools" />
Sergio0694 marked this conversation as resolved.
Show resolved Hide resolved
</ItemGroup>

<!-- Get the analyzer from the CommunityToolkit.AppServices NuGet package -->
<Target Name="CommunityToolkitAppServicesGatherAnalyzers">
<ItemGroup>
Expand Down