Sunday, June 27, 2010

TFS Builds

Some interesting things to note when doing TFS Builds. (I've written this article a few months ago before Visual Studio 2010, just to bear in mind)

Being exposed to the .NET world for too long, I actually became acquainted with Team Foundation Server for Version Control, Continuous Integration Builds and Release Builds. I'm not going to comment on the system at the moment. I just want to write something that is for me for future reference and maybe that can help someone else.

I've been struggling a bit on configuring the TFS Build server to not output all the project files to a single Binary folder but to keep it in its projects' configured output folders, i.e. \bin\Release, etc.

Also, I spent some time trying to get the solution's directory that's being built.

To get TFS's Build not to override the output directory, add the following in your TFSBuild.proj in the first PropertyGroup section:

Here are a few things you can do when writing your own custom MSBuild project file:
    <SolutionDir condition=" '$(IsDesktopBuild)' == 'false' ">$(MSBuildProjectDirectory)\..\</SolutionDir>
    <MSBuildCommunityTasksPath>$(SolutionDir)\Third Party Extensions\MSBuildCommunityTasks\</MSBuildCommunityTasksPath>

The SolutionDir is only set if this is a TFS Build (hence the '$(IsDesktopBuild)' == 'false' part). TFS doesn't set this variable. TFS does have a MSBuildProjectDirectory. This has worked for me so far.
If you need to reference some custom task file like MSBuildCommunityTasks, you can then make use of the $(SolutionDir) variable, as shown above.

Now that the projects are being built in their respective folders, you might want certain output to be generated so that you can do a release. You might for instance want all the installers to be copied to the Results path so that you can install it on the relevant test or distributed environments. So in each project that you want output from, insert this as AfterBuild Tasks:

<Target Name="AfterBuild" Condition=" '$(IsDesktopBuild)' == 'false' ">
  <Message Text="Copying output files from $(OutDir) to $(TeamBuildOutDir)" />
      <FilesToCopy Include="$(OutDir)\*.msi" />
    <Copy SourceFiles="@(FilesToCopy)" DestinationFiles="@(FilesToCopy ->'$(TeamBuildOutDir)\$(AssemblyName)\%(RecursiveDir)%(Filename)%(Extension)')" />

Another nice feature that I discovered while browsing through the MSBuildCommunityTasks collection is the ability to set a version number automatically. There are various provided options but the one with the least configuration and storage of info is this one that I'm using:

<Target Name="UpdateBuildVersion" Condition=" '$(IsDesktopBuild)' == 'false' ">
  <Version BuildType="Automatic"
    <Output TaskParameter="Major" PropertyName="Major" />
    <Output TaskParameter="Minor" PropertyName="Minor" />
    <Output TaskParameter="Build" PropertyName="Build" />
    <Output TaskParameter="Revision" PropertyName="Revision" />
  <Attrib Files="Properties\AssemblyInfo.cs" Normal="true" />
  <FileUpdate Files="Properties\AssemblyInfo.cs"
   ReplacementText="$(Major).$(Minor).$(Build).$(Revision)" />

Calling this from your built project's BeforeBuild task, will let TFS Build (only) to overwrite your Assembly Info and set the new version number and then do a build. Now you don't have to clutter your check-ins and you don't have to remember to set your version numbers anymore. By using a value from $(BuildStartDate) of the format YYYY/MM/DD, you can have a number increment on every build which equates to the amount of days since the $(BuildStartDate). You'll also note the 'Attrib' task. This is to set the file's attribute to normal because TFS checkouts usually sets the file's read-only flag to true and then all your apps will throw an exception because they "can't" write to a read-only file. Same with TFS Build.

No comments: