Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make an MSBuild Target that only runs once instead of once, before Targets that run once per framework in the TargetFrameworks tag?

I have a code generator tool that I partially own, and now that csproj files can list multiple Target Frameworks in them and building builds all of them, I am trying to figure out how to make an MSBuild Target to do the code generation only once per running the build, no matter how many Target Frameworks are listed, and have the compiling for each of the Target Frameworks wait until the code generation has completed.

I currently have it conditional on a specific value of TargetFramework. For example, Condition="'netstandard2.0' == '$(TargetFramework)'".

This avoids the code generation tool being kicked off for each Target Framework at the same time and then getting access denied errors as the processes try to create/update the same files.

However, the other Target Frameworks go right to trying to compile without waiting for the code generation to complete, and fail without that code existing.

I would like to have my code generation happen only once each time a project is built and have the compiling for each Target Framework only begin once that has finished.

It needs to run each time a build is run, in case the inputs have changed and it generates different code.

Note: For the moment I am ignoring a case where #if FrameworkSpecificDefine is used to have different input code to the code generator, such that different Target Frameworks causes different output from the code generator. For now, I am considering the output of the code generator to be the same and valid across all Target Frameworks.

Update: After looking for a Target that happens before MSBuild splits into TargetFramework specific builds, that I could then hook to build before, I see this in Detailed Build Output in VS:

1>Target _SetBuildInnerTarget:
1>Target _ComputeTargetFrameworkItems:
1>Target DispatchToInnerBuilds:
1>  Using "MSBuild" task from assembly "Microsoft.Build.Tasks.Core, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a".
1>  Task "MSBuild"
1>    Additional Properties for project "ProjectA.csproj":
1>      TargetFramework=netstandard2.0
1>    Additional Properties for project "ProjectA.csproj":
1>      TargetFramework=net47

I then set my target as BeforeTargets="DispatchToInnerBuilds" and it runs before the individual builds that set TargetFramework specifically and seems to meet my needs exactly.

(Adding to the BuildDependsOn property doesn't seem to work any longer: Add a msbuild task that runs after building a .NET Core project in Visual Studio 2017 RC; I suspect that Microsoft.Common.targets gets evaluated later in the new csproj format, and any appending to properties that you do in the project file are overwritten by Microsoft.Common.targets.)

like image 778
Jeremy Morton Avatar asked Dec 10 '22 09:12

Jeremy Morton


1 Answers

On single target framework I only use BeforeTargets="PreBuildEvent":

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>
  <Target Name="GenerateVersionInfo" BeforeTargets="PreBuildEvent">
    <Exec Command="your custom command" />
  </Target>
</Project>

on multi target frameworks I use BeforeTargets="DispatchToInnerBuilds"

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFrameworks>netstandard2.0;net461</TargetFrameworks>
  </PropertyGroup>
  <Target Name="GenerateVersionInfo" BeforeTargets="DispatchToInnerBuilds">
    <Exec Command="your custom command" />
  </Target>
</Project>

So my custom command is only exeuted once before every build. If you use InitialTargets, the command is executed more often than only once! For example if you save your project!

like image 105
Dominic Jonas Avatar answered Dec 26 '22 08:12

Dominic Jonas