10
votes

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?

5

5 Answers

10
votes

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

2
votes

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 ;)

1
votes

Maybe not the best answer but a reasonable hack.

msbuild ProjectA.csproj
msbuild ProjectB.csproj /t:Foo
1
votes

When msbuild building solution - msbuild emits only limited set of targets into it's .metaproj file, and afaik - you can't build custom target through building sln file, you have to use original project1.csproj or custom build script.

0
votes

Just for reference:

Use ContinueOnError when using MSBuildTask or -p:ContinueOnError=ErrorAndContinue when using (dotnet) msbuild

It may be in limited scenarios helpful: For example you have a list of .csproj files and want attach metadata only to specific project file items then you could write something like this:

<Target Name="UniqueTargetName" Condition="'$(PackAsExecutable)' == 'Package' Or '$(PackAsExecutable)' == 'Publish'" Outputs="@(_Hello)">
  <ItemGroup>
    <_Hello Include="$(MSBuildProjectFullPath)" />
  </ItemGroup>
</Target>

<Target Name="BuildEachTargetFramework" DependsOnTargets="_GetTargetFrameworksOutput;AssignProjectConfiguration;_SplitProjectReferencesByFileExistence"
        Condition="$(ExecutableProjectFullPath) != ''">

  <Message Text="[$(MSBuildThisFilename)] Target BuildEachTargetFramework %(_MSBuildProjectReferenceExistent.Identity)" Importance="high" />

  <MSBuild
    Projects="%(ProjectReferenceWithConfiguration.Identity)"
    Targets="UniqueTargetName"
    ContinueOnError="true">
    <Output TaskParameter="TargetOutputs" ItemName="_Hallo2" />
  </MSBuild>

  <Message Text="[$(MSBuildThisFilename)] ########### HELLO %(_Hallo2.Identity)" Importance="high" />
  
</Target>