Buildinator

Buildinator generates TFS Build definitions from an XML file, enabling canonical "templates" that make it easy to add or copy build definitions.

Why Buildinator?

Back in the good ol' days, before TFS and TFS Build were popular, we used CruiseControl.NET to host our continuous integration builds and automated deployments. The cool thing about CruiseControl.NET is that the build configuration is driven by an XML file, and (most importantly) there is an XML preprocessor that enabled composing common XML fragments (with variable references) such that one could author and maintain them as canonical build templates. The XML preprocessor is very powerful and enabled me to create one line XML elements that expanded to similarly configured build definitions.

When I moved to TFS Build, I missed the XML preprocessor and XML-defined build definitions. This was especially evident when we had 20 or so builds defined for a source tree--and when we branched, we had to painstakingly re-create each build for the branch manually (through the TFS Build UI, yuck).

Thus, Buildinator was born--to be able to manage TFS Build definitions via an XML file, in the comfort of my own text editor, where I could copy/paste at will, and manage all my builds with a single check-in! I can now make all my builds act and behave the same--for instance: use the same controller, the same drop folder, the same MSBuild parameters, retention policy, trigger method, etc. The only thing different would be the build's name, major version number and source branch folder.

Example

For example: with Buildinator, this simple XML:
<pp:include href="build-definitions.config" xmlns:pp="urn:builddefs.preprocessor"/>
<pp:CI SolutionName="MyApp" Version="1.0" TFSPath="$/myapp" BuildFile="Build.proj" Parameters="Configuration=Debug" BuildController="MyTFSBC" />

Using this template (named build-definitions.config):
<pp:define name="Common">
    <Map TFSPath="$(TFSPath)" />
    <pp:ifdef name="Cloaks">
        <pp:Cloaks />
    </pp:ifdef>
    <BuildControllerName>$(BuildController)</BuildControllerName>
    <DropLocation>\\$(BuildController)\builds</DropLocation>
    <ProcessTemplate>GetSourceAndRunMSBuild.xaml</ProcessTemplate>
    <Parameter Key="Verbosity" Value="Diagnostic" />
    <Parameter Key="MSBuildProjectFile" Value="$(BuildFile)" />
    <pp:if expr="'$(Parameters)'.substr(-1) == ';'">
        <pp:define Params="$(Parameters)Buildinator=true" />
    </pp:if>
    <pp:else>
        <pp:define Params="$(Parameters)" />
    </pp:else>
    <Parameter Key="MSBuildArguments" Value="/p:$(Params)" />
    <AgentSettings AgentNameFilter="*" MaxExecutionTime="00:30:00" MaxReservationTime="00:30:00" TagsFilter="MatchExactly" Tags="" />	
    <RetentionPolicy>
        <Policy BuildReason="Triggered" BuildStatus="Succeeded" Keep="10" DeleteOptions="All" />
        <Policy BuildReason="Triggered" BuildStatus="Failed" Keep="10" DeleteOptions="All" />
        <Policy BuildReason="Triggered" BuildStatus="Stopped" Keep="1" DeleteOptions="All" />
        <Policy BuildReason="Triggered" BuildStatus="PartiallySucceeded" Keep="10" DeleteOptions="All" />
    </RetentionPolicy>
</pp:define>

<pp:define name="CI">
    <TFSBuild Name="$(SolutionName) $(Version) CI">
        <Description>$(SolutionName) $(Version) Continuous Integration</Description>
        <Trigger Type="Individual" />
        <pp:Common Parameters="Configuration=Debug;$(Parameters)" />
    </TFSBuild>
</pp:define>

<pp:define name="Deploy">
    <TFSBuild Name="$(SolutionName) $(Version) DEPLOY $(Env)">
        <Description>$(SolutionName) $(Version) Deployment for $(Env)</Description>
        <Trigger Type="None" />
        <pp:Common Parameters="Configuration=Release;MajorRevision=$(Version);$(Parameters)" />
    </TFSBuild>
</pp:define>

Would expand to this entire tree:
<TFSBuild Name="MyApp 1.0 CI">
    <Description>MyApp 1.0 Continuous Integration</Description>
    <Trigger Type="Individual" />
    <Map TFSPath="$/myapp" />
    <BuildControllerName>MyTFSBC</BuildControllerName>
    <DropLocation>\\MyTFSBC\builds</DropLocation>
    <ProcessTemplate>GetSourceAndRunMSBuild.xaml</ProcessTemplate>
    <Parameter Key="Verbosity" Value="Diagnostic" />
    <Parameter Key="MSBuildProjectFile" Value="Build.proj" />
    <Parameter Key="MSBuildArguments" Value="/p:Configuration=Debug" />
    <AgentSettings AgentNameFilter="*" MaxExecutionTime="00:30:00" MaxReservationTime="00:30:00" TagsFilter="MatchExactly" Tags="" />
    <RetentionPolicy>
	<Policy BuildReason="Triggered" BuildStatus="Succeeded" Keep="10" DeleteOptions="All" />
	<Policy BuildReason="Triggered" BuildStatus="Failed" Keep="10" DeleteOptions="All" />
	<Policy BuildReason="Triggered" BuildStatus="Stopped" Keep="1" DeleteOptions="All" />
	<Policy BuildReason="Triggered" BuildStatus="PartiallySucceeded" Keep="10" DeleteOptions="All" />
    </RetentionPolicy>
</TFSBuild>

Which Buildinator then processes and creates the actual build definitions using the TFS Build web service API on the target TFS system.

Further, Buildinator is wired-in as a TFS Build definition itself (Buildinator is implemented as an MSBuild Task)--so when I check-in changes to the XML file, then all the builds get created (or updated). Awesome!

Setting Up Buildinator

To setup Buildinator, follow these steps:
  1. Download the latest Buildinator release, which should include the following files:
    • msbuild.tasks.buildinator.dll - the Buildinator MSBuild Task assembly
    • buildinator.proj - sample MSBuild file to wire-in as a TFS Build job to run Buildinator whenever configuration files change
    • build.config file - XML file where you create your simplified references
    • build-definitions.config file - XML file where you compose your canonical build definitions (aka Macros, or Templates)
    • GetSourceAndRunMSBuild.xaml - simplified TFS Build workflow used by Buildinator
  2. In your TFS Project source, create a folder under $/BuildProcessTemplates named Buildinator
  3. Add the GetSourceAndRunMSBuild.xaml file to $/BuildProcessTemplates, and the other files to the $/BuildProcessTemplates/Buildinator folder and check them in
  4. Edit the build.config file as you see fit; note that it references the build-definitions.config file where you can adjust or create new XML sections that fit your environment
  5. Create a TFS Build definition manually--to run the GetSourceAndRunMSBuild.xaml workflow when anything changes in the $/BuildTemplates/Buildinator folder
  6. Test, revise, refine, test again... add more builds to the build.config file

Notice

Note: This product includes software developed by ThoughtWorks, Inc. (http://www.thoughtworks.com/). Portions are Copyright (c) 2005 ThoughtWorks, Inc. All rights reserved. Namely, the open-source XML preprocessor logic from the CruiseControl.NET distribution. For more information, see the License-README.txt file in the preprocessor folder.

Last edited Jun 6, 2013 at 2:30 PM by bpmerkel, version 24