Msbuild: How to run MSBuild target only once before build?

Created on 21 Sep 2017  路  10Comments  路  Source: dotnet/msbuild

I have a multi targeting project with custom MSBuild target.

<Target Name="MyTarget" BeforeTargets="BeforeCompile">

The target is executed multiple times during build. Once for each target framework. Is there any way to make sure it will run only once?

Most helpful comment

Easiest way is to change the BeforeTargets to hook to the specific targets:

  <PropertyGroup>
    <TargetFrameworks>netstandard1.3;netstandard2.0</TargetFrameworks>
  </PropertyGroup>

  <Target Name="CustomOnOuterBuild" BeforeTargets="DispatchToInnerBuilds">
    <Message Importance="high" Text="I'm running in the outer build (before). TFs: $(TargetFrameworks), TF: $(TargetFramework)" />
  </Target>

  <Target Name="CustomOnInnerBuild" BeforeTargets="BeforeBuild">
    <Message Importance="high" Text="I'm running in the inner build (before). TFs: $(TargetFrameworks), TF: $(TargetFramework)" />
  </Target>

  <Target Name="CustomOnAfterBuild" AfterTargets="Build">
    <Message Importance="high" Text="I'm running in the outer build (after).  TFs: $(TargetFrameworks), TF: $(TargetFramework)" Condition=" '$(IsCrossTargetingBuild)' == 'true' " />
    <Message Importance="high" Text="I'm running in the innter build (after). TFs: $(TargetFrameworks), TF: $(TargetFramework)" Condition=" '$(IsCrossTargetingBuild)' != 'true' " />
  </Target>

Should print

$ dotnet build
Microsoft (R) Build Engine version 15.4.8.50001 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  I'm running in the outer build (before). TFs: netstandard1.3;netstandard2.0, TF:
  I'm running in the inner build (before). TFs: netstandard1.3;netstandard2.0, TF: netstandard1.3
  I'm running in the inner build (before). TFs: netstandard1.3;netstandard2.0, TF: netstandard2.0
  testlib -> /Users/martin.ullrich/tmp/testlib/bin/Debug/netstandard1.3/testlib.dll
  I'm running in the innter build (after). TFs: netstandard1.3;netstandard2.0, TF: netstandard1.3
  testlib -> /Users/martin.ullrich/tmp/testlib/bin/Debug/netstandard2.0/testlib.dll
  I'm running in the innter build (after). TFs: netstandard1.3;netstandard2.0, TF: netstandard2.0
  I'm running in the outer build (after).  TFs: netstandard1.3;netstandard2.0, TF:

While you could also use BeforeTargets="Build" on the outer build target, this will run after the build since the multi-targeting Build target depends on the inner builds.

@djanosik the compile targets (BeforeCompile and friends) are only executed in the inner builds so you'd need to hook on a different target as well.

All 10 comments

I have the same issue with a custom MSBuild task that shuld run only once per build cycle, but instead runs for every target framework. Is there maybe another target for this purpose?

Any update on this? My use-case is that I am building a custom non-MS project once per build (unrelated to target frameworks) and then I just want the output of the project to get copied in to bin\Debug\<target_framework_specific_dir>. Currently there is no way to do that. I will have to either add a Condition in Target saying to build just once and then not sure how i can copy it to other target framework's out directory.

I came up with a workaround, have a look at my .targets and .props files here:
https://github.com/BalassaMarton/MSBump/tree/master/MSBump
This solution uses a lock file to prevent the target from running when inside a multi-targeting build.

Easiest way is to change the BeforeTargets to hook to the specific targets:

  <PropertyGroup>
    <TargetFrameworks>netstandard1.3;netstandard2.0</TargetFrameworks>
  </PropertyGroup>

  <Target Name="CustomOnOuterBuild" BeforeTargets="DispatchToInnerBuilds">
    <Message Importance="high" Text="I'm running in the outer build (before). TFs: $(TargetFrameworks), TF: $(TargetFramework)" />
  </Target>

  <Target Name="CustomOnInnerBuild" BeforeTargets="BeforeBuild">
    <Message Importance="high" Text="I'm running in the inner build (before). TFs: $(TargetFrameworks), TF: $(TargetFramework)" />
  </Target>

  <Target Name="CustomOnAfterBuild" AfterTargets="Build">
    <Message Importance="high" Text="I'm running in the outer build (after).  TFs: $(TargetFrameworks), TF: $(TargetFramework)" Condition=" '$(IsCrossTargetingBuild)' == 'true' " />
    <Message Importance="high" Text="I'm running in the innter build (after). TFs: $(TargetFrameworks), TF: $(TargetFramework)" Condition=" '$(IsCrossTargetingBuild)' != 'true' " />
  </Target>

Should print

$ dotnet build
Microsoft (R) Build Engine version 15.4.8.50001 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.

  I'm running in the outer build (before). TFs: netstandard1.3;netstandard2.0, TF:
  I'm running in the inner build (before). TFs: netstandard1.3;netstandard2.0, TF: netstandard1.3
  I'm running in the inner build (before). TFs: netstandard1.3;netstandard2.0, TF: netstandard2.0
  testlib -> /Users/martin.ullrich/tmp/testlib/bin/Debug/netstandard1.3/testlib.dll
  I'm running in the innter build (after). TFs: netstandard1.3;netstandard2.0, TF: netstandard1.3
  testlib -> /Users/martin.ullrich/tmp/testlib/bin/Debug/netstandard2.0/testlib.dll
  I'm running in the innter build (after). TFs: netstandard1.3;netstandard2.0, TF: netstandard2.0
  I'm running in the outer build (after).  TFs: netstandard1.3;netstandard2.0, TF:

While you could also use BeforeTargets="Build" on the outer build target, this will run after the build since the multi-targeting Build target depends on the inner builds.

@djanosik the compile targets (BeforeCompile and friends) are only executed in the inner builds so you'd need to hook on a different target as well.

@dasMulli The "outer build (after)" message does not appear when the target is defined in a targets file in my NuGet package. Any idea how to detect "after outer" in that case? The other messages work, so I'm suspicious that IsCrossTargetingBuild isn't working in this case.

NuGet targets only contribute to the inner builds since NuGet only resolves assets (including msbuild targets) per target framework involved. You can see the generated import statements in the obj*.g.nuget.props|targets files.

If you want to contribute to project logic in general without actually needing a target framework, maybe MSBuild SDKs are what you are looking for.
These can basically be NuGet packages with 麓Sdk\Sdk.propsandSdk\Sdk.targets` files that will be imported by MSBuild itself and doesn't interfere with the nuget package graph of the project (that way it also works for packages.config projects ^^)

NuGet targets only contribute to the inner builds since NuGet only resolves assets (including msbuild targets) per target framework involved. You can see the generated import statements in the obj*.g.nuget.props|targets files.

This is true for normal props/targets in the build/ folder, but you can have a buildMultitargeting/ folder in the package that is imported only for the "outer" no-TargetFramework build.

I don't think using an SDK helps here.

@rainersigwald buildMultitargeting/ makes nearly all the common macros empty, including anything that would imply which project the package is in or where "bin/release" is actually located. Any ideas?

Edit: Also, buildMultitargeting does not support non-SDK-style projects. Using both causes my target to run multiple times. And I found no way to differentiate between non-SDK-style project build and inner builds in my NuGet task. I gave up hope.

Condition="'$(TargetFrameworks)' == '' OR $(TargetFrameworks.EndsWith($(TargetFramework)))" 
Was this page helpful?
0 / 5 - 0 ratings