Run a target exactly once
If you're building a .NET project for multiple frameworks using .NET SDK multitargeting, generally each build is a complete build of all relevant targets. However, in some cases, you want a certain target to only run once, for example, a target that increments a version.
Set up the targets
The solution shown here runs the MyBeforeBuildTarget
once and once only regardless of whether a single framework is being built, or if multiple frameworks are being built, without a change in the target itself.
Add
Condition
expressions that use theMultitarget
property to determine whether to build multiple frameworks, or just one.<PropertyGroup> <TargetFrameworks Condition=" '$(Multitarget)' == 'true' ">net7.0;net8.0</TargetFrameworks> <TargetFramework Condition=" '$(Multitarget)' != 'true' ">net8.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <SuppressNETCoreSdkPreviewMessage>true</SuppressNETCoreSdkPreviewMessage> </PropertyGroup>
Add three targets to the project, yours that you want to run just once (
MyBeforeBuildTarget
), the other two as shown here:<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFrameworks Condition=" '$(Multitarget)' == 'true' ">net7.0;net8.0</TargetFrameworks> <TargetFramework Condition=" '$(Multitarget)' != 'true' ">net8.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <SuppressNETCoreSdkPreviewMessage>true</SuppressNETCoreSdkPreviewMessage> </PropertyGroup> <Target Name="MyBeforeBuildTarget"> <Warning Text="MyBeforeBuildTarget" /> </Target> <Target Name="BuildMyBeforeBuildTargetBeforeOuterBuild" DependsOnTargets="MyBeforeBuildTarget" BeforeTargets="DispatchToInnerBuilds" /> <Target Name="BuildMyBeforeBuildTargetBeforeInnerBuild" BeforeTargets="BeforeBuild"> <MSBuild Projects="$(MSBuildProjectFullPath)" Targets="MyBeforeBuildTarget" RemoveProperties="TargetFramework" /> </Target> </Project>
This method relies on the way that multitargeting works in .NET SDK projects. The outer build sets TargetFrameworks
but the inner builds set TargetFramework
for each desired target. Therefore, to build once for the inner build, remove the TargetFramework
property. This invokes the outer build with the desired target instead of running it in each of the inner builds.
Build the project
From the command line, build without multitargeting:
dotnet msbuild -nologo -tl:false -bl -clp:nosummary -p:Multitarget=false
ExtendMaybeMultitargeted.csproj(12,5): warning : MyBeforeBuildThing ExtendMaybeMultitargeted -> ExtendMaybeMultitargeted\bin\Debug\net8.0\ExtendMaybeMultitargeted.dll
Now try building with multitargeting:
dotnet msbuild -nologo -tl:false -bl -clp:nosummary -p:Multitarget=true
ExtendMaybeMultitargeted\ExtendMaybeMultitargeted.csproj(12,5): warning : MyBeforeBuildThing ExtendMaybeMultitargeted -> ExtendMaybeMultitargeted\bin\Debug\net8.0\ExtendMaybeMultitargeted.dll ExtendMaybeMultitargeted -> ExtendMaybeMultitargeted\bin\Debug\net7.0\ExtendMaybeMultitargeted.dll
As you can see in the output, the warning was only emitted once regardless of whether multitargeting was used or not.
The solution shown here works because the target you want to run once, MyBeforeBuildTarget
, is tied to a different target usingBeforeTargets
, but regardless of whether multitargeting is true or false, the target it's attached to is always one that runs once. The outer build is a single target, DispatchToInnerBuilds
, that runs the inner build for all the different frameworks specified in TargetFrameworks
. DispatchToInnerBuilds
itself only runs once for a multitargeted build, but for a single targeted build, it doesn't run at all. For a single targeted build, you set BeforeTargets
to BeforeBuild
as usual to run your target, but the Condition
makes sure that it's only attached to BeforeBuild
in the single-targeted case.