Wednesday, May 14, 2008

VisualSVN + TeamCity + Nant + Sql Server = A Great CI solution

When you work full time as a programmer, the chances are that your codebase is a big portion of your business value.

You shouldnt just write code write code; protect it, keep it clean, version it out, and ensure its quality!
You should also be able to easily deploy and deliver that codebase in a clean, reliable automated fashion, limiting human-related error risks and giving yourself the ability to deploy quality release artifacts/MSIs/ website updates at the drop of a hat.

Its hard to argue against these ideals, but sometimes it just seems you never have the time to "do it right".

After a bit of experimentation, I've put together a solid CI system on the cheap. Its flexible, scales out well, and you could go from zero to solid build server in two day's time**.
**It may take 1 hour mechanically, but the two days factors in learning curve and any unanticipated setbacks.

The Tools:

SCM: VisualSVN Server
Cost: Free
Subversion may not be the perfect SCM, but short of its merging hassles, its a widely popular well-documented rock-solid standard, and handles true branching strategies perfectly. VisualSVN Server is a configuration of subversion straight-jacketed for windows environments. It has windows integrated authentication, and you can provision subsets of the code base to windows users and groups on a read-only/read-write basis. Https server access comes automatically as the program starts up. Their Visual Studio plugin works with Vs 2005 and 2008 flawlessly. Even though there are free alternatives for VS integration, dont be a cheapskate here, if you pony up the $50 for their VS plugin, you wont regret it.

CI: JetBrains TeamCity
Cost: Free for up to 20 unique build actions, 20 users, 3 separate build agent processes. (A generous number)
While its true that CruiseControl.NET or CIFactory gives you everything you need for CI, those options look like a scripting nightmare reminiscent of recompiling a linux kernel in comparison to TeamCity. Besides, with built-in features such as distributed build farm agents, reports, and alerting it rivals the feature set of Microsoft Team Foundation server. TeamCity integrates tightly with subversion and gives you the ability to perform "delayed commits", which is like running a hypothetical build on the server without committing any code. (This is the equivalent to "shelfing" a build in TFS). TeamCity is a platform-agnostic build server, which is nice if you have Java/rails/AIR, or other non-.NET codebases to manage.

Build Action Scripting: NANT and NantContrib
Cost: Free, small learning curve
Dont be scared, just learn it. Ant and Nant are powerful xml-based scripting languages designed specifically for building and deploying code. If you dont know nant, dont write a script from scratch, invest a focused 2-3 hours learning how it works by tinkering around with my pre-written example and you'll be ready to write your own. A well-built nant script can be executed locally or on the build server with the same results. This FAQ shows you how to add Nant-scripting intellisense into your visual studio editor to help you get started.

Build Server Database: Sql Server
Cost: If youre reading this, chances are you already have one.
TeamCity relies on a database. Out of the box, it uses a file-based database, which is not recommended for long-term stability. JetBrains recommends you migrate the server to a MySql database, but for those of us adventurous and adamant about consolidating our resources, there is experimental support** for Sql Server 2005. I'll show you how to do that.
**Its been working flawlessly for me with everything Ive thrown at it

The goal:


Basically, developers of diverse roles and platforms (web designers, QAs, architects), can update/commit to and from a common VisualSVN server over an https line, administered and secured by a "build master" role. The "build master" provisions read/write/none access to windows/AD identities and groups.

When changes have been committed to the SVN repository, a build action can be triggered on the TeamCity server. The particular action that is triggered can depend on factors such as:
  • The location of the changed files within the source tree
  • What person committed changes
During a build action, a build agent process will create a new clean temp directory, check out the latest version of the code base into the directory, and carry out a set of desired build actions. TeamCity offers some basic pre-fab build actions such as:
  • SLN 2005/2007: Build an entire solution, run NUnit tests, and report build/unit test results
  • Duplicate Finder: Report any duplicate code within a codebase
They are good starting examples, but more than likely your best option is to trigger your own custom NANT script. With custom scripting:
  • You have versions of a build routine managed source control
  • You can run a custom build script locally instead of the standard ctrl+shift-B in visual studio.
  • Its easy to configure a build agent to run a script within the source code tree.
So, if this design sounds good to you, lets go through the steps to making it happen.

