Posted by admin 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 admin 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              
Posted by admin on Tuesday, April 29, 2008 at 4:12 PM

Previously on Adventures of a Lead Programmer in a One-man Shop -
"No, please, I can't take it anymore.  I'll do it.  I'll put my code in source control.  I can't bear the .zip file shame any longer.  Just leave me enough money for milk and crackers.  What?  It's free?!?  And easy?!?  You had me at free..."

It's official.  I've begun wriggling out of my inferiority complex and scrambling up toward a shinny, new Superiority Complex.  In my previous post I said that I wanted to couple my source control solution to a Continuous Integration server that will compile my latest bugs edits into a software release.  When researching continuous integration solutions the two that I heard mentioned most often were CruiseControl.NET, an open source project from ThoughtWorks, and TeamCity from JetBrains.  CC.NET is free and JetBrains offers a free professional version so they both passed the first test.  My sense from snooping around on the web though was that TeamCity would be easier to configure.  I can't say if TeamCity was easier than CC.NET because I didn't try both, but I can say that TeamCity was easy.  There were a couple of points I found confusing though.  I've detailed them in the guide below.  I went through the actual setup twice, but if you find any errors to fix or tips to add please let me know. 

This guide is intended to be used in conjunction with the TeamCity documentation.  I have included the relevant links below.  The guide covers installation of TeamCity and installation and configuration of MySQL (also free, of course) to serve as a back end for TeamCity.  For completeness I've included the Subversion setup steps at the beginning.  Refer to Craig Shoemaker's Subversion episode for more details.  On the first run through I set up TeamCity on an instance of Windows 2003 running locally on Virtual Server 2005 R2.  The second setup was on a remote Windows 2003 server that will be used for production.  Windows 2003 is not a requirement.  All of this can be configured on a single machine (i.e., your desktop machine). 

  1. Installed Subversion using SVN 1-Click Setup option. NOTE: During the install I skipped the Tortoise installation step because the bundled version is not the most current.
  2. Installed the current version of Tortoise.
  3. Installed TeamCity 3.1 Profession Edition following their installation documentation.
    • During the setup I chose 8080 as the port because I already had IIS installed using port 80.
    • For the rest of the installation I clicked through, accepting all of the defaults including for the Build Configuration dialog at the end.
    • Logged in for the first time to accept the license agreement and create the administrative account.
  4. Setup MySQL for TeamCity following their documentation.
    • Downloaded MySQL Community Server.  I chose the default typical installation type.
    • At the end of the installation when prompted with the database configuration I chose Detailed Configuration. 
    • I accepted the defaults until I reached the Concurrent Connections option.  I chose Manual Setting and set the value to 5.  Nothing magical, I just picked the lowest one since I'll only be using this for TeamCity.  You can also manually enter any number.
    • On the Default Character Set option page of the wizard I chose the Manual option and set the value to utf8, as suggested in the TeamCity docs. 
    • On the security screen I checked the box to allow remote access.
    • I accepted the default settings on the rest of the pages in the setup wizard.
    • If you make a mistake or want to change the settings later the configuration wizard can be accessed from the start menu (Programs > MySQL > MySQL Server 5.0 > MySQL Server Instance Configuration Wizard).
  5. Downloaded and installed SQLyog MySQL GUI - Community Edition.  Yep, it's free.  I've used it before and recommend it.  This will be used to create and manage databases in your installation of MySQL. 
    • After installation I connected to my instance of MySQL with SQLyog using the root credentials that I specified in the setup.
    • I created a new database for use with TeamCity (DB > Create Database).  I named it...wait for it... TeamCity and set the character set to utf8.
    • I used the User Manager of SQLyog to create an account on the MySQL instance under which TeamCity will access the database.  The User Manager is found in the Tools menu or by clicking the User Manager button on the toolbar.  I set the account name to...you guessed it... teamcityuser and granted full permissions because at the time I had no idea which privileges were necessary.  Certainly it will need Select, Insert, Update, Delete, and Create to create the supporting tables.  I wasn't sure if it would need Drop, Alter, and Grant for the setup as well.  I'll restrict them later once I've got everything working. 
    • At this point I connected to my instance of MySQL using the new credentials to verify everything was working.  If you're following along notice that the tables directory in the newly created teamcity database is empty.
  6. Downloaded and configured the MySQL JDBC driver.  The JDBC driver allows TeamCity to connect to the MySQL database.
    • The TeamCity documentation says to put the MySQL connector jar in the WEB-INF/lib of TeamCity's web application.  The MySQL connector jar file name is mysql-connector-java-5.1.6-bin.jar and is found at the root of the unzipped directory.  I copied it to C:\TeamCity\webapps\ROOT\WEB-INF\lib of my installation. 
    • The next step is to modify the database.mysql.properties file which is found in the C:\Documents and Settings\<account name>\.BuildServer\config directory.  Before renaming and modifying the file as directed in the setup I made a copy of the file as a backup.  I opened the renamed file in notepad and made the following changes:
      1. connectionUrl=jdbc:mysql://localhost/teamcity
      2. connectionProperties.user=teamcityuser
      3. connectionProperties.password=the password I chose
      4. uncommented the connectionProperties.characterEncoding=UTF-8 line by removing the leading # character
    • Saved the changes and closed the file. 

After making the changes I restarted the server.  I opened a web browser on the server and navigated to localhost:8080.  I was prompted to accept the license agreement again and had to recreate the administration account because the old local data store had now been successfully replaced with MySQL.  You'll also notice at this point if you've followed along that SQLyog will show the supporting tables have been created in the database you made for TeamCity.

Now what? 

Next I need to configure TeamCity to automatically perform a build of the code from my source control repository.  I'll cover that in the next thrilling Adventure of a Lead Programmer in a One-Man Shop.

Comments [2]     Categories: Continuous Integration | Source Control