Using a common intermediate and output directory for your solution


By default each project in a VS solution has its output directory set to bin and its intermediate output directory to obj subdirectories of each project directory. If you’re using version control, you will find bin and obj directories scattered all over your tree, and although you can easily clean them all up with git clean –xdf or tfpt treeclean, it’s still a mess – for aesthetic reasons I think source shouldn’t be mixed with intermediates or binaries.

MSBuild allows to change this by specifying a common intermediate output root for projects in your solution. This is controlled by the IntermediateOutputPath MSBuild property. Each project can set its IntermediateOutputPath to:

<IntermediateOutputPath>$(SolutionDir)\obj\$(Configuration)\$(MSBuildProjectName)\</IntermediateOutputPath>

You will then find that there is a single obj directory at the root of your solution, and it has subdirectories for each configuration (Debug/Release) followed by a dedicated folder for each project. You can’t dump all projects in the same folder because this will cause numerous conflicts during parallel build, overwrites, broken incrementality and other unpleasant things.

An advantage of using a common intermediate output directory is you can effectively “clean” your entire solution by deleting a single obj folder.

Likewise, you can set the OutputPath property to redirect the bin folder to a common directory under the solution root:

<OutputPath>$(SolutionDir)\bin\$(Configuration)\$(AssemblyName)\</OutputPath>
<OutDir>$(OutputPath)</OutDir>

Again, you need $(AssemblyName) or $(MSBuildProjectName) there to place bin directories separately for each project. You will find however that MSBuild will copy all dependencies to each of these output folders, causing unnecessary duplication and wasting disk space. You can take a risk and do this instead:

<OutputPath>$(SolutionDir)\bin\$(Configuration)\</OutputPath>
<OutDir>$(OutputPath)</OutDir>
<!-- Don't transitively copy output files, since everything builds to the same folder. -->
<UseCommonOutputDirectory>True</UseCommonOutputDirectory>

This will stuff everything into a single folder, avoiding multiple redundant copies of output binaries. There is a risk associated with it, however: if more than one project are outputting a file with the same name, your build will have a race condition called a "double-write". This means that depending on the order in which the projects are built, the final contents of a file in the output directory might end up either from one or from another project. Even if the contents of the file are identical, you risk running into errors during build because multiple projects could be writing into the same file simultaneously, causing errors in one of them.

Finally, to avoid setting OutputPath and IntermediateOutput path for each project in your solution individually, it really helps to have a shared file like Common.props included in each of the projects. You can declare these once instead of duplicating these properties in each project.

Here’s how to include a common file in each project:

https://github.com/KirillOsenkov/CodeCleanupTools/blob/master/FileTypes/FileTypes.csproj#L4

And here’s a sample Common.props that redirects both bin and obj to a common location:

https://github.com/KirillOsenkov/CodeCleanupTools/blob/master/Common.props#L4-L9


Comments (4)

  1. mafu says:

    Thank you, I wondered about the common redirection since I started C# in 2008. I'll try this when I get home!

  2. Ivan Danilov says:

    Didn't know about UseCommonOutputDirectory – I wrote an utility that rewrites project files during build (and reverts them back afterwards) to include only one CopyLocal=true per dll file per solution (github.com/…/CopyLocalFixer). With it in place, parallel build became stable. I will definitely give a try to this property, thanks!

    By the way, could you please elaborate, what OutDir property is and how it differs from OutputPath?

  3. Ivan says:

    Unfortunately, after setting UseCommonOutputDirectory, build was not copying third-party dependencies to $(OutputPath) not only transitively, but none at all…

    I mean, if you reference something like $(SolutionDir)Libmylib.dll (e.g. with HintPath) – this file won't be copied to output directory 🙁

Skip to main content