Friday, September 16, 2011

Continuous Delivery with psake and TeamCity - Reusing the Local Build to Create a CI Build

In my previous two posts I have shown how you can compile your solution and run your tests with psake, effectively creating a simple local build. Building upon my last two posts, this time we will reuse our local build script to create a CI build with TeamCity 6.5. Assuming that you have already installed TeamCity, create a new build configuration called CI and setup your VCS. I’ve set it up with Git:

image

Add a Powershell build step:

image

Choose “Source code” and paste in this:

& .\tools\psake\psake .\build\build.ps1

It is also important to add

-NoProfile -ExecutionPolicy unrestricted

as “Additional command line parameters” because execution of PowerShell scripts are disabled by default. Now make sure that you have set up “Build Triggering” in TeamCity to “VCS Trigger”:

image

If we now run our build we should get a green “Success”.

image

Since we have executed the tests as a part of the build script and not through TeamCity we are missing a test report. We can easily report the test results to TeamCity by using a Service Message. Add the following to
the exec function of the test task:

Write-Output "##teamcity[importData type='nunit' path=`'$test_dir\tests_results.xml`']"

If the path to the test report is very long PowerShell will automatically wrap the service message and the message will not be picked up. To alleviate this problem we can increase the PowerShell UI buffer size. Replace the “"Script source” in the Powershell build step in TeamCity with this:

&  {$host.UI.RawUI.BufferSize = new-object System.Management.Automation.Host.Size(512,50); .\tools\psake\psake .\build\build.ps1}

Check in and watch the test being reported to TeamCity!

image

At this point you should consider creating a ci task that depends on compile and test. You probably want to add other tasks which ci is dependent on, for instance swapping out the config file for your test project, migrating a database, deploying, etc., etc. Just add the ci task as an argument to build.ps1 in TeamCity:

&  {$host.UI.RawUI.BufferSize = new-object System.Management.Automation.Host.Size(512,50); .\tools\psake\psake .\build\build.ps1 ci}

That’s all for now!

Download the code from GitHub.

Friday, September 2, 2011

Creating a Local Build with psake, Part 2: Testing

In my last post Creating a Local Build with psake, Part 1: Compiling I showed how you could compile a solution with psake. This time we will expand our build script and include a task that will run all the tests in our solution using NUnit 2.5.10.

The output from building is often referred to as build artifacts. I like to configure the output of the projects in my solution so that they output to a folder called “build_artifacts”. In my example solution I have two projects. An WPF application and a test class library:

image

I have configured them so that the output from building with Debug is output to the “build_artifacts”-directory:

image

image

Now that the compilation outputs to our build artifacts folder we can make a psake task that finds all the test assemblies in “build_artifacts\Debug\Tests” and runs them with NUnit:

properties {
    $base_dir = resolve-path .\..
    $source_dir = "$base_dir\src"
    $build_artifacts_dir = "$base_dir\build_artifacts"
    $tools_dir = "$base_dir\tools"
    $config = "Debug"
    $test_dir = "$build_artifacts_dir\$config\Tests"
}

task test {    
    $testassemblies = get-childitem $test_dir -recurse -include *tests*.dll
    exec { 
        & $tools_dir\NUnit-2.5.10\nunit-console-x86.exe $testassemblies /nologo /nodots /xml=$test_dir\tests_results.xml; 
    }
}

Here I have defined some extra properties to make the script more readable and maintainable. We now have to remember to make the “local” task dependent on “test”. Let’s also add a “clean” task that deletes the build artifacts folder and recreates it:

task clean {
    rd $build_artifacts_dir -recurse -force  -ErrorAction SilentlyContinue | out-null
    mkdir $build_artifacts_dir  -ErrorAction SilentlyContinue  | out-null
}

Our build script now loos like this:

$framework = '4.0'

properties {
    $base_dir = resolve-path .\..
    $source_dir = "$base_dir\src"
    $build_artifacts_dir = "$base_dir\build_artifacts"
    $tools_dir = "$base_dir\tools"
    $config = "Debug"
    $test_dir = "$build_artifacts_dir\$config\Tests"
}

task default -depends local
 
task local -depends compile, test

task compile -depends clean {
    exec { msbuild  $source_dir\ContinuousDelivery.sln /t:Clean /t:Build /p:Configuration=$config /v:q /nologo }
}

task clean {
    rd $build_artifacts_dir -recurse -force  -ErrorAction SilentlyContinue | out-null
    mkdir $build_artifacts_dir  -ErrorAction SilentlyContinue  | out-null
}

task test {    
    $testassemblies = get-childitem $test_dir -recurse -include *tests*.dll
    exec { 
        & $tools_dir\NUnit-2.5.10\nunit-console-x86.exe $testassemblies /nologo /nodots /xml=$test_dir\tests_results.xml; 
    }
}

If we run build.bat we now get the following output:

image

We have now successfully run our tests with psake! This concludes how you can create a local build with psake. There might more to this than what I’ve shown, but I think this should be enough to get you going.

You can download the source code from GitHub.

Creating a Local Build With psake, Part 1: Compiling

Often there is more to building a solution than just compiling with Visual Studio. If you’re strict about your Continuous Integration process you’d probably want to make sure that the solution compiles, migration of the database doesn’t fail (if you have one), all the tests are green etc before you check in. For this reason I like to create a local build, something very similar to the build running on a CI server. My preferred choice for build automation is psake, a tool written in PowerShell. “It avoids the angle-bracket tax associated with executable XML by leveraging the PowerShell syntax in your build scripts.” Since PowerShell is a programming language in itself you can pretty much do everything you would possibly like, including integration with the .NET Framework.

Now, let’s compile our solution with psake.

Assuming that I have the following folder structure.

image

Download psake from https://github.com/psake/psake/zipball/master and put the content (just the files) in \tools\psake\ . There is a bug in the current version of psake where it’s not possible to set framework version through $framework global variable in the build script. You therefore have to edit \tools\psake\psake.ps1 and set line 13 to:

[string]$framework = '4.0'

if you have a .NET 4.0 solution.

Create a file called build.ps1 in the build folder and include this in the file:

$framework = '4.0'

properties {
    $base_dir = resolve-path .\..
    $source_dir = "$base_dir\src"
    $config = "Debug"
}

task default -depends local
 
task local -depends compile

task compile {
    exec { msbuild  $source_dir\ContinuousDelivery.sln /t:Clean /t:Build /p:Configuration=$config /v:q /nologo }
}

With psake you can set up tasks with dependencies between them. If you run this with psake without specifying a task it will run the “default” task. As you can see I have created a task “local” in which “default” is dependent on. “local” is then dependent on the task “compile” where we call out to msbuild to actually do the compilation. The call is wrapped in an exec-function defined by psake. If the command line program we call out to fails, psake will automatically throw an exception and fail the build.

To make it easier to run the build script I like to wrap the invocation in a simple bat-file called build.bat at the root of the project:

@echo off
powershell.exe -NoProfile -ExecutionPolicy unrestricted -Command "& { .\tools\psake\psake .\build\build.ps1 %*; if ($lastexitcode -ne 0) {write-host "ERROR: $lastexitcode" -fore RED; exit $lastexitcode} }" 
pause

Now, open a command line and navigate to your project directory and run build.bat:

image

Congratulations! You’ve compiled your solution with psake.

You can download the source code from GitHub.