0

Rather than building to the default folder structure, which is like

Solution.sln
Project1
    bin    <- project 1 output
    obj    <- project 1 intermediate output
Project2
    bin    <- project 2 output
    obj    <- project 2 intermediate output

I instead want to build it like

Solution.sln
bin    <- project 1 AND 2 output
obj
    Project1   <- project 1 intermediate output
    Project2   <- project 2 intermediate output

I can do

msbuild "/p:OutputPath=../bin" "/p:IntermediateOutputPath=../obj/" Test123.sln

However, using "/p:IntermediateOutputPath=../obj/$(ProjectName)/" does not work. Instead of creating a folder for each project, it creates one folder literally called $(ProjectName) (I've read that most, but not all of these macros are actually a Visual Studio thing, rather than MSBuild magic).

How can I use a project-specific value (such as ProjectName) in a property value (such as IntermediateOutputPath) when building?

(Some background information:

Having one bin folder on the solution level saves unnecessary copying of output files, which quickly amass over 100 MB in large solutions. Furthermore, it keeps the source folders clean so they can be read-only.

I still want separate obj folders though, because who knows what goes in there - might be the same file names for different projects.)

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
dialer
  • 4,348
  • 6
  • 33
  • 56
  • I doubt you can do this directly from the command line without modifying the project files. However your actual question isn't clear: do you only want that single bin directory for command line builds? Or always? wrt *I've read that most, but not all of these macros are actually a Visual Studio thing rather than msbuild magic*: no idea where you got that, but that is not at all the case. Your problem here seems to be `$(ProjectName)` is passed as a literal string and doesn't get interpreted by msbuild – stijn Apr 21 '18 at 07:16

1 Answers1

2

You can override the CustomAfterMicrosoftCommonTargets property of the Microsoft.Common.targets file. It's allowing injecting custom targets into projects and performs some actions.


The entry point of the build process is Make.targets:

<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"; DefaultTargets="EntryMSBuild">

    <ItemGroup>
        <Project Include="**\*.csproj"/>
    </ItemGroup>

    <Target Name="EntryMSBuild">

        <Message Text="-----Entry-----" Importance="high"/>
        <Message Text="    Rebuild    " Importance="high"/>
        <Message Text="-----Entry-----" Importance="high"/>

        <MSBuild Projects="@(Project)" Targets="rebuild" Properties="CustomBeforeMicrosoftCommonTargets=$(MSBuildThisFileDirectory)Setting.targets"/>
    </Target>

</Project>

In Setting.targets defined IntermediateOutputPath and OutputPath for each projects:

<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

    <PropertyGroup>
        <IntermediateOutputPath>..\Global\obj\$(MSBuildProjectName)</IntermediateOutputPath>
    </PropertyGroup>

    <PropertyGroup>
        <OutputPath>..\Global\bin\</OutputPath>
    </PropertyGroup>

</Project>

As a result, you get the desired structure:

Solution.sln
bin    <- project 1 AND 2 output
obj
    Project1   <- project 1 intermediate output
    Project2   <- project 2 intermediate output

Tested on solution with two projects under .net46 and .netstandard2.0. MSBuild 15.6.82.30579


You don’t need store custom targets under C:\Program Files[(x86)]\microsoft visual studio\2017\xxx\msbuild\15.0 or any predefined path. You override CustomBeforeMicrosoftCommonTargets property that already defined in Microsoft.Common.targets and injected in the project by default.

From Microsoft.Common.targets comment:

This file defines the steps in the standard build process for .NET projects. It contains all the steps that are common among the different .NET languages, such as Visual Basic, and Visual C#.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
UserName
  • 895
  • 7
  • 17
  • Looks promising, but if I understand this (and [that](https://stackoverflow.com/questions/5246015/copy-built-assemblies-including-pdb-config-and-xml-comment-files-to-folder-p)) correctly, I need to put the custom targets under `c:\program files[ (x86)]\microsoft visual studio\2017\xxx\msbuild\15.0\`, which means that everyone who wants to build the solution needs write access in that folder, and also that it affects all projects built on the machine. Is that correct? Is there a way to do this without changing anything in the msbuild installation folder? – dialer Apr 22 '18 at 17:14
  • You can find many useful information about this in [Supplement to Inside the Microsoft Build Engine](https://books.google.ru/books?id=l7hCAwAAQBAJ&pg=PT56&lpg=PT56&dq=From+the+sample+in+Chapter+8,+we+simply+placed+the+file&source=bl&ots=jB1lB6MIQn&sig=IGMdPcM-Cvr10CmLtkTGKTjbGoQ&hl=en&sa=X&ved=0ahUKEwjz9-nFwM7aAhUM3SwKHQPBCPcQ6AEIKTAA#v=onepage&q=From%20the%20sample%20in%20Chapter%208%2C%20we%20simply%20placed%20the%20file&f=false) book on page 25. – UserName Apr 22 '18 at 18:27
  • Got it to work. My mistake was that the path you set the property to apparently must be absolute, like `msbuild "/p:CustomBeforeMicrosoftCommonTargets=d:\dev\...\Setting.targets" Solution123.sln` – dialer Apr 22 '18 at 19:08