How to call a custom target after building each individual solution (sln) in Team Build?
Issue: -
I would like to invoke a specific target just after each solution is compiled. Unfortunately, team build performs a build including all solution in an atomic MSBuild call. I don’t want to make changes in my sln or csproj files but I am OK with modifying the tfsbuild.proj. What can I do to get the desired behavior?
Solution:-
This is possible in Team Build but is slightly complicated. You need to override the default CoreCompile target with your own version.
To give you the idea of what needs to be done, please refer this simplified script -
In this proj file, all the three projects (classlibrary1, classlibrary2, classlibrary3) are build for each configuration (Debug|AnyCPU, Release|AnyCPU) and the target CommonPostCompileStep is invoked after project is build for a particular configuration.
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="https://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<!-- List of projects to build -->
<SolutionToBuild Include=".\ClassLibrary1\ClassLibrary1.sln" />
<SolutionToBuild Include=".\ClassLibrary2\ClassLibrary2.sln" />
<SolutionToBuild Include=".\ClassLibrary2\ClassLibrary2.sln" />
</ItemGroup>
<ItemGroup>
<!-- Configuration to build -->
<ConfigurationToBuild Include="Release|Any CPU">
<FlavorToBuild>Release</FlavorToBuild>
<PlatformToBuild>Any CPU</PlatformToBuild>
</ConfigurationToBuild>
<ConfigurationToBuild Include="Debug|Any CPU">
<FlavorToBuild>Debug</FlavorToBuild>
<PlatformToBuild>Any CPU</PlatformToBuild>
</ConfigurationToBuild>
</ItemGroup>
<Target Name="CoreCompile">
<!-- Call the compile for each configuration -->
<MSBuild
Projects="$(MSBuildProjectFile)"
Targets="RunCoreCompileWithConfiguration"
Properties="Platform=%(ConfigurationToBuild.PlatformToBuild);Flavor=%(ConfigurationToBuild.FlavorToBuild);" />
</Target>
<Target Name="RunCoreCompileWithConfiguration">
<!-- Call the compile for each project (per configuration), with your custom target -->
<MSBuild
Projects="$(MSBuildProjectFile)"
Targets="RunCoreCompileForProject; CommonPostCompileStep"
RunEachTargetSeparately="true"
Properties="Platform=$(Platform);Flavor=$(Flavor);
ProjectToBuild=%(SolutionToBuild.Identity)" />
</Target>
<Target Name="RunCoreCompileForProject">
<!-- Actual compile for a particular {configuration, project} pair -->
<MSBuild
Condition="'$(ProjectToBuild)'!='' "
Projects="$(ProjectToBuild)"
Properties="Configuration=$(Flavor);Platform=$(Platform);
SkipInvalidConfigurations=true;"
Targets="ReBuild" />
</Target>
<Target Name="CommonPostCompileStep">
<!-- Custom target to execute after building each project/solution-->
<Message Text="Common postcompile target to execute after building solution"/>
</Target>
</Project>
Note the following issues when making changes in your tfsbuild.proj file –
- In msbuild task, Projects property – use $(SolutionRoot)\TeamBuildTypes\$(BuildType)\tfsbuild.proj instead of using "$(MSBuildProjectFile). Otherwise the teambuild logger will crash
- As you are spanning msbuild process for each project (per configuration) separately, this will have performance implication.
- If making change in your csproj file to include custom post build step is easier. However if you are building large number of solutions and making changes in individual files is painful then this might be an better approach.
Please feel free to use this example as hint and customize your CoreCompile target.
Comments
- Anonymous
May 10, 2006
Manish Agarwal tells us how to call a custom target after building each individual solution in Team Build.... - Anonymous
May 15, 2006
I have tried to do something like this on the main Microsoft.TeamFoundation.Build.targets file on CoreCompile. I would like to insert a target that is executed for each configuration immediately before that configuration.
If I use $(SolutionRoot)TeamBuildTypes$(BuildType)tfsbuild.proj as the reference for the recursive call to the project file, it can't find my solutions (because it now searches relative to this file rather than relative to $(SolutionRoot)..BuildTypetfsbuild.proj. If I use $(SolutionRoot)..BuildTypetfsbuild.proj, the build crashes on the server claiming that it can't find the working directory.
How do I insert a target into the CoreBuild step that is executed once for each configuration, and not mess up all the other stuff that the Microsoft.TeamFoundation.Build.targets CoreBuild is doing? - Anonymous
May 19, 2006
I think I have answered my own question:
When calling the target for MSBuild to do the CoreCompile, pass the original $(SolutionRoot) with
Properties="SolutionRoot=$(SolutionRoot)" - Anonymous
June 07, 2006
At the end of your article, you mention that the logger will crash under certain circumstances. Can you elaborate further on why this occurs? I am using your pattern to perform code generation prior to compilation and it looks like the logger is crashing during the run. My build is failing and I have a truncated build log. - Anonymous
June 07, 2006
Your override of the CoreCompile target does fewer tasks than the original version in Microsoft.TeamFoundation.Build.targets. Do you believe that this will materially change the behavior of the overridden build vs. the behavior of the original? - Anonymous
August 10, 2006
For those who try this, note that you'll need to copy the TeamBuildMessage task invocations as well, otherwise the logger will crash (Steve points this out in his blog post http://sstjean.blogspot.com/2006/06/tfs-team-build-problems-with-calling.html). The v1 logger depends on those messages to determine what's being built.
Regardless, it's not a good idea to override any of the Core* targets, especially CoreCompile, as they will definitely be changing in future releases.
Buck - Anonymous
August 10, 2006
There have been several times when folks have needed to run a custom target prior to building each solution... - Anonymous
June 07, 2009
PingBack from http://weakbladder.info/story.php?id=5108