Skip to content

Commit

Permalink
Merge pull request #911 from CommunityToolkit/dev/trim-feature-switches
Browse files Browse the repository at this point in the history
Allow trimming feature switches
  • Loading branch information
Sergio0694 authored Aug 20, 2024
2 parents 2fe303e + d8a442f commit cc35d3f
Show file tree
Hide file tree
Showing 10 changed files with 294 additions and 191 deletions.
4 changes: 2 additions & 2 deletions dotnet Community Toolkit.sln
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{CD16E790
build\Update-Headers.ps1 = build\Update-Headers.ps1
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Configuration", "Configuration", "{6640D447-C28D-4DBB-91F4-3ADCE0CA64AD}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "FeatureSwitches", "FeatureSwitches", "{6640D447-C28D-4DBB-91F4-3ADCE0CA64AD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.DisableINotifyPropertyChanging.UnitTests", "tests\CommunityToolkit.Mvvm.DisableINotifyPropertyChanging.UnitTests\CommunityToolkit.Mvvm.DisableINotifyPropertyChanging.UnitTests.csproj", "{9E09DA49-4389-4ECE-8B68-EBDB1221DA90}"
EndProject
Expand Down Expand Up @@ -81,7 +81,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.Exter
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.ExternalAssembly.Roslyn431", "tests\CommunityToolkit.Mvvm.ExternalAssembly.Roslyn431\CommunityToolkit.Mvvm.ExternalAssembly.Roslyn431.csproj", "{4FCD501C-1BB5-465C-AD19-356DAB6600C6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Mvvm.CodeFixers", "src\CommunityToolkit.Mvvm.CodeFixers\CommunityToolkit.Mvvm.CodeFixers.csproj", "{E79DCA2A-4C59-499F-85BD-F45215ED6B72}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Mvvm.CodeFixers", "src\CommunityToolkit.Mvvm.CodeFixers\CommunityToolkit.Mvvm.CodeFixers.csproj", "{E79DCA2A-4C59-499F-85BD-F45215ED6B72}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project>

<!-- Default values for all feature switches -->
<PropertyGroup>
<MvvmToolkitEnableINotifyPropertyChangingSupport Condition="'$(MvvmToolkitEnableINotifyPropertyChangingSupport)' == ''">true</MvvmToolkitEnableINotifyPropertyChangingSupport>
</PropertyGroup>

<!--
Configuration for the feature switches (to support IL trimming).
See the 'ILLink.Substitutions.xml' file for more details on that.
We only include these on .NET 6 and above (no .xml file otherwise).
-->
<ItemGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0'))">

<!-- MVVMTOOLKIT_ENABLE_INOTIFYPROPERTYCHANGING_SUPPORT switch -->
<RuntimeHostConfigurationOption Include="MVVMTOOLKIT_ENABLE_INOTIFYPROPERTYCHANGING_SUPPORT"
Value="$(MvvmToolkitEnableINotifyPropertyChangingSupport)"
Trim="true" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
<Project>

<!-- Get the analyzer from the CommunityToolkit.Mvvm NuGet package -->
<Target Name="MVVMToolkitGatherAnalyzers">
<ItemGroup>
<MVVMToolkitAnalyzer Include="@(Analyzer)" Condition="'%(Analyzer.NuGetPackageId)' == 'CommunityToolkit.Mvvm'" />
</ItemGroup>
</Target>

<!-- Remove the analyzer if Roslyn is missing -->
<Target Name="MVVMToolkitRemoveAnalyzersForRosynNotFound"
Condition="'$(CSharpCoreTargetsPath)' == ''"
AfterTargets="ResolvePackageDependenciesForBuild;ResolveNuGetPackageAssets"
DependsOnTargets="MVVMToolkitGatherAnalyzers">

<!-- If no Roslyn assembly could be found, just remove the analyzer without emitting a warning -->
<ItemGroup>
<Analyzer Remove="@(MVVMToolkitAnalyzer)"/>
</ItemGroup>
</Target>

<!-- Remove the analyzer if using Roslyn 3.x (incremental generators require Roslyn 4.x) -->
<Target Name="MVVMToolkitRemoveAnalyzersForRoslyn3"
Condition="'$(CSharpCoreTargetsPath)' != ''"
AfterTargets="ResolvePackageDependenciesForBuild;ResolveNuGetPackageAssets"
DependsOnTargets="MVVMToolkitGatherAnalyzers">

<!--
Use the CSharpCoreTargetsPath property to find the version of the compiler we are using. This is the same mechanism
MSBuild uses to find the compiler. We could check the assembly version for any compiler assembly (since they all have
the same version) but Microsoft.Build.Tasks.CodeAnalysis.dll is where MSBuild loads the compiler tasks from so if
someone is getting creative with msbuild tasks/targets this is the "most correct" assembly to check.
-->
<GetAssemblyIdentity AssemblyFiles="$([System.IO.Path]::Combine(`$([System.IO.Path]::GetDirectoryName($(CSharpCoreTargetsPath)))`,`Microsoft.Build.Tasks.CodeAnalysis.dll`))">
<Output TaskParameter="Assemblies" ItemName="MVVMToolkitCurrentCompilerAssemblyIdentity"/>
</GetAssemblyIdentity>

<PropertyGroup>

<!-- Transform the resulting item from GetAssemblyIdentity into a property representing its assembly version -->
<MVVMToolkitCurrentCompilerVersion>@(MVVMToolkitCurrentCompilerAssemblyIdentity->'%(Version)')</MVVMToolkitCurrentCompilerVersion>

<!-- The CurrentCompilerVersionIsNotNewEnough property can now be defined based on the Roslyn assembly version -->
<MVVMToolkitCurrentCompilerVersionIsNotNewEnough Condition="$([MSBuild]::VersionLessThan($(MVVMToolkitCurrentCompilerVersion), 4.0))">true</MVVMToolkitCurrentCompilerVersionIsNotNewEnough>
</PropertyGroup>

<!-- If the Roslyn version is < 4.0, disable the source generators -->
<ItemGroup Condition ="'$(MVVMToolkitCurrentCompilerVersionIsNotNewEnough)' == 'true'">
<Analyzer Remove="@(MVVMToolkitAnalyzer)"/>
</ItemGroup>

<!--
If the source generators are disabled, also emit a warning. This would've been produced by MSBuild itself as well, but
emitting this manually lets us customize the message to inform developers as to why exactly the generators have been
disabled, and that the rest of the MVVM Toolkit will still keep working as intended, just without additional features.
-->
<Warning Condition ="'$(MVVMToolkitCurrentCompilerVersionIsNotNewEnough)' == 'true'"
Code="MVVMTKCFG0001"
HelpLink="https://aka.ms/mvvmtoolkit/errors/mvvmtkcfg0001"
Text="The MVVM Toolkit source generators have been disabled on the current configuration, as they need Roslyn 4.x in order to work. The MVVM Toolkit will work just fine, but features relying on the source generators will not be available."/>

<PropertyGroup>

<!--
Setup the checks for the MVVMToolkitRemoveDuplicateAnalyzersWhenRoslynComponentVersioningIsNotSupported target below.
These are done here so that the following target can run only if it's sure this one has run, which is a necessary
condition to ensure all dependent MSBuild properties defined here will also be available there whenever needed.
-->
<MVVMToolkitIsManualRoslynMultiTargetingLogicNeeded Condition="'$(MVVMToolkitCurrentCompilerVersionIsNotNewEnough)' != 'true' AND '$(SupportsRoslynComponentVersioning)' != 'true'">true</MVVMToolkitIsManualRoslynMultiTargetingLogicNeeded>
</PropertyGroup>
</Target>

<!--
Manually remove duplicate analyzers if Roslyn component versioning is not supported (ie. if a legacy .csproj project is used).
This target is only run if Roslyn 4.0 or greater is present, as otherwise all analyzers would have already been removed anyway.
Additionally, skip this target if MVVMToolkitRemoveAnalyzersForRoslyn3 has been skipped (ie. if $(CSharpCoreTargetsPath) is not
defined, which will be the case on VB.NET projects). In these cases, MVVMToolkitRemoveAnalyzersForRosynNotFound will run at the
end and will remove all source generators (as they're only supported in C# projects), so there's nothing left to do here.
-->
<Target Name="MVVMToolkitRemoveDuplicateAnalyzersWhenRoslynComponentVersioningIsNotSupported"
Condition="'$(MVVMToolkitIsManualRoslynMultiTargetingLogicNeeded)' == 'true'"
AfterTargets="MVVMToolkitRemoveAnalyzersForRoslyn3">

<!--
This switch manually implements Roslyn component versioning. That is, it checks the current version of Roslyn and
removes and removes all analyzers except the highest version that is supported. The fallback is just Roslyn 4.0.
-->
<PropertyGroup>
<MVVMToolkitSelectedRoslynAnalyzerDirectoryName Condition="$([MSBuild]::VersionGreaterThanOrEquals($(MVVMToolkitCurrentCompilerVersion), 4.3))">roslyn4.3</MVVMToolkitSelectedRoslynAnalyzerDirectoryName>
<MVVMToolkitSelectedRoslynAnalyzerDirectoryName Condition="'$(MVVMToolkitSelectedRoslynAnalyzerDirectoryName)' == ''">roslyn4.0</MVVMToolkitSelectedRoslynAnalyzerDirectoryName>
</PropertyGroup>
<ItemGroup>

<!--
This condition is a bit convoluted, but it's essentially just selecting all analyzers from the NuGet package that don't have the target Roslyn directory name in their full path.
For instance, if Roslyn 4.3 is the highest supported version, the target directory name will be "roslyn 4.3", and this condition will filter out all analyzers with a path such
as: "C:\...\.nuget\...\CommunityToolkit.Mvvm\analyzers\roslyn4.0\cs\CommunityToolkit.Mvvm". The [System.String]::Concat trick is used to achieve two things: we can't directly
invoke a property function (ie. Contains in this case) on a metadata item, so we need an intermediate string to invoke it on. We could also use [System.String]::new, but using
Concat is more efficient as it'll just skip the allocation entirely if one of the two inputs is an empty string, which is the case here.
-->
<Analyzer Remove="@(MVVMToolkitAnalyzer)" Condition="!$([System.String]::Concat('', '%(MVVMToolkitAnalyzer.FullPath)').Contains('$(MVVMToolkitSelectedRoslynAnalyzerDirectoryName)'))"/>
</ItemGroup>
</Target>

<!--
Inform the user if packages.config is used (as the analyzers and the source generators
won't work at all). Since packages.config can only be used with legacy-style projects,
the entire package can be skipped if an SDK-style project is used.
-->
<Target Name="MVVMToolkitWarnForPackagesConfigUse"
AfterTargets="ResolvePackageDependenciesForBuild;ResolveNuGetPackageAssets"
Condition="'$(UsingMicrosoftNetSDK)' != 'true'">

<!--
Check whether packages are being restored via packages.config, by reading the associated MSBuild property.
This happens when either the project style is using packages.config, or when explicitly requested.
See https://learn.microsoft.com/nuget/reference/msbuild-targets#restoring-packagereference-and-packagesconfig-projects-with-msbuild.
-->
<PropertyGroup>
<MVVMToolkitIsTargetProjectUsingPackagesConfig Condition ="'$(RestorePackagesConfig)' == 'true' OR '$(RestoreProjectStyle)' == 'PackagesConfig'">true</MVVMToolkitIsTargetProjectUsingPackagesConfig>
</PropertyGroup>

<!--
If no packages.config properties are set, also try to manually find the packages.config file.
This will be in the @(None) elements, if present. Doing so makes sure this works in builds as
well, since the implicit targets populating the properties above only run when restoring.
Since the packages.config file will always be in the root of the project, if present, we will
match with the full item spec (see https://learn.microsoft.com/nuget/reference/packages-config).
-->
<FindInList ItemSpecToFind="packages.config"
List="@(None)"
MatchFileNameOnly="false"
Condition="'$(MVVMToolkitIsTargetProjectUsingPackagesConfig)' != 'true'">
<Output TaskParameter="ItemFound" PropertyName="MVVMToolkitPackagesConfigFile"/>
</FindInList>

<!-- Make sure to update the MSBuild property if the above task did find something -->
<PropertyGroup>
<MVVMToolkitIsTargetProjectUsingPackagesConfig Condition ="'$(MVVMToolkitPackagesConfigFile)' == 'packages.config'">true</MVVMToolkitIsTargetProjectUsingPackagesConfig>
</PropertyGroup>

<!-- Emit a warning in case packages.config is used -->
<Warning Condition ="'$(MVVMToolkitIsTargetProjectUsingPackagesConfig)' == 'true'"
Code="MVVMTKCFG0002"
HelpLink="https://aka.ms/mvvmtoolkit/errors/mvvmtkcfg0002"
Text="The MVVM Toolkit source generators might not be loaded correctly, as the current project is using the packages.config setup to restore NuGet packages. Source generators require PackageReference to be used (either in a legacy-style or SDK-style .csproj project, both are supported as long as PackageReference is used)."/>
</Target>

</Project>
18 changes: 13 additions & 5 deletions src/CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" />
</ItemGroup>

<!-- Include the ILLink file (to properly trim configuration switches in publish builds) -->
<ItemGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0'))">
<EmbeddedResource Include="Properties\ILLink.Substitutions.xml" LogicalName="ILLink.Substitutions.xml" />
</ItemGroup>

<!-- Reference CsWinRT when targeting Windows, to get the latest source generators -->
<ItemGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0-windows10.0.17763.0'))">
<PackageReference Include="Microsoft.Windows.CsWinRT" Version="2.1.1" PrivateAssets="all" />
Expand Down Expand Up @@ -91,11 +96,13 @@

<ItemGroup Label="Package">

<!-- Include the custom .targets file to check the source generator (.NET 6 and 8 are not needed as they guarantee Roslyn 4.x) -->
<None Include="CommunityToolkit.Mvvm.targets" PackagePath="buildTransitive\netstandard2.0" Pack="true" />
<None Include="CommunityToolkit.Mvvm.targets" PackagePath="buildTransitive\netstandard2.1" Pack="true" />
<None Include="CommunityToolkit.Mvvm.targets" PackagePath="build\netstandard2.0" Pack="true" />
<None Include="CommunityToolkit.Mvvm.targets" PackagePath="build\netstandard2.1" Pack="true" />
<!-- Include the custom .targets files (shared across TFMs) -->
<None Include="CommunityToolkit.Mvvm.FeatureSwitches.targets" PackagePath="build" Pack="true" />
<None Include="CommunityToolkit.Mvvm.SourceGenerators.targets" PackagePath="build" Pack="true" />
<None Include="CommunityToolkit.Mvvm.targets" PackagePath="build" Pack="true" />
<None Include="CommunityToolkit.Mvvm.FeatureSwitches.targets" PackagePath="buildTransitive" Pack="true" />
<None Include="CommunityToolkit.Mvvm.SourceGenerators.targets" PackagePath="buildTransitive" Pack="true" />
<None Include="CommunityToolkit.Mvvm.targets" PackagePath="buildTransitive" Pack="true" />

<!--
Pack the source generator to the right package folders (each matching the target Roslyn version).
Expand All @@ -112,4 +119,5 @@
<None Include="..\CommunityToolkit.Mvvm.CodeFixers\bin\$(Configuration)\netstandard2.0\CommunityToolkit.Mvvm.CodeFixers.dll" PackagePath="analyzers\dotnet\roslyn4.0\cs" Pack="true" Visible="false" />
<None Include="..\CommunityToolkit.Mvvm.CodeFixers\bin\$(Configuration)\netstandard2.0\CommunityToolkit.Mvvm.CodeFixers.dll" PackagePath="analyzers\dotnet\roslyn4.3\cs" Pack="true" Visible="false" />
</ItemGroup>

</Project>
Loading

0 comments on commit cc35d3f

Please sign in to comment.