Posted by Will on Wednesday, June 25, 2008 at 1:06 PM

In the previous episode I intended to cover the configuration of TeamCity, but took a quick detour to talk about MSBuild and MSBuild scripts.  Now that we've got a script to use we can configure TeamCity.  The following will be the minimal steps to get up an running.

  1. To start off, log into your TeamCity installation and click on the Administration button (top right area of screen) to go to the Projects and Build Configurations screen.  This screen will give you an overview of all the projects that you have configured to run on this installation. 
  2. Click on the Create Project link on the page.  Name your project and give it a description then click the Create button.
  3. The next screen will provide you will the options for creating a build configuration and a VCS root.  Here are the values I used under the heading of General Settings:
    • Name: Something that makes sense in your context (i.e., Standard MSBuild).
    • Build number format: I'm currently using 2.0.{0}.{build.vcs.number.1}  The initial 2.0 is static, but {0} is a placeholder for the build counter value.  The {build.vcs.number.1} placeholder is, as the note below the text box says "a reference to VCS changeset number.  So for example 2.0.71.144 means the 71st build using revision 144 of 2.0.  There are different strategies for version number make up.  The important thing is to pick something that works for you and stick with it.
    • Build counter: This allows you to set the build number to a higher seed if for instance you're migrating from another configuration where you've already reached build number.  Conversely, you can also decrement the value if you want to zero the build counter.
    • Click on VCS settings button to continue with the setup.
  4. Version Control Settings are next. 
    • Click on Create and attach a new VCS root.
    • Enter a meaningful name and choose the appropriate type of VCS, in my case Subversion.
    • Fill in the blanks for the URL, and if necessary, user name and password.  There is an option to test the connection at the bottom of the page. 
    • Save your changes to return to the previous page.
    • Back on the Version Control Settings I chose Automatically on server for the VCS checkout mode and left the rest of the settings unchanged.  Click the Choose Build Runner button to advance.
  5. From the Build runner options I chose MSBuild.  The next parameter, Build file path, took me a couple of tries to get right the first I set this up.  The note under the text box says "Specified path should be relative to the checkout directory" but keep in mind that your URL choice in the VCS root options will influence the relative path.  If you have a traditional directory structure of branches, tags, and trunk at your VCS root and specify the root of your source tree then your Build file path may look something like trunk\MyBuildScript.xml.  Two MSBuild versions should be available, the one from the 2.0 and the 3.5 version of the .NET Framework.  I would suggest 3.5 even if you're building a Visual Studio 2005 project.  Click Save.

At this point all of the necessary steps have been completed.  However, TeamCity offers more options that can be configured around your project.  The options are listed on the right side of the screen under Build Configuration Steps heading when you're editing a project.  All of these options are also available from the Edit Configuration Settings link on the project tool bar.  One of the options in particular is worth considering - Build Triggering.  TeamCity offers many options for triggering a build.  The one I found most useful is VCS Triggers.  TeamCity can check for any changes to your source control repository on the interval you choose and run a new build with the changes. 

In order to make use of your project you need to have an authorized build agent available.  Click on the Agents tab in the top left area of the screen.  Under the Agents heading you will see four tabs.  One of them (probably the Unauthorized agents tab) should have a number greater than zero in parenthesis.  Click on that tab.  If it is the Unauthorized agents tab click on the Authorize agent link then move over to the Connected Agents tab and click the Enable agent link if necessary.  Your agent is now available for use.

With your project completed you will find it listed under the Projects tab (top left area of screen).  Choose your project from the drop down menu embedded in the Project tab.  Under the Current status section of the Overview tab if you have a No suitable agents link return to the previous paragraph and check your settings.  From this screen you can trigger a build by clicking on the Run button in the toolbar.  After a second or two the page will refresh and show you the status of the build as it progresses.  The build result will be shown in the Recent history section of the page. 

