TFS 2010 and multiple projects output
This post is a part of automated deployment story
Some time ago we were forced to switch from TFS 2008 to TFS 2010. I must emphasise here that choosing TFS as source code repository and CI software was not my choice in the first place. We were forced to use it. Anyway, we wanted to move because the new system was in the same physical network as the whole environment so it would make transporting binary packages much easier and safer.
Apart from obvious changes, like replacing MSBuild with workflow, there is one thing that is more subtle but has so tremendous impact that nearly blocked our adoption of 2010.
It turns out that 2010 overrides the bin directory when building projects so that output of all compilations goes to one folder. By doing so it saves a lot of effort of copying copy local binaries here and there. There is a downside however. Having out output directory makes it really hard to build more than one application in the solution. The result is, all the binaries are mixed together and you can’t figure out (by the results alone) which one belongs to which application (and which are shared).
The problem is less dramatic with web applications because they have publish feature out of the box. What publish does is especially it gathers all the files related to the app and puts them into a zip file. It also gathers all the directly and indirectly referenced binaries, which is cool.
What about console applications then? In the obj directory you can find only the result of compiling the application. There are libraries it depends on. How can we find them?
We can use the very same file that you use to publish web applications. It is called
Microsoft.WebApplication.targets and it is located in
MSBuild folder in
Program Files. All you need to do is strip it from all stuff that does not apply to console apps. Here’s what remains of it:
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <UsingTask TaskName="Microsoft.WebApplication.Build.Tasks.CopyFilesToFolders" AssemblyFile="Microsoft.WebApplication.Build.Tasks.dll" /> <PropertyGroup> <BuildDependsOn> $(BuildDependsOn); PackageBinaries; </BuildDependsOn> </PropertyGroup> <Target Name="PackageBinaries" DependsOnTargets="ResolveReferences"> <!-- Log --> <Message Text="Generating binary package for $(MSBuildProjectName)" /> <!-- copy any referenced assemblies --> <Copy SourceFiles="@(ReferenceCopyLocalPaths)" DestinationFiles="@(ReferenceCopyLocalPaths->'$(IntermediateOutputPath)\%(DestinationSubDirectory)%(Filename)%(Extension)')" SkipUnchangedFiles="true" Retries="$(CopyRetryCount)" RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)"/> <!-- Copy content files --> <Copy SourceFiles="@(Content)" Condition="'%(Content.Link)' == ''" DestinationFolder="$(IntermediateOutputPath)\%(Content.RelativeDir)" SkipUnchangedFiles="true" Retries="$(CopyRetryCount)" RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)" /> </Target> </Project>
It automatically hooks to a compile process, calculates the list of transitive dependencies of your project and fetches their binary forms from the common output location.
Now all you need to do is include the file in your
.csproj files just under this line
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
Then, after the build is finished, the obj folders of your projects are populated with all necessary files.