Skip to content

Commit

Permalink
Dynamically Pack MVVM SourceGen project outputs
Browse files Browse the repository at this point in the history
Previously, static output path to the MVVM SourceGen assembly was used to pack the MVVM project.
This leads to error when OutputPath was updated dynamically when testing or in forks.

So, here, we'll update the build so that the SourceGen build outputs will be dynamically packed.
  • Loading branch information
Nirmal4G committed Jun 7, 2022
1 parent a1fcd7e commit a1962c0
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" PrivateAssets="All" Pack="false" />
</ItemGroup>

<Import Project="$(BuildToolsDirectory)Community.Toolkit.GetBuildOutputs.targets" />

</Project>
36 changes: 26 additions & 10 deletions CommunityToolkit.Mvvm/CommunityToolkit.Mvvm.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -50,22 +50,38 @@
</ItemGroup>

<!-- Source generator project reference for packing -->
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<ItemGroup>
<ProjectReference Include="..\CommunityToolkit.Mvvm.SourceGenerators\CommunityToolkit.Mvvm.SourceGenerators.csproj"
ReferenceOutputAssembly="false" />
</ItemGroup>

<!-- Use 'GenerateNuSpecDependsOn' extensibility point to include source generator assets in the package -->
<PropertyGroup>
<GenerateNuSpecDependsOn>GetSourceGenBuildOutputs</GenerateNuSpecDependsOn>
</PropertyGroup>

<!--
Pack the source generator build outputs to the correct package folder for it to be used as an analyzer.
HACK: The recommended way to pack is to get the build outputs using the project's built-in targets,
and include them using NuGet's Pack targets. However, due to a bug in v5 of those targets,
the outputs were not included in the package. So, including them directly via final
output path and building it first is the only way to ensure they are included.
The following target uses 'GenerateNuSpecDependsOn' extensibility point to pack the build assets
by using the custom 'GetBuildOutputs' target imported within the target project.
-->
<ItemGroup>
<PackageFile Include="..\CommunityToolkit.Mvvm.SourceGenerators\bin\$(Configuration)\netstandard2.0\CommunityToolkit.Mvvm.SourceGenerators.dll"
TargetPath="analyzers\dotnet\roslyn4.0\cs" />
</ItemGroup>
<Target Name="GetSourceGenBuildOutputs"
BeforeTargets="_AddGlobalPackageFilesToNuGetPack"
AfterTargets="_CalculateInputsOutputsForPack">

<!-- Get the build outputs of the source generator project -->
<MSBuild
Projects="@(ProjectReference->WithMetadataValue('ReferenceOutputAssembly', 'false'))"
Targets="GetBuildOutputs">
<Output TaskParameter="TargetOutputs" ItemName="SourceGenBuildOutput" />
</MSBuild>

<ItemGroup>
<!-- Include the Build Outputs in the package via 'PackageFile' item -->
<PackageFile Include="@(SourceGenBuildOutput)" TargetPath="analyzers\dotnet\roslyn4.0\cs" />
<!-- Use the 'FinalOutputPath' metadata to mark the inputs thus enabling incremental builds -->
<NuGetPackInput Include="@(SourceGenBuildOutput->'%(FinalOutputPath)')" />
</ItemGroup>
</Target>

</Project>
2 changes: 1 addition & 1 deletion build/Community.Toolkit.Common.targets
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
Use 'TargetsForTfmSpecificContentInPackage' extensibility point to include custom TFM-specific assets in the package.
-->
<PropertyGroup>
<GenerateNuSpecDependsOn>_AddGlobalPackageFilesToNuGetPack</GenerateNuSpecDependsOn>
<GenerateNuSpecDependsOn>_AddGlobalPackageFilesToNuGetPack;$(GenerateNuSpecDependsOn)</GenerateNuSpecDependsOn>
<TargetsForTfmSpecificContentInPackage>_AddPackageFilesPerTargetFrameworkToNuGetPack</TargetsForTfmSpecificContentInPackage>
</PropertyGroup>

Expand Down
47 changes: 47 additions & 0 deletions build/Community.Toolkit.GetBuildOutputs.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<Project>

<!-- We depend on these Common Targets that gathers various Build Outputs of the included project -->
<PropertyGroup>
<_GetBuildOutputsDependsOn>BuiltProjectOutputGroup;DocumentationProjectOutputGroup</_GetBuildOutputsDependsOn>
<_GetBuildOutputsDependsOn Condition="'$(IncludeSymbols)' != 'false'">$(_GetBuildOutputsDependsOn);DebugSymbolsProjectOutputGroup</_GetBuildOutputsDependsOn>
<_GetBuildOutputsDependsOn Condition="'$(IncludeSatelliteAssemblies)' != 'false'">$(_GetBuildOutputsDependsOn);SatelliteDllsProjectOutputGroup</_GetBuildOutputsDependsOn>
</PropertyGroup>

<!-- Check for 'TargetFramework' input before proceeding. -->
<Target Name="_CheckTargetFrameworkInput"
DependsOnTargets="_CheckForInvalidConfigurationAndPlatform"
Condition="'$(TargetFramework)' == ''">
<Error Code="NCTDEV01" Text="The 'GetBuildOutputsPerTarget' target is meant to output the build items per 'TargetFramework'. Please call the target with a non-empty 'TargetFramework'."/>
</Target>

<!--
Gathers various Build Outputs from included Project per 'TargetFramework'.
This target is called in by the 'GetBuildOutputs' target to combine build outputs from all 'TargetFrameworks'.
-->
<Target Name="GetBuildOutputsPerTarget"
DependsOnTargets="_CheckTargetFrameworkInput;$(_GetBuildOutputsDependsOn)"
Returns="@(BuildOutputPerTarget)">
<ItemGroup>
<BuildOutputPerTarget Include="@(BuiltProjectOutputGroupOutput);@(DocumentationProjectOutputGroupOutput)"/>
<BuildOutputPerTarget Include="@(DebugSymbolsProjectOutputGroupOutput)" Condition="'$(IncludeSymbols)' != 'false'"/>
<BuildOutputPerTarget Include="@(SatelliteDllsProjectOutputGroupOutput)" Condition="'$(IncludeSatelliteAssemblies)' != 'false'"/>
<BuildOutputPerTarget Update="@(BuildOutputPerTarget)" TargetFramework="$(TargetFramework)"/>
</ItemGroup>
</Target>

<!--
Gathers various Build Outputs from this Project across 'TargetFrameworks'.
This target is called in the parent project to pack into a package.
-->
<Target Name="GetBuildOutputs"
DependsOnTargets="_GetTargetFrameworksOutput"
Returns="@(BuildOutput)">
<MSBuild
Projects="$(MSBuildProjectFullPath)"
Targets="GetBuildOutputsPerTarget"
Properties="TargetFramework=%(_TargetFrameworks.Identity)">
<Output TaskParameter="TargetOutputs" ItemName="BuildOutput" />
</MSBuild>
</Target>

</Project>

0 comments on commit a1962c0

Please sign in to comment.