What's the best way to get TFS to output each project to its own directory?

TfsMsbuild

Tfs Problem Overview


I'm putting a large codebase into Team Foundation Server. I would like the build process to create a "ready to deploy" build of our projects.

The normal way we've been doing this is to have each project's output be in its own folder. So, for example, we wind up with something like

C:\project1\
            assembly1.dll
            assembly2.dll
            project1.exe
            project1.exe.config
C:\project2\
            assembly2.dll
            assembly3.dll
            project2.exe
            project2.exe.config
C:\project3\
            assembly1.dll
            assembly3.dll
            project3.exe
            project3.exe.config

Which is the way we like it.

TFS, though, seems to want to stick everything in the same directory.

C:\output\
          assembly1.dll
          assembly2.dll
          assembly3.dll
          project1.exe
          project1.exe.config
          project2.exe
          project2.exe.config
          project3.exe
          project3.exe.config

which, although it saves some amount of disk space (the assemblies are only there one time each) is not how we want it.

What's the best way to specify where TFS/MSBuild should put the output files? Do I need to edit sln/csproj files individually to achieve this or can I do it in the TFSBuild.proj file? (i.e., in a MSBuild-specific file)

Tfs Solutions


Solution 1 - Tfs

I just blogged another method here:

[http://mikehadlow.blogspot.com/2009/06/tfs-build-publishedwebsites-for-exe-and.html][1] [1]: http://mikehadlow.blogspot.com/2009/06/tfs-build-publishedwebsites-for-exe-and.html

but if you can't be bothered to follow the link, here it is in full:

It’s generally good practice to collect all the code under your team’s control in a single uber-solution as described in this Patterns and Practices PDF, Team Development with TFS Guide. If you then configure the TFS build server to build this solution, it’s default behaviour is to place the build output into a single folder, ‘Release’.

Any web application projects in your solution will also be output to a folder called _PublishedWebsites<name of project>. This is very nice because it means that you can simply robocopy deploy the web application.

Unfortunately there’s no similar default behaviour for other project types such as WinForms, console or library. It would be very nice if we could have a _PublishedApplications<name of project> sub folder with the output of any selected project(s). Fortunately it’s not that hard to do.

The way _PublishedWebsites works is pretty simple. If you look at the project file of your web application you’ll notice an import near the bottom:

<Import Project="$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v9.0\WebApplications\Microsoft.WebApplication.targets" />

On my machine the MSBuildExtensionsPath property evaluates to C:\Program Files\MSBuild, if we open the Microsoft.WebApplication.targets file we can see that it’s a pretty simple MSBuild file that recognises when the build is not a desktop build, i.e. it’s a TFS build, and copies the output to:

$(OutDir)_PublishedWebsites\$(MSBuildProjectName)

I simply copied the Micrsoft.WebApplication.targets file, put it under source control with a relative path from my project files and changed _PublishedWebsites to _PublishedApplications and renamed the file CI.exe.targets. For each project that I want to output to _PublishedApplications, I simply added this import at the bottom of the project file:

<Import Project="<your relative path>\CI.exe.targets" />

You can edit CI.exe.targets (or whatever you want to call it) to do your bidding. In my case, the only change so far is to add a couple of lines to copy the App.config file:

<Copy SourceFiles="$(OutDir)$(TargetFileName).config" DestinationFolder="$(WebProjectOutputDir)\bin" SkipUnchangedFiles="true" />

There’s a lot of stuff in Microsoft.WebApplication.targets that’s only relevant to web applications and can be stripped out for other project types, but I’ll leave that as an exercise for the reader.

Solution 2 - Tfs

TFS 2012+
I like this solution...

Edit your build definition. Under Process section, set MSBuild arguments to

/p:GenerateProjectSpecificOutputFolder=true

Like this:

enter image description here

Solution 3 - Tfs

By default each project file (*.csproj, *.vbproj, etc.) specifies a default output directory (which is usually bin\Debug, bin\Release, etc.). Team Build actually overrides this so that you're not at the whim of what properties the developer sets in the project file but also so that Team Build can make assumptions about where the outputs are located.

The easiest way to override this behaviour is to set CustomizableOutDir to true in the SolutionToBuild item group as shown here:

<ItemGroup>
  <SolutionToBuild Include="$(BuildProjectFolderPath)\path\MySolution.sln" />
    <Properties>CustomizableOutDir=true</Properties>
  </SolutionToBuild>
</ItemGroup>

This will make the drop folder structure roughly match what you would get locally if you built the solution.

This method is definitely preferable to overriding the Core* targets which can cause upgrade issues.

Solution 4 - Tfs

For each SolutionToBuild node, set the property OutDir to $(OutDir)\SubFolder
For example:

  <ItemGroup>
   <SolutionToBuild Include="Project1.sln" >
    <Properties>OutDir=$(OutDir)\Project1\</Properties>      
   </SolutionToBuild>
   <SolutionToBuild Include="Project2.sln" >
    <Properties>OutDir=$(OutDir)\Project2\</Properties>      
   </SolutionToBuild>
   <SolutionToBuild Include="Project3.sln" >
    <Properties>OutDir=$(OutDir)\Project3\</Properties>      
   </SolutionToBuild>
  <ItemGroup>

(This works in TF2008, but not TF2005.)

Solution 5 - Tfs

I'm a bit late to the party answering this question but there is a very simple way to implement Mike Hadlows answer. Someone has written a nuget package that does exactly what Mike talks about. You can find it here: http://www.nuget.org/packages/PublishedApplications

Solution 6 - Tfs

Update for TFS 2010 (and upcoming TFS 2012). Jason Stangroome has written a nice blog post outlining how to do this.

https://blog.stangroome.com/2012/02/03/override-the-tfs-team-build-outdir-property/

> # Override the TFS Team Build OutDir property # > > Update: with .NET 4.5 there is an easier way. > > A very common complaint from users of Team Foundation Server’s build system is that it changes the folder structure of the project outputs. By default Visual Studio puts all the files in each project’s respective /bin/ or /bin// folder but Team Build just uses a flat folder structure putting all the files in the drop folder root or, again, a // subfolder in the drop folder, with all project outputs mixed together. > > Additionally because Team Build achieves this by setting the OutDir property via the MSBuild.exe command-line combined with MSBuild’s property precedence this value cannot easily be changed from within MSBuild itself and the popular solution is to edit the Build Process Template *.xaml file to use a different property name. But I prefer not to touch the Workflow unless absolutely necessary. > > Instead, I use both the Solution Before Target and the Inline Task features of MSBuild v4 to override the default implementation of the MSBuild Task used to build the individual projects in the solution. In my alternative implementation, I prevent the OutDir property from being passed through and I pass through a property called PreferredOutDir instead which individual projects can use if desired. > > The first part, substituting the OutDir property for the PreferredOutDir property at the solution level is achieved simply by adding a new file to the directory your solution file resides in. This new file should be named following the pattern “before..sln.targets”, eg for a solution file called “Foo.sln” then new file would be “before.Foo.sln.targets”. The contents of this new file should look like this. Make sure this new file gets checked-in to source control. > > The second part, letting each project control its output folder structure, is simply a matter of adding a line to the project’s *.csproj or *.vbproj file (depending on the language). Locate the first element inside the project file that doesn’t have a Condition attribute specified, and the locate the corresponding closing tag for this element. Immediately above the closing tag add a line something like this: > > <OutDir Condition=" '$(PreferredOutDir)' != '' ">$(PreferredOutDir)$(MSBuildProjectName)\</OutDir> > > In this example the project will output to the Team Build drop folder under a subfolder named the same as the project file (without the .csproj extension). You might choose a different pattern. Also, Web projects usually create their own output folder under a _PublishedWebSites subfolder of the Team Build drop folder, to maintain this behaviour just set the OutDir property to equal the PreferredOutDir property exactly. > > You can verify if your changes have worked on your local machine before checking in simply by running MSBuild from the command-line and specifying the OutDir property just like Team Build does, eg: > > msbuild Foo.sln /p:OutDir=c:\TestDropFolder\

Solution 7 - Tfs

For those curious about how this works with TFS 2010, this post has several answers, of which the linked one worked very well for me.

Solution 8 - Tfs

You could have one buildscript per project, that would do exactly what you want. Just create a new TFSBuild file, add the projects you want to have built to the itemgroup(in the order you want them built), set where you want the output to be. This is done by overriding the - property in your TFSBuild file.

But I also agree with the previous poster - why don't you just run with a single build script, and add a zip-task at the end? Maintaining a buildscript per project does add maintenance overhead...

Solution 9 - Tfs

Sling this in a propertygroup:

<CustomizableOutDir>true</CustomizableOutDir>

It'll override the global 'CustomizableOutDir' property which, by default, is set to False. Setting this in the SolutionToBuild's properties will not work.

Solution 10 - Tfs

You achieve this by overriding the default CoreDropBuild target implementation.

In your TFSBuild.proj file (by default stored under TeamBuildTypes/<Build Type>) add the following target:

    <!-- Override default implementation -->
    <Target 
       Name="CoreDropBuild"
       Condition=" '$(SkipDropBuild)'!='true' and '$(IsDesktopBuild)'!='true' "
       DependsOnTargets="$(CoreDropBuildDependsOn)">
...
    </Target>

Within this target you can manipulate the output as you want it. The default is to just copy everything from $(BinariesRoot)$(BuildType) to $(DropLocation)$(BuildNumber).

I normally use the [Microsoft.Sdc.Tasks project][1] for file copying capabilities. [1]: http://www.codeplex.com/sdctasks

Solution 11 - Tfs

Simple Solution:

Replace all <SolutionToBuild> nodes with <SolutionToPublish>. This will of course only work for publishable projects (e.g. Web projects and applications), not for library projects.

As simple as that :)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTom KiddView Question on Stackoverflow
Solution 1 - TfsMike HadlowView Answer on Stackoverflow
Solution 2 - TfsTheoView Answer on Stackoverflow
Solution 3 - TfsWilliam D. BartholomewView Answer on Stackoverflow
Solution 4 - Tfsuser75810View Answer on Stackoverflow
Solution 5 - TfsCeejeeBView Answer on Stackoverflow
Solution 6 - TfsJeremy WiebeView Answer on Stackoverflow
Solution 7 - TfsCaseView Answer on Stackoverflow
Solution 8 - TfsErling PaulsenView Answer on Stackoverflow
Solution 9 - Tfsuser386316View Answer on Stackoverflow
Solution 10 - TfscrowleymView Answer on Stackoverflow
Solution 11 - TfsErik A. BrandstadmoenView Answer on Stackoverflow