Wednesday, October 14, 2009

By default Team Build spews all compilation output into a single directory. Although web projects are output in deployable form into a directory called _PublishedWebsites\<name of project> the same is not true for exe or dll projects. A while back I wrote a post showing how you could grab the output for exe projects and place them in a similar _PublishedApplications directory, and this worked fine for simple cases.

However that solution relied on getting the correct files from the single flat compile output directory. Now, we have exe projects that output various helper files, such as XSLT documents, in subdirectories. So we may end up with paths like this: MyProject\bin\Release\Transforms\ImportantTransform.xslt. But because these subdirectories get flattened by the default TFS build we loose our output directory structure.

This begs the question: why do we need to output everything in this big flat directory anyway? Why can’t we just have our CI build do the same as our Visual Studio build and simply output the build products into the <project name>\bin\Release folders? Then we can simply copy the compilation output to our build output directory.

There’s an easy way to do this with TFS introduced with 2008; simply change the property CustomizableOutDir to true and the TFS build will behave just like a Visual Studio build. Put the following in your TFSBuild.proj file somewhere near the top under the Project element:

Aaron Hallberg has a great blog post explaining exactly how this all works. Aaron’s blog is essential reading if you’re doing pretty much anything with TFS. You can still get the directory where TFS would have put the output from the new TeamBuildOutDir property.

Now the TFS build outputs into bin/Release in exactly the same way as a standard Visual Studio build and we can just grab the outputs for the projects we need and copy them to our build output directory. I do this by including a CI.exe.targets file near the end of the .csproj file of any project that I want to output:

<Import Project="..\..\Build\CI.build.targets\CI.exe.targets" />

My CI.exe.targets looks like this:

<ProjectDefaultTargets="Build"xmlns="http://schemas.microsoft.com/developer/msbuild/2003"><PropertyGroup><PublishedApplicationOutputDirCondition=" '$(TeamBuildOutDir)'!='' ">$(TeamBuildOutDir)_PublishedApplications\$(MSBuildProjectName)</PublishedApplicationOutputDir><PublishedApplicationOutputDirCondition=" '$(TeamBuildOutDir)'=='' ">$(MSBuildProjectDirectory)</PublishedApplicationOutputDir></PropertyGroup><PropertyGroup><PrepareForRunDependsOn>
$(PrepareForRunDependsOn);
_CopyPublishedApplication;
</PrepareForRunDependsOn></PropertyGroup><!-- ============================================================ _CopyPublishedApplication This target will copy the build outputs This Task is only necessary when $(TeamBuildOutDir) is not empty such as is the case with Team Build. ============================================================ --><TargetName="_CopyPublishedApplication"Condition=" '$(TeamBuildOutDir)'!='' "><!-- Log tasks --><MessageText="Copying Published Application Project Files for $(MSBuildProjectName)"/><MessageText="PublishedApplicationOutputDir is: $(PublishedApplicationOutputDir)"/><!-- Create the _PublishedWebsites\app\bin folder --><MakeDirDirectories="$(PublishedApplicationOutputDir)"/><!-- Copy compile output to publish directory --><ItemGroup><ApplicationBinContentsInclude="$(OutputPath)\**\*.*"/></ItemGroup><CopySourceFiles="@(ApplicationBinContents)"DestinationFiles="$(PublishedApplicationOutputDir)\%(RecursiveDir)%(Filename)%(Extension)"></Copy></Target></Project>

First of all we define a new property PublishedApplicationOutputDir to hold the directory that we want our exe’s build output to be published to. If the TeamBuildOutDir is empty it means that this build has been triggered by Visual Studio, so we don’t really want to do anything. In the target _CopyPublishedApplication we create a list of everything in the build output directory called ApplicationBinContents, and copy it all to to PublishedApplicationOutputDir. Simple when you know how.

3 comments:

I am working on exactly the same issue at the momnet, but try for another level on top of it, not just publish application, but putting them into indiviudal folders for each server they will be deployed too, in their correct folder structure on the disk, loving the simplicty of the Microsoft.TeamFoundation.Build.targets file and lack of info on the build system :)

Code Rant

Notepad, thoughts out loud, learning in public, misunderstandings, mistakes. undiluted opinions. I'm Mike Hadlow, an itinerant developer. I live (and try to work in) Brighton on the south coast of England. Please don't mistake me for an expert in anything. I love technology and programming, but make no claims to be any good at it. Much of what you read here may be poorly thought out, wrong, or just plain dangerous.