A word about troubleshooting a failed build: If the build fails you can use the drop down menu in the history list beside the result in the Results column to view the build messages to guide your troubleshooting.  A couple of things that have tripped me up are forgetting to add a newly referenced dll to source control.  Along the same lines, referencing a new dll and not updating the MSBuild script accordingly caused a failed build.  If you have no idea where to begin troubleshooting start by running the script locally from a Visual Studio command prompt.  See the end of Episode 4 for more information.

If you're new to TeamCity I would suggest spending the time to explore the interface and become familiar with the many available options.  As I said at the start of this post, I've only covered the minimal step to get you up and running from a cold start. 

Comments [0]     Categories: Continuous Integration | MSBuild | TeamCity              
Posted by Will on Tuesday, May 27, 2008 at 10:13 PM

In the last episode I covered the installation of TeamCity and the associated MySQL database and said I would cover the configuration next.  I'm going to put that off for now and instead focus on MSBuild and the associated build script. 

Within TeamCity there are several available build runners.  From the TeamCity documentation a build runner is "a mechanism that executes a build of a certain type."  That's vague way of saying it will do the same thing that choosing "Build Solution" from the Build menu in Visual Studio will do - an oversimplification perhaps, but true.  So what's going on under the hood?  In this case, Visual Studio uses the settings in your solution and project files to compile your application or class library whether it be a debug or release build.  That is to say - the build script (a solution file) directs the actions of the build runner (Visual Studio).

TeamCity supports a dozen build runners, among which are sln2005, sln2008, and MSBuild.  The TeamCity documentation says to choose sln2005 or sln2008 if you want to use your .sln file from Visual Studio 2005 or 2008 respectively as the build script.  I started off down that track but quickly realized that I needed more control than I could get with a .sln file so I switched to MSBuild and used an MSBuild script.  In truth sln2005 uses MSBuild from .Net 2.0 while sln2008 uses the version of MSBuild from .Net 3.5.  The practical differences are in the configuration screens in the TeamCity setup.  For now, just be aware that MSBuild comes as part of the .Net framework installation.  You'll find it on your machine at <system drive>\Windows\Microsoft.NET\Framework\<version>\MSBuild.exe.  You can see it in action by opening a Visual Studio command prompt (Programs > Microsoft Visual Studio> Visual Studio Tools > Visual Studio 2008 Command Prompt) and entering the command msbuild followed by the full path to a .sln file.  Hit return and you'll see the build process.  It's the same thing that happens when you build from inside Visual Studio itself.   

As I mentioned earlier, MSBuild can use solution (.sln) files as build scripts although they do not have the same format as a true MSBuild project file.  Visual Studio project files do follow the format though. You can open one in NotePad to see the structure.  To get the whole thing started though you'll need a project file.  Start with something like this:

<Project DefaultTargets="" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
</Project>

I called mine BuildScipt.xml and used Visual Studio for editing.  You might prefer the XML Notepad tool from Microsoft.  As for the contents, MSDN has an overview of the file format for your reference.  In summary, there are several distinct sections:

PropertyGroup: Contains key/value pairs that can be referenced elsewhere in the script

<PropertyGroup>
    <TextResources>$(MSBuildProjectDirectory)\textfiles</TextResources>
    <SourceControlUserName>bobsuruncle</SourceControlUserName>
</PropertyGroup>

The $(MSBuildProjectDirectory) notation above uses a reserved word.  MSBuildProjectDirectory refers to the directory that contains the build script.  The TextResources key (one of my own making) above refers to a directory named "textfiles" that is within the directory containing the build script itself.  The $() notation is used to invoke a reserved word and is also used to reference the value of a PropertyGroup element via its key (see below).

ItemGroup: Contains references to inputs into the build

<ItemGroup>
    <FilesToInclude="$(TextResources)\readme.txt" />
    <FilesToInclude="$(TextResources)\license.doc" />
</ItemGroup>

