Skip to content

Commit

Permalink
Support C# resource filtering by .NET version (#281)
Browse files Browse the repository at this point in the history
Motivation
----------
The current filtering approach is limited to only .NET Standard vs .NET Core and modern .NET. There are reasons to also allow filtering based on specific .NET versions.

Modifications
-------------
- Move a lot of duplicate logic in the .Client projects to the Directory.Build.targets file.
- Allow `.netX.Y.cs` suffixes on resource files in the .Client projects to limit the target versions they apply to.
  • Loading branch information
brantburnett authored Feb 1, 2025
1 parent 65f3dbd commit 8380f19
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 40 deletions.
2 changes: 2 additions & 0 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,7 @@
despite the fact that .NET 6 is out of support. Support will be dropped from Yardarm at a later date.
-->
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>

<IsClientProject Condition=" '$(IsClientProject)' == '' And $(MSBuildProjectName.EndsWith('.Client'))">true</IsClientProject>
</PropertyGroup>
</Project>
37 changes: 36 additions & 1 deletion src/Directory.Build.targets
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<Project>
<PropertyGroup Condition=" '$(IsPackable)' != 'false' Or $(MSBuildProjectFile) == 'Yardarm.UnitTests.csproj' ">
<PropertyGroup Condition=" ('$(IsPackable)' != 'false' And '$(IsClientProject)' != 'true') Or $(MSBuildProjectFile) == 'Yardarm.UnitTests.csproj' ">
<!-- We must do this in targets, not props, so that we have the values set in the csproj file for the condition -->
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)Yardarm.snk</AssemblyOriginatorKeyFile>
Expand All @@ -22,4 +22,39 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<!-- .Client projects -->

<PropertyGroup Condition=" '$(IsClientProject)' == 'true' ">
<RootNamespace>RootNamespace</RootNamespace>
<DefineConstants>$(DefineConstants);FORTESTS</DefineConstants>
</PropertyGroup>

<ItemGroup Condition=" '$(IsClientProject)' == 'true' And $([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0'))">
<Compile Remove="**/*.netstandard.cs" />
<None Include="**/*.netstandard.cs" />
</ItemGroup>

<ItemGroup Condition=" '$(IsClientProject)' == 'true' And !$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0'))">
<Compile Remove="**/*.net6.0.cs" />
<Compile Remove="**/*.netcoreapp.cs" />
<None Include="**/*.net6.0.cs" />
<None Include="**/*.netcoreapp.cs" />
</ItemGroup>

<ItemGroup Condition=" '$(IsClientProject)' == 'true' And !$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">
<Compile Remove="**/*.net7.0.cs" />
<None Include="**/*.net7.0.cs" />
</ItemGroup>

<ItemGroup Condition=" '$(IsClientProject)' == 'true' And !$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">
<Compile Remove="**/*.net8.0.cs" />
<None Include="**/*.net8.0.cs" />
</ItemGroup>

<ItemGroup Condition=" '$(IsClientProject)' == 'true' And !$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net9.0'))">
<Compile Remove="**/*.net9.0.cs" />
<None Include="**/*.net9.0.cs" />
</ItemGroup>

</Project>
8 changes: 0 additions & 8 deletions src/main/Yardarm.Client/Yardarm.Client.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,10 @@

<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0;net8.0</TargetFrameworks>
<RootNamespace>RootNamespace</RootNamespace>
<OutputType>Library</OutputType>

<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>

<DefineConstants>$(DefineConstants);FORTESTS</DefineConstants>
</PropertyGroup>

<PropertyGroup>
Expand All @@ -24,9 +21,4 @@
<PackageReference Include="System.Collections.Immutable" />
</ItemGroup>

<ItemGroup>
<Compile Remove="**/*.netstandard.cs" Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0'))" />
<Compile Remove="**/*.netcoreapp.cs" Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0'))" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@

<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0;net8.0</TargetFrameworks>
<RootNamespace>RootNamespace</RootNamespace>
<OutputType>Library</OutputType>

<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>

<PropertyGroup>
Expand All @@ -22,9 +20,4 @@
<ProjectReference Include="..\Yardarm.Client\Yardarm.Client.csproj" />
</ItemGroup>

<ItemGroup>
<Compile Remove="**/*.netstandard.cs" Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0'))" />
<Compile Remove="**/*.netcoreapp.cs" Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0'))" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@

<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0;net8.0</TargetFrameworks>
<RootNamespace>RootNamespace</RootNamespace>
<OutputType>Library</OutputType>

<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
Expand All @@ -18,9 +16,4 @@
<ProjectReference Include="..\Yardarm.Client\Yardarm.Client.csproj" />
</ItemGroup>

<ItemGroup>
<Compile Remove="**/*.netstandard.cs" Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0'))" />
<Compile Remove="**/*.netcoreapp.cs" Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0'))" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,9 @@

