MSBuild - Project-specific targets for solution does not work
Asked Answered
V

2

28

I have a solution that has multiple projects in it, including a web application. I want MSBuild to execute "WebPublish" target against the web application project and "default target" for all other projects in the solution.

This MSDN article says that I can do it specifying the command line

msbuild SlnFolders.sln /t:NotInSlnfolder:Rebuild;NewFolder\InSolutionFolder:Clean

But I never could make it work - MSBuild return an error, something like "NotInSlnFolder:Rebuild" target does not exist. It does not matter what target to specify, Build, Rebuild or Clean - it does not work in any case.

How can I achieve my goal of specifying project-specific targets for a solution?

The MSDN documentation does not work. Or have I missed something?

Vibraharp answered 20/8, 2012 at 10:34 Comment(1)
After finding answer to my own question, I found the reason why even standard targets didn't work for me. Because my project used DOTS in namesVibraharp
V
41

NOTE: This workaround is not officially supported by Microsoft, so there is no guarantee that it will work forever.


Short Answer

In folder with the SLN file, create the file before.{YourSolution}.sln.targets, with the following content: (Replace what in curly brackets to whatever you need.)

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="{MyCompany_MyProduct_WebApp:WebPublish}">
    <MSBuild
        Condition="'%(ProjectReference.Identity)' == '{$(SolutionDir)MyCompany.MyProduct.WebApp\MyCompany.MyProduct.WebApp.csproj}'"
        Projects="@(ProjectReference)"
        Targets="{WebPublish}"
        BuildInParallel="True"
        ToolsVersion="4.0"
        Properties="BuildingSolutionFile=true; CurrentSolutionConfigurationContents=$(CurrentSolutionConfigurationContents); SolutionDir=$(SolutionDir); SolutionExt=$(SolutionExt); SolutionFileName=$(SolutionFileName); SolutionName=$(SolutionName); SolutionPath=$(SolutionPath)"
        SkipNonexistentProjects="%(ProjectReference.SkipNonexistentProjects)" />
  </Target>
</Project>

After that you can execute the command line:

msbuild {YourSolution}.sln /t:{MyCompany_MyProduct_WebApp:WebPublish}

Long Answer

If you add environment variable MSBUILDEMITSOLUTION, setting its value to 1, MSBuild will not delete temporary files generated for the solution and projects.

This will allow you to find {YourSolution}.sln.metaproj and {YourSolution}.sln.metaproj.tmp files generated in the solution folder, which are just standard MSBuild project files.

For MSBuild 3.5, the generated file is {YourSolution}.sln.cache and is retained regardless of environment variables. Analyzing those files, you will understand low-level details of the process and to see the customization opportunities available.

After executing MSBuild with some project-specific target in the .Metaproj file you will find out that the list of project-specific targets is hardcoded and only standard targets are supported (Build, Rebuild, Clean, Compile, Publish; note: Publish and WebPublish are not the same). MSBuild 3.5 only generates Clean, Rebuild and Publish targets as well as a target with just the project's name that means "Build".

You also can see that NotInSlnfolder:Rebuild is just a name of an autogenerated target. In reality MSBuild does not parse it and does not care about project names and location. Also note that the autogenerated target names specify the project name with solution folders hierarchy if it's in one, e.g. SolFolder\SolSubfolder\ProjectName:Publish.

One more critically important thing you will find: The MSBuild Target Name does not support dots. All dots in project names are replaced with underscores. For example, for a project named MyCompany.MyProduct.Components you will have to specify in the command line:

/t:MyCompany_MyProduct_Components:Rebuild

That's why even standard project-specific target Build didn't work - my project name contained dots.

Analyzing file {YourSolution}.sln.metaproj.tmp, you will find out that at runtime it tries to import targets from file named before.{YourSolution}.sln.targets and after.{YourSolution}.sln.targets, if those files exist. This has a key to the workaround for this MSBuild limitation/bug.

Vibraharp answered 22/8, 2012 at 21:51 Comment(4)
I used the MSBUILSEMITSOLUTION variable to get to the meta project file. This helped me to find the names of my targets. <solution folder>\<project name>, then replace all DOTS with underscores. Once I had that info, I could build individual targets from the solution.Campfire
is there any other documentation on the after.sln.targets and before.sln.targets that anyone has come across?Toomin
you have some specific question? To get more low level info I just equipped with disassembling tools (.NET Reflector, ILSpy) and started reading internals of MSbuild binaries and standard msbuild *.targetsVibraharp
no specific question i think. I am wanting to provide some specific targets to my solution to do various things (copy output to a folder outside of the solution folder), but I like to get an understanding of how things are supposed to work and the docs for msbuild are a bit obtuse.Toomin
U
0

You can open your solution file in text editor and check whether following line is exist or not if not then you can add

<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> inside the <Project> tag.

Hope this help you.

Undoubted answered 15/7, 2016 at 12:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.