Notice that in the "FilesToInclude" item group there are two files, readme.txt and license.doc.  They are found using the path described by the TextResources PropertyGroup element.  It is therefore equivalent to ".\textfiles\".  Later we can use @ character instead of $ (i.e., @(FilesToInclude)) to refer to this entire list of elements (see below). 

Target: Groups of tasks to be performed as a unit. 

<Target Name="CleanProject">
  <Message Text="******** Beginning Cleaning ********" />
  <!-- delete the InstallerComponentDirectory directory and its contents -->
  <RemoveDir Directories="$(InstallerComponentDirectory)" />
  <!-- delete the InstallerOutputDirectory directory and its contents -->
  <RemoveDir Directories="$(InstallerOutputDirectory)" />
  <!-- clean the build -->
  <MSBuild Projects=".\MySolution.sln" Targets="Clean" ContinueOnError="false" />
  <Message Text="******** Cleaning Complete ********" />
</Target>

This section introduces a few more concepts.  The name attribute is how you refer to the target elsewhere in the script.  Comments are designated with <!-- --> notation.  The Messages element is a preconfigured MSBuild task similar to Console.WriteLine() command for outputting information to the screen.  You will see this output while watching a build in TeamCity (as well as in the build log) so it makes for useful real-time feedback during the process.  The RemoveDir and MSBuild elements are also MSBuild tasks.  The MSBuild task is used to run the "Clean" command on the solution via the Targets attribute.  I could have just as easily put "release" or "debug" to run those builds.  In addition to the tasks that ship with MSBuild you can also make use of MSBuild Community Tasks which expands your options to include creating assembly files, FTP support, zip compression, registry manipulation, and more.  MSBuild Community Tasks is an open source project available as both an installer and source code.  Download the source code even if you don't need it because it includes the documentation as a help file.  If you get that annoying "Navigation to webpage was canceled" error when you open the help file remember to right-click on the file, choose properties from the context menu, and then click the Unblock button on the General tab to restore functionality.  To make use of the tasks run the MSBuild Community Tasks installer then add the following line just after the opening tag of your build script:

<Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>

For my HappyFish build script I wanted to be able to upload my changes to source control and with no other interaction end up with an installer on my Web site with the version number in the file name (i.e., HappyFish.2.0.63.117.msi).  The steps involved break down to:

  1. Create the assembly file(s) for the dll's and the exe to handle versioning
  2. Compile the solution
  3. Merge the assemblies
  4. Create the installer
  5. FTP the results

The hardest part was dealing with the build number.  I tried a lot of different techniques for tracking and managing build number incrementing but finally decided the least amount of friction was to let TeamCity handle it for me.  I'll have more on this in a later post, but TeamCity allows you to specify the build number format and seed for the build count value.  You can then refer to the build number using "$(build_number)" environment variable in your script wherever you need it. 

Returning to the list, step one consisted of making Assembly.cs files for the project components like so:

<Target Name="SomeComponentAssembly" >
  <Message Text="Writing SomeComponentAssembly file for $(build_number)"/>
  <!-- Create/update the assembly.cs file -->
  <AssemblyInfo CodeLanguage="CS"
   OutputFile="$(ProjectDirectory)\MyProject\Properties\AssemblyInfo.cs"
   AssemblyTitle="MyProject.MyAssembly"
   AssemblyDescription="My Project Library"
   AssemblyConfiguration=""
   AssemblyCompany="ThirstyCrow"
   AssemblyProduct="ThirstyCrow.MyProject.MyAssembly"
   AssemblyCopyright="Copyright © ThirstyCrow 2008"
   AssemblyTrademark="thirstycrow.net"
   ComVisible="false"
   CLSCompliant="true"
   Guid="9cd1823a-7c8f-4a0c-b740-0311ec695afe"
   AssemblyVersion="$(build_number)"
   AssemblyFileVersion="$(build_number)" />
</Target>

Complete documentation of the Assembly task can be found in the MSBuild help file by searching for assemblyinfo class members.  If want a quick way to create Guids check out this tip.