<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0;net8.0</TargetFrameworks>
<RootNamespace>RootNamespace</RootNamespace>
<OutputType>Library</OutputType>

<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>

<DefineConstants>$(DefineConstants);FORTESTS</DefineConstants>
</PropertyGroup>

<PropertyGroup>
Expand All @@ -26,11 +22,4 @@
<ProjectReference Include="..\Yardarm.Client\Yardarm.Client.csproj" />
</ItemGroup>

<ItemGroup>
<Compile Remove="**/*.netstandard.cs" Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0'))" />
<None Include="**/*.netstandard.cs" Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0'))" />
<Compile Remove="**/*.netcoreapp.cs" Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0'))" />
<None Include="**/*.netcoreapp.cs" Condition="!$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0'))" />
</ItemGroup>

</Project>
1 change: 1 addition & 0 deletions src/main/Yardarm.sln
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0F27CDEF-318E-4305-AE2B-AA44C670E7C2}"
ProjectSection(SolutionItems) = preProject
..\Directory.Build.props = ..\Directory.Build.props
..\Directory.Build.targets = ..\Directory.Build.targets
Directory.Packages.props = Directory.Packages.props
EndProjectSection
EndProject
Expand Down
72 changes: 66 additions & 6 deletions src/main/Yardarm/Generation/ResourceSyntaxTreeGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
Expand All @@ -12,8 +14,29 @@

namespace Yardarm.Generation
{
public abstract class ResourceSyntaxTreeGenerator : ISyntaxTreeGenerator
public abstract partial class ResourceSyntaxTreeGenerator : ISyntaxTreeGenerator
{
[GeneratedRegex(@"\.netstandard\.cs$")]
private static partial Regex NetStandardSuffix();

[GeneratedRegex(@"\.netcoreapp\.cs$")]
private static partial Regex NetCoreAppSuffix();

[GeneratedRegex(@"\.net\d+\.\d+\.cs$")]
private static partial Regex AnyNetNumberSuffix();

[GeneratedRegex(@"\.net6\.0\.cs$")]
private static partial Regex Net60Suffix();

[GeneratedRegex(@"\.net7\.0\.cs$")]
private static partial Regex Net70Suffix();

[GeneratedRegex(@"\.net8\.0\.cs$")]
private static partial Regex Net80Suffix();

[GeneratedRegex(@"\.net9\.0\.cs$")]
private static partial Regex Net90Suffix();

private static readonly UTF8Encoding s_utf8NoBom = new(encoderShouldEmitUTF8Identifier: false);
private static ReadOnlySpan<byte> RootNamespaceBytes => "RootNamespace"u8;

Expand All @@ -31,19 +54,23 @@ protected ResourceSyntaxTreeGenerator(GenerationContext generationContext, IRoot
RootNamespace = rootNamespace;
}

public virtual IEnumerable<Regex> GetResourceNameExclusions() =>
GenerationContext.CurrentTargetFramework.Framework ==
NuGetFrameworkConstants.NetStandardFramework
? [NetCoreAppSuffix(), AnyNetNumberSuffix()]
: [NetStandardSuffix(), .. GetNetVersionSuffixExclusions(GenerationContext.CurrentTargetFramework.Version)];

public virtual IEnumerable<SyntaxTree> Generate()
{
string excludeSuffix = GenerationContext.CurrentTargetFramework.Framework ==
NuGetFrameworkConstants.NetStandardFramework
? ".netcoreapp.cs"
: ".netstandard.cs";
Regex[] excludeSuffixes = GetResourceNameExclusions().ToArray();

byte[] namespaceName = s_utf8NoBom.GetBytes(RootNamespace.Name.ToString());

var result = new List<SyntaxTree>();
foreach (string resourceName in GetType().Assembly.GetManifestResourceNames())
{
if (resourceName.StartsWith(ResourcePrefix) && resourceName.EndsWith(".cs") && !resourceName.EndsWith(excludeSuffix))
if (resourceName.StartsWith(ResourcePrefix) && resourceName.EndsWith(".cs") &&
!IsAnyMatch(excludeSuffixes, resourceName))
{
result.Add(ParseResource(resourceName, namespaceName));
}
Expand Down Expand Up @@ -88,5 +115,38 @@ private unsafe SyntaxTree ParseResource(string resourceName, ReadOnlySpan<byte>

return syntaxTree;
}

private static IEnumerable<Regex> GetNetVersionSuffixExclusions(Version version)
{
if (version.Major < 6)
{
yield return Net60Suffix();
}
if (version.Major < 7)
{
yield return Net70Suffix();
}
if (version.Major < 8)
{
yield return Net80Suffix();
}
if (version.Major < 9)
{
yield return Net90Suffix();
}
}

private static bool IsAnyMatch(Regex[] patterns, string input)
{
foreach (Regex pattern in patterns)
{
if (pattern.IsMatch(input))
{
return true;
}
}

return false;
}
}
}

0 comments on commit 8380f19

Please sign in to comment.