tags:

views:

3588

answers:

11

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)

A: 

When you say, "ready to deploy", do I take it that your current deployment scripts copy binaries from each project output folder? If so, then why not simply change them to copy from the single folder that TFS copies binaries into? It's actually more convenient for deployment, since there's only one folder.

John Saunders
By "ready to deploy" I mean "ready to zip up into files to hand the server guys". We don't have any scripts that automatically deploy to production currently.
Schnapple
I'm surprised you don't find this more convenient when there's only a single output folder. Just zip the whole thing up.
John Saunders
Or write a script that sorts out the right files for each of your projects, and zips those up separately.
thijs
Maybe because copy knownFolder\*.* is a one-time setup, and forever thereafter the normal configuration of a project done from within Visual Studio (adding files, references, etc.) continues to work with your automated deployment?
sliderhouserules
@sliderhouserules: Sorry, I don't know what you are replying to.
John Saunders
To your "then why not" query. If you have 10 projects in a solution that you want to distribute to different locations during deployment, how do you do that? Your reply to the OP is "why would you even want to do that, you can just copy out of a common folder". But to do it that way you have to maintain a separate list that contains all the required files for any given project. Visual Studio *already does this*. Team Build is just short-circuiting it on us. The hack solution is to create separate solutions and use what gumsy said, but that's lame. Team Build should fix this.
sliderhouserules
Ok, I guess I would have had them in separate solutions if they were going to be deployed separately. In the previous project when I used TFS, we had one bunch of assemblies to be deployed or GAC'd together, then several web application projects, each being built into its own folder structure.
John Saunders
A: 

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...

epaulsen
A: 

Without commenting on the reason why you'd want to do this, I'll just say that it's highly unusual :-)

Now, when Team Build compiles code it sets the location for all compilation output to a single folder ($BinariesRoot) before it then does the copy to the final drop location.

What this means is that when the compile occurs you won't get a xxx\bin\release folder created for each project. All compilation output is placed directly in that binariesroot folder. This means you can't easily split out your output on a project by project basis.

If you're building a solution you're probably going to find changing this a lot harder than if you build project by project, but if you want to try something then as a starting point you might want to consider overriding the team build default behaviour.

If you want to do this then you'll either need to change the Microsoft.TeamFoundation.Build.targets files (which I would strongly advise against) or override the CoreCompileSolution target in your Team Build proj file. The easiest way would be to copying the CoreCompileSolution target from the TFS build.targets file and changing the values where the $(teambuildoutdir) and $(teambuildpublishdir) properties are passed MSBuild.

That said, I haven't personally tried it so I don't know what other wrinkles you might come up against.

Hope that helps!

Richard Banks
A: 

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 for file copying capabilities.

crowleym
A: 

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

Erik A. Brandstadmoen
+4  A: 

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

A: 

+1 to gumsy's answer

The other option is to add a target that before build depends on. Doable, but his solution is even more elegant.

(I wanted to post this as a comment, but I don't have the rep points)

Vaccano
+6  A: 

I just blogged another method here:

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\. 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\ 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.

Mike Hadlow
I love this answer! It works very simply, and with comparable results to published websites.
David Keaveny
This approach works well with a TFS2005 build server where the CustomizableOutDir property won't work.
Daniel Ballinger
+4  A: 

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.

William D. Bartholomew
+1  A: 

If you're running 2008 check out this link

Jeff
A: 

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.