Second on the list is to compile the solution.  This is easily done with the MSBuild task show above.  By specifying that the StartMyBuild task below depends on another target, in this case SomeComponentAssembly then the tasks within that target will be run before the StartMyBuild tasks.  If you have more than one dependency you can enter them as a semicolon separated string (i.e., "SomeComponentAssembly;SomeOtherComponentAssembly;YetAnotherComponentAssembly"

<Target Name="StartMyBuild" DependsOnTarget="SomeComponentAssembly">
  <MSBuild Projects=".\MySolution.sln" Targets="Rebuild" ContinueOnError="false" />
  <Message Text="******** Build Complete ********" />
</Target>

You may or may not want to merge your assemblies, but if so you can use the ILMerge class of the MSBuild Community Tasks.  In the example below I've used the MakeDir MSBuild task to conditionally make a directory that will be used later to hold all of the components that will be combined into an installer project.  ILMerge is used to merge the list of components found in the "MergeFiles" ItemGroup.  NOTE: For some reason that I have yet to figure out if you use the $(propertygroup) notation in the OutputFile attribute of ILMerge it will fail so you'll have to put in the path explicitly.  However, it can be a relative path as shown.  Finally, as part of this target I've copied the supporting documents from the FilesToInclude (see above) - the readme and license documents - to the InstallerComponentDirectory that I'll be using later.

<Target Name="MergeAssemblies" >
  <Message Text="******** Beginning Merge ********" />
  <!-- create the InstallerComponentDirectory directory -->
  <MakeDir Directories="$(InstallerComponentDirectory)" Condition="!Exists('$(InstallerComponentDirectory)')"/>
  <!-- merge the assemblies -->
  <ILMerge TargetKind="WinExe" OutputFile=".Installer\ComponentDirectory\MyApplication.exe" InputAssemblies="@(MergeFiles)" DebugInfo="false"/>
  <!-- copy the files to the InstallerComponentDirectory directory -->
  <Copy SourceFiles="@(FilesToInclude)" DestinationFolder="$(InstallerComponentDirectory)" />
  <Message Text="******** Merge Complete ********" />
</Target>

For the installer I used Wix, which warrants a post or two of its own.  I'll skip over that for now and move on to FTP.  FTP is straight forward, just follow the documentation in the MSBuild Community Tasks found under FtpUpload Class.  My section looked something like this:

<Target Name="FtpProject">
  <Message Text="******** Uploading Files ********" />
  <FtpUpload
    Username="myusername"
    password="mypassword"
    RemoteUri="ftp://mydomain.com/builds/MyApplication.$(build_number).msi"
    LocalFile="$(InstallerOutputDirectory)\MyApplication.msi" />
  <Message Text="******** Uploading Complete ********" />
</Target>

You'll notice I made use of the build number environment variable provided by TeamCity to differentiate this build from others, thereby preventing naming collisions.

To test your build script you'll need a way to get the whole thing started.  Use the DefaultTargets attribute of the opening Project tag to tell MSBuild where to start.  Based on the example above I might use DefaultTargets="StartMyBuild".  This list can also be a semicolon separated list such as DefaultTargets="StartMyBuild;MergeAssemblies;FtpProject".  Save your changes and bring up the Visual Studio command prompt (see above) and run msbuild <path to your script>.  Be aware of the following caveats in troubleshooting:

  • References to the TeamCity specific $(build_number) environment variable will be empty strings, but it will not cause the script to fail.  The FtpProject output to the remote server would consequently be MyApplication..msi.  In production the space between the two dots in the filename would contain the build number.
  • If you use the copyright symbol © you'll need to make sure that the encoding of you build script is UTF-8.

At this point, if the script runs from the Visual Studio command prompt and you've used relative directory paths based on the MSBuildProjectDirectory reserved word you should be in good shape for using this script with TeamCity.  That will be the topic of the next thrilling episode of Adventures of a Lead Programmer in a One-man Shop.

Comments [0]     Categories: Continuous Integration | MSBuild