0. First things first: determine what machines are going to host each application listed above and make sure they have access to one another (maybe all of the resources can be on the same server?). make a list of the build routines you desire, and when they will need to run. Make a list of your target deployment servers, their target directories, how and when deployments are going to be delivered. Create a windows identity that will host the build agent service, and make sure that this identity has write access to your intended deployment targets. The more time you invest in step 0, the easier the rest of this system will be to implement.

1. Install and configure the VisualSVN server. This is mostly a no-brainer here, it needs to be readily available to anyone who works with the code, so install this on a server that you can reach via https, or at least a secret port number. A good repository design is to use one solution per repository.

2. Import your codebase into the repository. "Check out" a local copy of your new and empty repository, paste your codebase into the trunk folder, and commit what you just checked out. Avoid adding auto-generated (dlls, resharper temp files, bin/obj folders) to the code repository. There are many great articles about getting started with subversion, here is one about getting started with VisualSVN. Once your code base has been committed to the repository, you can set up user access to the source tree.

3. Install and configure TeamCity. Run the setup on the target server. Log in to ther web server -based console. Create a VCS root, and point it to the URL of your newly created repository. The URL of your repository can be found on the VisualSVN administration console.

4. Install and configure a build agent. Use the TeamCity console to add/install a build agent, install the build agent service on the desired host server. In the MMC services console (start->control panel->administration tools->services), locate the build agent service, and bind the service's identity to that identity you made in step 0.

5. Create the TeamCity Database in your SQL Server. Just login to your target sql server and create a new database and call it 'TeamCity'. Unfortunately, TeamCity uses JDBC for connectivity and that means you can't use windows integrated authentication via this connection, you will need to create a login for this database, and give the new login "dbowner" privileges for the TeamCity database.

5. Make TeamCity talk to Sql server 2005. (One more time, sql server has "experimental support", but it has worked well for me) TeamCity has migration scripts inside of the bin folder of your installation. When the migration scripts run, they install tables and stored procedures onto the target database. For a successful migration, you will need to configure the dbmigration.properties file, and point it to the traget database, as well as the correct jdbc driver. General instructions here. To migrate into sql server, you will need the sql server 2005 jdbc driver located here at microsoft, and you should copy it to the same driver folder specified in the general instructions (\webapps\ROOT\WEB-INF\lib folder off your root TeamCity installation directory). The connection string you will be using inside of the dbMigration file should look something like:
driverName=com.microsoft.sqlserver.jdbc.SQLServerDriver
connectionUrl=jdbc:sqlserver://localhost:1433;database=TeamCity;
connectionProperties.user=user
connectionProperties.password=password

maxConnections=50
poolPreparedStatements=false
and you should change the server/user/password/port connections to your needs. You can test the connection to your server using [telnet [server] [port]] command on the command line. After youve run the migration batch file and the migration is complete, restart the TeamCity web server service (under the services console). When you re-visit the server with a browser, dont worry about the prompt to create a new user, you have not lost your build configurations.

6. Install Nant/Nant-Contrib. You will need to install these both on your developer machine and on the build server machine. When defining a new Nant build action in TeamCity, you will need to specify the path to the Nant binaries. Nant does not support building sln files in VS2005 or older, nor does it build individual csproj files, but MSBuild does. TeamCity doesnt support MSBuild triggering, but it supports Nant triggering. I bridged this annoying little gap using a custom MSBuild Nant task, a Nant task that calls MsBuild. Thats one of the many great custom tasks found in the NantContrib library. Here is an example of a simple build, test, deploy script using Nant:

<?xml encoding="utf-8">
<project name="MyProj" default="run" basedir="..\" xmlns="http://nant.sf.net/release/0.85/nant.xsd">
<property name="SchemaFile" value="C:\Program Files\Microsoft Visual Studio 8\Xml\Schemas\NAnt.xsd">
<description>Build Script</description>

<!-- builds only the csproj, not the entire solution-->
<target name="build"
description="Compile the project using Debug configuration for more verbose error descriptions">
<echo message="Building...">
<msbuild verbose="true" verbosity="Detailed" failonerror="true" project="MyProject.csproj">
<property name="debug" value="true">
</msbuild>
</target>

<!-- deploys the project -->
<target name="deploy" depends="build, test" description="Copy production files">
<!-- Copy build results to Production folder -->
<echo message="Deploying...">
<copy
todir="\\mydestinationserver"
failonerror="true"
overwrite="true"
includeemptydirs="true"
verbose="true"
>
<fileset basedir=".">
<includes name="**/*">
<exclude name="**.resx">
<exclude name="**.cs">
<exclude name="**.csproj*">
<exclude name="**/obj/**">
<exclude name="**.pdb">
<!--<includes name="**.aspx">
<includes name="**.ascx">
<includes name="**.config">
<includes name="**.gif">
<includes name="**.jpg">
<includes name="**.mdb">-->
</fileset>
</copy>

<!-- Update connection string settings -->
<!--<xmlpoke file="Web.config"
xpath="/configuration/location/appSettings/add
[@key='ConnStringAdmin']/@value"
value="Data Source=whatever;User ID=...;" />-->
</target>

<!-- runs unit tests, if the unit tests fail, the process stops -->
<target name="test" depends="build" description="run unit tests">
<echo message="testing">
<nunit2 failonerror="false" verbose="true">
<test>
<assemblies basedir="UnitTest\bin\Debug\">
<includes name="*Test.dll">
</assemblies>
</test>
</nunit2>
</target>

<!-- default target -->
<target name="run" depends="build, test, deploy" description="build, test and deploy">
</target>

</project>
This code will build a specified csproj file, run unit tests on a specified assembly, and if everything goes well enough, it will update a connection string in the configuration file as it copies the new code base to a specified destination (maybe change over to a staging server database). For web projects, if you have any trouble running this, you may need to copy your Microsoft.WebApplication.Targets file to a local directory, so you wont have to install visual studio on your build server. More details about that little trick here.

7. Define your build actions and how they are triggered. Maybe you will run an NDepend analysis or duplication search on a weekly basis. Maybe you have a different deployment destination for a branch of a project versus the trunk of a project, or maybe you need one build action to run in succession to another, forming a chain of build actions. Here is an example of location-based build triggering syntax in TeamCity:
+:/trunk/**
-:/branches/**
This build action would be triggered by committed files to the trunk, and not by changes to the branches directory of a repository.

8. Test it out, fine tune it. You should be able to run that nant script locally, or on the build server. Once you have the kinks worked out, release it to your development team and try it out in practice.

In summary, this plan is an approach to a relatively easy-to-implement build system that can pay off big time in both the immediate and long run.

I'll be interested in hearing any variations on this plan that work for you.

7 Comments:

Blogger Rossen Totev said...

Nice article. I was planning to do something similar for our build system. You have brought up some very useful points - for example, I was not aware that the JetBrains TeamCity has a free version but will take a look at it now. We currently use an external Subversion hosting service but I think that your solution can be tweaked to use that instead of a local VisualSVN server.
Thanks again for sharing your ideas. I will post later how our "tweaked" version goes.

8:30 PM  
Blogger Peter Weissbrod said...

Rossen--
If you would be so kind, I would be very interested in hearing your experiences with the external source code hosting.

My guess is that it will be a seamless connection :)

12:23 PM  
Anonymous Ivan B said...

I get the following error using your NAnt build script:

"Invalid element msbuild. Unknown task or datatype."

Any ideas?

11:12 AM  
Blogger Peter Weissbrod said...

MasBuild is an "unknown task"?

My guess is that the MSBuild task has not been installed.

That is a part of NantContrib. I would download NantContrib and copy all of those dlls into the same directory as your Nant.exe, and then maybe it will find the msbuild task.

Hope that helps

12:02 PM  
Anonymous Ivan B said...

Thanks, that helped! (Still a newb to NAnt)

7:24 AM  
Blogger BioKitsap said...

Any luck getting VisualSVN to install a repository at the root of your server?

For example, I want to access my repository at http://svn. However, I end up with a repository at http://svn/svn/svn.

Truely dumb... And the VisualSVN Google forums is useless. Hope you can help.

Thanks.

9:16 AM  
Blogger Peter Weissbrod said...

its just the conventions set forth by the protocols.

[http://] [servername] [svn]

the first section is the protocol, it could be https (port 483) http(80) or svn(3690).

the second section is the netbios/host header name of the server. note that visualsvn installs an apache instance. if you have iis and apache on this same machine then you may have have a port conflict on 80, unless a special port is reserved for each destination,or host headers are properly defined.

finally the third section is a convention set forth by visualsvn server, where svn is below the root directory of the apache instance.

in your case, svn seems be the name of the server, and svn/svn must be subdirectories within the svn server.

I recommand you contact the visualsvn team for support, I think you will find them to be very accommodating. there are also videos of installing visualsvn up on youtube if you need a visual demo.

9:35 AM  

Post a Comment

<< Home