Revisiting my automated build & continuous integration setup

A while back I wrote a small series on creating a basic build script and setting up a build server (part 1, part 2, and part 3). I used NAnt and CruiseControl.NET in that series, but alluded to a few other options for each. I recently got around to switching our build script from NAnt to psake, which is written in PowerShell, and switching our build server from CruiseControl.NET to JetBrain’s TeamCity. I’ll give a quick overview of our new build script here, which I’ll use to build on in future posts showing a few of the more interesting things that suddenly became much easier with this setup, and in a few cases, possible at all.

To start with, you’ll want to make sure you have the latest versions of PowerShell (2.0) and psake. Here’s the basics of our build script:

$ErrorActionPreference = 'Stop'
 
Include ".\functions_general.ps1"
 
properties {
	$project_name = "MainApplication"
	$build_config = "Debug"
}
 
properties { # Directories
	$scm_hidden_dir = ".svn";
 
	$executing_directory = new-object System.IO.DirectoryInfo $pwd
	$base_dir = $executing_directory.Parent.FullName
 
	$source_dir = "$base_dir\src"
 
	$build_dir = "$base_dir\build"
	$tools_dir = "$base_dir\tools"
	$build_tools_dir = "$build_dir\tools"
 
	$build_artifacts_dir = "$build_dir\artifacts"
	$build_output_dir = "$build_artifacts_dir\output"
	$build_reports_dir = "$build_artifacts_dir\reports"
 
	$transient_folders = @($build_artifacts_dir, $build_output_dir, $build_reports_dir)
}
 
properties { # Executables
	$tools_nunit = "$tools_dir\nunit\nunit-console-x86.exe"
	$tools_gallio = "$tools_dir\Gallio\Gallio.Echo.exe"
	$tools_coverage = "$build_tools_dir\ncover\ncover.console.exe"
	$tools_coverageExplorer = "$build_tools_dir\ncover_explorer\NCoverExplorer.Console.exe"
}
 
properties { # Files
	$solution_file = "$source_dir\$project_name.sln"
 
	$output_unitTests_dll = "$build_output_dir\$project_name.UnitTests.dll"
	$output_unitTests_xml = "$build_reports_dir\UnitTestResults.xml"
	$output_coverage_xml = "$build_reports_dir\NCover.xml"
	$output_coverage_log = "$build_reports_dir\NCover.log"
	$output_coverageExplorer_xml = "$build_reports_dir\NCoverExplorer.xml"
	$output_coverageExplorer_html = "$build_reports_dir\NCover.html"
}
 
properties { # Skip coverage attributes
	$skipCoverage_general = "Testing.SkipTestCoverageAttribute"
}
 
task default -depends unit_test_coverage
 
task clean {
	$transient_folders | ForEach-Object { delete_directory $_ }
	$transient_folders | ForEach-Object { create_directory $_ }
}
 
task compile -depends clean {
	exec { msbuild $solution_file /p:Configuration=$build_config /p:OutDir=""$build_output_dir\\"" /consoleloggerparameters:ErrorsOnly }
}
 
task unit_test_coverage -depends compile {
	exec { & $tools_coverage $tools_nunit $output_unitTests_dll /nologo /xml=$output_unitTests_xml //reg //ea $skipCoverage_general //l $output_coverage_log //x "$output_coverage_xml" //a $project_name }
	exec { & $tools_coverageExplorer $output_coverage_xml /xml:$output_coverageExplorer_xml /html:$output_coverageExplorer_html /project:$project_name /report:ModuleClassFunctionSummary /failMinimum }
}

As the second line alludes to, you can break functions out into separate files and include them back into the main one. Here’s functions_general.ps1:

function delete_directory($directory_name)
{
	Remove-Item -Force -Recurse $directory_name -ErrorAction SilentlyContinue
}
 
function create_directory($directory_name)
{
	New-Item $directory_name -ItemType Directory | Out-Null
}

This script will build our project and run the unit tests, producing a coverage report we can display later inside Team City. Much of this maps loosely one to one against the NAnt version discussed in my past series, and there’s plenty of articles/posts online explaining this stuff in much more detail than I can here. Note that all the pieces that can “fail” the script are wrapped in exec, which will execute the code block (i.e. lambda/anonymous delegate) and basically alert the build server if it fails. Not too difficult, at least for now 🙂

As for getting this to work with Team City, if you specify the runner as a command line and point it at a batch file with these contents:

@echo off
cls
powershell -Command "& { Set-ExecutionPolicy Unrestricted; Import-Module .\build\tools\psake\psake.psm1; $psake.use_exit_on_error = $true; Invoke-psake '.\build\build.ps1' %*; Remove-Module psake}"

You’ll be golden. This batch allows the build server to run the script (perhaps setting unrestricted execution isn’t the smartest from a security standpoint, but oh well), sets up the psake environment, tells psake to raise its warnings in a way that TeamCity can pick up on, executes your build script, and tears down the psake environment. Looks a little complicated, but it’s just a bunch of smaller commands strung together on one line, and you shouldn’t have to look at it again.

3 Responses

  1. Jason Lautzenheiser Says:

    Darrell, I see your using ncover. What version are you guys using? Did you purchase the latest? Or using the free community version?

    I’ve also been looking at the latest coverage tool dotCover from Jetbrains currently in EAP. Seems with resharper, team city, and I’ve been playing with Webstorm, I seem to be standardizing on Jetbrains tools.

  2. Darrell Mozingo Says:

    We’re using the last free community edition. It’s serving its purpose for the moment – telling us obvious spots in the code base that aren’t touched for whatever reason.

    I’m honestly not even sure what the latest commercial versions offer that’d be worth shelling out the cash. Seems like they have a marketing problem 🙂

  3. Taller Code » Blog Archive » Production deployment with your build script - Part 1 Says:

    […] a heads up, this will all be written in PowerShell. We’ve moved our build script setup to it and it’s what made this deployment scenario […]

Leave a Comment

Please note: Comment moderation is enabled and may delay your comment. There is no need to resubmit your comment.