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.