ISWIX, LLC View Christopher Painter's profile on LinkedIn profile for Christopher Painter at Stack Overflow, Q&A for professional and enthusiast programmers

April 05, 2007

TFS Build Automation Part 2 - Incremental Builds

Once you have your VS2005 solutions building, it's time to learn how to do an incremental build. Modify your TFSBuild.proj and insert these properties into your PropertyGroup:

<forceget>false</forceget>
<skipinitializeworkspace>true</skipinitializeworkspace>
<skipclean>true</skipclean>

This tells TFS to not reset your build directory and to only rebuild projects that need to be rebuilt. Personally incremental builds `scare` me, but Neil Enns ( MSFT program manager for Visual Studio ) personally assures me

"Incremental builds using MSBuild are 100% reliable for managed projects."

There is always the possibility that an incremental build would success when a full build would fail ( perhaps a deprecated component that doesn't go missing until you initialize the workspace ) so we will perform a redundant full build as a sanity check but discard the bits for deployment.

Now that we have incremental builds working, it sure would be nice to properly version them so Windows Installer costing patterns will work effeciently. Here is where Neil Enns comes to the rescue again. Neil wrote the AssemblyInfoTask community task. This task does a bunch of things, but one of the things it does well is to update the AssemblyInfo.cs files of only the projects that are out of date, or reference an out of date project. This means that during an incremental build, only the projects that get compiled will get versioned. Now isn't that exactly the sort of pattern that MSI costing needs?

To take advantage of this, we download the task and install it. Then we have to add a few lines to each of the projects in our solution to `subscribe` them to the pattern.

<Import Project="$(MSBuildExtensionsPath)\Microsoft\AssemblyInfoTask\Microsoft.VersionNumber.Targets" Condition="'$(BuildLabBuild)'=='true'"/>
<PropertyGroup>
<AssemblyMajorVersion>$(Major)</AssemblyMajorVersion>
<AssemblyMinorVersion>$(Minor)</AssemblyMinorVersion>
<AssemblyBuildNumber>$(Build)</AssemblyBuildNumber>
<AssemblyRevision>$(Revision)</AssemblyRevision>
<AssemblyBuildNumberType>NoIncrement</AssemblyBuildNumberType>
<AssemblyBuildNumberFormat>DirectSet</AssemblyBuildNumberFormat>
<AssemblyRevisionType>NoIncrement</AssemblyRevisionType>
<AssemblyRevisionFormat>DirectSet</AssemblyRevisionFormat>
<AssemblyFileMajorVersion>$(Major)</AssemblyFileMajorVersion>
<AssemblyFileMinorVersion>$(Minor)</AssemblyFileMinorVersion>
<AssemblyFileBuildNumber>$(Build)</AssemblyFileBuildNumber>
<AssemblyFileRevision>$(Revision)</AssemblyFileRevision>
<AssemblyFileBuildNumberType>NoIncrement</AssemblyFileBuildNumberType>
<AssemblyFileBuildNumberFormat>DirectSet</AssemblyFileBuildNumberFormat>
<AssemblyFileRevisionType>NoIncrement</AssemblyFileRevisionType>
<AssemblyFileRevisionFormat>DirectSet</AssemblyFileRevisionFormat>
</PropertyGroup>

By now you'll note that TFS `drops` the build ( stage the build to an archive location ) in a directory structure with it's build name. I like the `latest` pattern where each build drops the bits in a folder called LATEST so that a downstream build can consume it. In our TFSBuild.proj we'll do something like this:

<Target Name="AfterDropBuild">
<CreateItem Include="$(BinariesRoot)\**\*.*" >
<Output TaskParameter="Include" ItemName="AllLatestFiles"/>
</CreateItem>
<RemoveDir Directories="$(DropLocation)\LATEST" />
<Copy SourceFiles="@(AllLatestFiles)" DestinationFiles="@(AllLatestFiles->'$(DropLocation)\LATEST\\%(RecursiveDir)%(Filename)%(Extension)')"/>
</Target>

Now when we drop the build we'll see the binaries get copied to a build specific folder, and a latest folder. It's time to move on to the InstallShield portion that will be in Part 3 of this article.

3 comments:

Simon said...

Hi Christopher,

I'm trying to get the assemblyinfo task working with an incremental build - I've followed the steps you mention, but I'm having no luck at all with it.

I can't find where $(BuildLabBuild) is being set, or even defined - I understand that you put the Imports code into the individual .csproj files rather than into the top-level TFSBuild.proj file, but are you defining/setting this variable there? Is it just the opposite of $(IsDesktopBuild)?

I'm also not sure where $(Build) is being defined and set. Any chance you could provide a little more information?

Christopher Painter said...

The way I currently do it is I put the property in my TFSBuild.rsp response file.

/p:BuildLabBuild=true
/p:VersionControlFile=\\SOMESERVER\BUILD_CONTROL\A2_BRANCH_2_3.CONFIG

Then in my TFSBuild.proj I do an import element specifying the project $(VersionControlFile). This allows me to externalize the Major.Minor.Build.Revision that gets called into my custom VersionNumber pattern ( BuildNumberOverrideTarget ).

Anonymous said...

you can find the AssemblyInfo Task version 1.0.51130.0
here:
http://code.msdn.microsoft.com/AssemblyInfoTaskvers