Skip to content

Commit e1e1bf7

Browse files
committed
Add support for modern .NET to AppServices library
1 parent 7b163ce commit e1e1bf7

9 files changed

+155
-16
lines changed

components/AppServices/CommunityToolkit.AppServices.SourceGenerators/AppServiceGenerator.Helpers.cs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
// Licensed to the .NET Foundation under one or more agreements.
1+
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

55
using Microsoft.CodeAnalysis;
6+
using Microsoft.CodeAnalysis.Diagnostics;
67

78
namespace CommunityToolkit.AppServices.SourceGenerators;
89

@@ -18,10 +19,27 @@ private static class Helpers
1819
/// Gets whether the current target is a UWP application.
1920
/// </summary>
2021
/// <param name="compilation">The input <see cref="Compilation"/> instance to inspect.</param>
22+
/// <param name="analyzerOptions">The analyzer options to use to get info on the target application.</param>
2123
/// <returns>Whether the current target is a UWP application.</returns>
22-
public static bool IsUwpTarget(Compilation compilation)
24+
public static bool IsUwpTarget(Compilation compilation, AnalyzerConfigOptions analyzerOptions)
2325
{
24-
return compilation.Options.OutputKind == OutputKind.WindowsRuntimeApplication;
26+
// If the application type is a Windows Runtime application, then it's for sure a UWP app
27+
if (compilation.Options.OutputKind == OutputKind.WindowsRuntimeApplication)
28+
{
29+
return true;
30+
}
31+
32+
// Otherwise, the application is UWP if "UseUwpTools" is set
33+
if (analyzerOptions.TryGetValue("build_property.UseUwpTools", out string? propertyValue))
34+
{
35+
if (bool.TryParse(propertyValue, out bool useUwpTools))
36+
{
37+
return true;
38+
}
39+
}
40+
41+
// The app is definitely not a UWP app
42+
return false;
2543
}
2644
}
2745
}

components/AppServices/CommunityToolkit.AppServices.SourceGenerators/AppServiceGenerator.cs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,12 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
2424
{
2525
// Get all app service class implementations, and only enable this branch if the target is not a UWP app (the component)
2626
IncrementalValuesProvider<(HierarchyInfo Hierarchy, AppServiceInfo Info)> appServiceComponentInfo =
27-
context.SyntaxProvider
28-
.CreateSyntaxProvider(
27+
context.CreateSyntaxProviderWithOptions(
2928
static (node, _) => node is ClassDeclarationSyntax classDeclaration && classDeclaration.HasOrPotentiallyHasBaseTypes(),
3029
static (context, token) =>
3130
{
3231
// Only retrieve host info if the target is not a UWP application
33-
if (Helpers.IsUwpTarget(context.SemanticModel.Compilation))
32+
if (Helpers.IsUwpTarget(context.SemanticModel.Compilation, context.GlobalOptions))
3433
{
3534
return default;
3635
}
@@ -80,14 +79,13 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
8079

8180
// Gather all interfaces, and only enable this branch if the target is a UWP app (the host)
8281
IncrementalValuesProvider<(HierarchyInfo Hierarchy, AppServiceInfo Info)> appServiceHostInfo =
83-
context.SyntaxProvider
84-
.ForAttributeWithMetadataName(
82+
context.ForAttributeWithMetadataNameAndOptions(
8583
"CommunityToolkit.AppServices.AppServiceAttribute",
8684
static (node, _) => node is InterfaceDeclarationSyntax,
8785
static (context, token) =>
8886
{
8987
// Only retrieve host info if the target is a UWP application
90-
if (!Helpers.IsUwpTarget(context.SemanticModel.Compilation))
88+
if (!Helpers.IsUwpTarget(context.SemanticModel.Compilation, context.GlobalOptions))
9189
{
9290
return default;
9391
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Collections.Immutable;
6+
using Microsoft.CodeAnalysis;
7+
using Microsoft.CodeAnalysis.Diagnostics;
8+
9+
namespace CommunityToolkit.AppServices.SourceGenerators.Extensions;
10+
11+
/// <summary>
12+
/// <inheritdoc cref="GeneratorAttributeSyntaxContext" path="/summary/node()"/>
13+
/// </summary>
14+
/// <param name="syntaxContext">The original <see cref="GeneratorAttributeSyntaxContext"/> value.</param>
15+
/// <param name="globalOptions">The original <see cref="AnalyzerConfigOptions"/> value.</param>
16+
internal readonly struct GeneratorAttributeSyntaxContextWithOptions(
17+
GeneratorAttributeSyntaxContext syntaxContext,
18+
AnalyzerConfigOptions globalOptions)
19+
{
20+
/// <inheritdoc cref="GeneratorAttributeSyntaxContext.TargetNode"/>
21+
public SyntaxNode TargetNode { get; } = syntaxContext.TargetNode;
22+
23+
/// <inheritdoc cref="GeneratorAttributeSyntaxContext.TargetSymbol"/>
24+
public ISymbol TargetSymbol { get; } = syntaxContext.TargetSymbol;
25+
26+
/// <inheritdoc cref="GeneratorAttributeSyntaxContext.SemanticModel"/>
27+
public SemanticModel SemanticModel { get; } = syntaxContext.SemanticModel;
28+
29+
/// <inheritdoc cref="GeneratorAttributeSyntaxContext.Attributes"/>
30+
public ImmutableArray<AttributeData> Attributes { get; } = syntaxContext.Attributes;
31+
32+
/// <inheritdoc cref="AnalyzerConfigOptionsProvider.GlobalOptions"/>
33+
public AnalyzerConfigOptions GlobalOptions { get; } = globalOptions;
34+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using Microsoft.CodeAnalysis;
6+
using Microsoft.CodeAnalysis.Diagnostics;
7+
8+
namespace CommunityToolkit.AppServices.SourceGenerators.Extensions;
9+
10+
/// <summary>
11+
/// <inheritdoc cref="GeneratorSyntaxContext" path="/summary/node()"/>
12+
/// </summary>
13+
/// <param name="syntaxContext">The original <see cref="GeneratorSyntaxContext"/> value.</param>
14+
/// <param name="globalOptions">The original <see cref="AnalyzerConfigOptions"/> value.</param>
15+
internal readonly struct GeneratorSyntaxContextWithOptions(
16+
GeneratorSyntaxContext syntaxContext,
17+
AnalyzerConfigOptions globalOptions)
18+
{
19+
/// <inheritdoc cref="GeneratorSyntaxContext.Node"/>
20+
public SyntaxNode Node { get; } = syntaxContext.Node;
21+
22+
/// <inheritdoc cref="GeneratorSyntaxContext.SemanticModel"/>
23+
public SemanticModel SemanticModel { get; } = syntaxContext.SemanticModel;
24+
25+
/// <inheritdoc cref="AnalyzerConfigOptionsProvider.GlobalOptions"/>
26+
public AnalyzerConfigOptions GlobalOptions { get; } = globalOptions;
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Threading;
7+
using Microsoft.CodeAnalysis;
8+
using Microsoft.CodeAnalysis.Diagnostics;
9+
10+
namespace CommunityToolkit.AppServices.SourceGenerators.Extensions;
11+
12+
/// <summary>
13+
/// Extension methods for <see cref="IncrementalGeneratorInitializationContext"/>.
14+
/// </summary>
15+
internal static class IncrementalGeneratorInitializationContextExtensions
16+
{
17+
/// <inheritdoc cref="SyntaxValueProvider.ForAttributeWithMetadataName"/>
18+
public static IncrementalValuesProvider<T> ForAttributeWithMetadataNameAndOptions<T>(
19+
this IncrementalGeneratorInitializationContext context,
20+
string fullyQualifiedMetadataName,
21+
Func<SyntaxNode, CancellationToken, bool> predicate,
22+
Func<GeneratorAttributeSyntaxContextWithOptions, CancellationToken, T> transform)
23+
{
24+
// Invoke 'ForAttributeWithMetadataName' normally, but just return the context directly
25+
IncrementalValuesProvider<GeneratorAttributeSyntaxContext> syntaxContext = context.SyntaxProvider.ForAttributeWithMetadataName(
26+
fullyQualifiedMetadataName,
27+
predicate,
28+
static (context, token) => context);
29+
30+
// Do the same for the analyzer config options
31+
IncrementalValueProvider<AnalyzerConfigOptions> configOptions = context.AnalyzerConfigOptionsProvider.Select(static (provider, token) => provider.GlobalOptions);
32+
33+
// Merge the two and invoke the provided transform on these two values. Neither value
34+
// is equatable, meaning the pipeline will always re-run until this point. This is
35+
// intentional: we don't want any symbols or other expensive objects to be kept alive
36+
// across incremental steps, especially if they could cause entire compilations to be
37+
// rooted, which would significantly increase memory use and introduce more GC pauses.
38+
// In this specific case, flowing non equatable values in a pipeline is therefore fine.
39+
return syntaxContext.Combine(configOptions).Select((input, token) => transform(new GeneratorAttributeSyntaxContextWithOptions(input.Left, input.Right), token));
40+
}
41+
42+
/// <inheritdoc cref="SyntaxValueProvider.CreateSyntaxProvider"/>
43+
public static IncrementalValuesProvider<T> CreateSyntaxProviderWithOptions<T>(
44+
this IncrementalGeneratorInitializationContext context,
45+
Func<SyntaxNode, CancellationToken, bool> predicate,
46+
Func<GeneratorSyntaxContextWithOptions, CancellationToken, T> transform)
47+
{
48+
// Invoke 'ForAttributeWithMetadataName' normally, but just return the context directly
49+
IncrementalValuesProvider<GeneratorSyntaxContext> syntaxContext = context.SyntaxProvider.CreateSyntaxProvider(
50+
predicate,
51+
static (context, token) => context);
52+
53+
// Do the same for the analyzer config options
54+
IncrementalValueProvider<AnalyzerConfigOptions> configOptions = context.AnalyzerConfigOptionsProvider.Select(static (provider, token) => provider.GlobalOptions);
55+
56+
// Merge the two and invoke the provided transform, like the extension above
57+
return syntaxContext.Combine(configOptions).Select((input, token) => transform(new GeneratorSyntaxContextWithOptions(input.Left, input.Right), token));
58+
}
59+
}

components/AppServices/src/AppServiceComponent.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
using System.Diagnostics.CodeAnalysis;
1515
using CommunityToolkit.AppServices.Helpers;
1616

17-
#pragma warning disable CA2213, CA1063
17+
#pragma warning disable CA2213, CA1063, CsWinRT1028
1818

1919
namespace CommunityToolkit.AppServices;
2020

components/AppServices/src/CommunityToolkit.AppServices.csproj

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
</ItemGroup>
3737

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

@@ -49,10 +49,8 @@
4949
<ItemGroup Label="Package">
5050

5151
<!-- Include the custom .targets file to check the source generator -->
52-
<None Include="CommunityToolkit.AppServices.targets" PackagePath="buildTransitive\netstandard2.0" Pack="true" />
53-
<None Include="CommunityToolkit.AppServices.targets" PackagePath="buildTransitive\uap10.0" Pack="true" />
54-
<None Include="CommunityToolkit.AppServices.targets" PackagePath="build\netstandard2.0" Pack="true" />
55-
<None Include="CommunityToolkit.AppServices.targets" PackagePath="build\uap10.0" Pack="true" />
52+
<None Include="CommunityToolkit.AppServices.targets" PackagePath="buildTransitive" Pack="true" />
53+
<None Include="CommunityToolkit.AppServices.targets" PackagePath="build" Pack="true" />
5654

5755
<!-- Pack the source generator to the right package folder -->
5856
<None Include="..\CommunityToolkit.AppServices.SourceGenerators\bin\$(Configuration)\netstandard2.0\CommunityToolkit.AppServices.SourceGenerators.dll" PackagePath="analyzers\dotnet\cs" Pack="true" Visible="false" />

components/AppServices/src/CommunityToolkit.AppServices.targets

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
<Project>
22

3+
<!-- Allow the source generators to detect whether an app on modern .NET is UWP or not -->
4+
<ItemGroup>
5+
<CompilerVisibleProperty Include="UseUwpTools" />
6+
</ItemGroup>
7+
38
<!-- Get the analyzer from the CommunityToolkit.AppServices NuGet package -->
49
<Target Name="CommunityToolkitAppServicesGatherAnalyzers">
510
<ItemGroup>

components/AppServices/src/MultiTarget.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
MultiTarget is a custom property that indicates which target a project is designed to be built for / run on.
55
Used to create project references, generate solution files, enable/disable TargetFrameworks, and build nuget packages.
66
-->
7-
<MultiTarget>uwp;netstandard;</MultiTarget>
7+
<MultiTarget>uwp;netstandard;net8.0-windows10.0.17763.0</MultiTarget>
88
</PropertyGroup>
99
</Project>

0 commit comments

Comments
 (0)