Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MSBuild: Ignore targets that don't exist

Tags:

msbuild

Solution1.sln contains two projects:

  • ProjectA.csproj
  • ProjectB.csproj

ProjectB has a custom target called "Foo". I want to run:

msbuild Solution1.sln /t:Foo

This will fail because ProjectA doesn't define the "Foo" target.

Is there a way to make the solution ignore the missing target? (E.g., do nothing if the target doesn't exist for a specific project) without modifying the SLN or project files?

like image 381
Paul Stovell Avatar asked Apr 07 '13 23:04

Paul Stovell


2 Answers

There is a two-part solution if you don't want to edit the solution or project files and you're happy for it to work from MSBuild command-line but not from Visual Studio.

Firstly, the error you get when you run:

MSBuild Solution1.sln /t:Foo

Is not that ProjectA does not contain a Foo target but that the solution itself does not contain a Foo target. As @Jaykul suggests, setting the MSBuildEmitSolution environment variable will reveal the default targets contained within the solution metaproj.

Using the metaproj as inspiration you can introduce a new file "before.Solution1.sln.targets" next to the solution file (the file name pattern is important) with contents like this:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="Foo">
    <MSBuild Projects="@(ProjectReference)" Targets="Foo" BuildInParallel="True" Properties="CurrentSolutionConfigurationContents=$(CurrentSolutionConfigurationContents); SolutionDir=$(SolutionDir); SolutionExt=$(SolutionExt); SolutionFileName=$(SolutionFileName); SolutionName=$(SolutionName); SolutionPath=$(SolutionPath)" SkipNonexistentProjects="%(ProjectReference.SkipNonexistentProjects)" />
  </Target>
</Project>

The MSBuild element is mostly just copied from the solution metaproj's Publish target. Adjust the target name and any other details to suit your scenario.

With this file in place, you'll now get the error that ProjectA does not contain the Foo target. ProjectB may or may not build anyway depending on inter-project dependencies.

So, secondly, to solve this problem we need to give every project an empty Foo target which is then overridden in projects that actually already contain one.

We do this by introducing another file, eg "EmptyFoo.targets" (name not important) that looks like this:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="Foo" />
</Project>

And then we get every project to automatically import this targets file either by running MSBuild with an extra property, eg:

MSBuild Solution1.sln /t:Foo /p:CustomBeforeMicrosoftCommonTargets=c:\full_path_to\EmptyFoo.targets

Or include the CustomerBeforeMicrosoftCommonTargets property in the Properties attribute on the MSBuild element in the first targets file where you could optionally specify the full path relative to the $(SolutionDir) property.

However, if you're willing to run Foo in conjunction with any of the default solution targets (ie Build, Rebuild, Clean, or Publish) you could take some inspiration for how the Web Publishing Pipeline in MSBuild uses the DeployOnBuild property to call the Publish target on Web projects in a solution containing other project types that don't support publishing.


More info on the before.Solution1.sln.targets file here: http://sedodream.com/2010/10/22/MSBuildExtendingTheSolutionBuild.aspx

like image 64
Jason Stangroome Avatar answered Oct 02 '22 16:10

Jason Stangroome


You can target those by project name, like /t:project:target (might need quotes, I can't remember).

You can find all the generated targets by setting the environment variable MSBuildEmitSolution = 1 ... which causes msbuild to save to disk the temp .metaproj file which it generates for your solution. That file has all those targets defined in it, just open it up and take a look ;)

like image 45
Jaykul Avatar answered Oct 02 '22 15:10

Jaykul