Skip to content

Commit

Permalink
Merge pull request #576 from CommunityToolkit/user/sergiopedri/uwp-ne…
Browse files Browse the repository at this point in the history
…t9-appservices

Add support for modern .NET to AppServices library
  • Loading branch information
Arlodotexe authored Oct 14, 2024
2 parents f05e004 + d423cfe commit c9ed532
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 18 deletions.
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" />
</ItemGroup>

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

0 comments on commit c9ed532

Please sign in to comment.