James Kovacks introduced psake ( a power shell based build system )over a year ago, and at the time, I gave it a glance and decided that it was interesting, but not worth further investigation.
This weekend, as I was restructuring my Rhino Tools project, I realized that I need to touch the build system as well. The Rhino Tools build system has been through several projects, and was originally ported from Hibernate. It is NAnt based, complex, and can do just about everything that you want expect be easily understandable.
It became clear to me very quickly that it ain’t going to be easy to change the way it works, nor would it be easy to modify that to reflect the new structure. There are other issues with complex build systems, they tend to create zones of “there be dragons”, where only the initiated go, and even they go with trepidation. I decided to take advantage of the changes that I am already making to get a simpler build system.
I had a couple of options open to me: Rake and Bake.
Bake seemed natural, until I remember that no one touched it in a year or two. Beside, I can only stretch NIH so far :-). And while I know that people rave about rake, I did not want to introduce a Ruby dependency on my build system. I know that it was an annoyance when I had to build Fluent NHibernate.
One thing that I knew that I am not willing to go back to was editing XML, so I started looking at other build systems, ending up running into PSake.
There are a few interesting things that reading about it brought to mind. First, NAnt doesn’t cut it anymore. It can’t build WPF applications nor handle multi targeting well. Second, I am already managing the compilation part of the build using MSBuild, thanks to Visual Studio.
That leave the build system with executing msbuild, setting up directories, executing tests, running post build tools, etc.
PSake handles those well, since the execution environment is the command line. The syntax is nice, just enough to specify tasks and dependencies, but everything else is just pure command line. The following is Rhino Mocks build script, using PSake:
properties {
$base_dir = resolve-path .
$lib_dir = "$base_dir\SharedLibs"
$build_dir = "$base_dir\build"
$buildartifacts_dir = "$build_dir\"
$sln_file = "$base_dir\Rhino.Mocks-vs2008.sln"
$version = "3.6.0.0"
$tools_dir = "$base_dir\Tools"
$release_dir = "$base_dir\Release"
}
task default -depends Release
task Clean {
remove-item -force -recurse $buildartifacts_dir -ErrorAction SilentlyContinue
remove-item -force -recurse $release_dir -ErrorAction SilentlyContinue
}
task Init -depends Clean {
. .\psake_ext.ps1
Generate-Assembly-Info `
-file "$base_dir\Rhino.Mocks\Properties\AssemblyInfo.cs" `
-title "Rhino Mocks $version" `
-description "Mocking Framework for .NET" `
-company "Hibernating Rhinos" `
-product "Rhino Mocks $version" `
-version $version `
-copyright "Hibernating Rhinos & Ayende Rahien 2004 - 2009"
Generate-Assembly-Info `
-file "$base_dir\Rhino.Mocks.Tests\Properties\AssemblyInfo.cs" `
-title "Rhino Mocks Tests $version" `
-description "Mocking Framework for .NET" `
-company "Hibernating Rhinos" `
-product "Rhino Mocks Tests $version" `
-version $version `
-clsCompliant "false" `
-copyright "Hibernating Rhinos & Ayende Rahien 2004 - 2009"
Generate-Assembly-Info `
-file "$base_dir\Rhino.Mocks.Tests.Model\Properties\AssemblyInfo.cs" `
-title "Rhino Mocks Tests Model $version" `
-description "Mocking Framework for .NET" `
-company "Hibernating Rhinos" `
-product "Rhino Mocks Tests Model $version" `
-version $version `
-clsCompliant "false" `
-copyright "Hibernating Rhinos & Ayende Rahien 2004 - 2009"
new-item $release_dir -itemType directory
new-item $buildartifacts_dir -itemType directory
cp $tools_dir\MbUnit\*.* $build_dir
}
task Compile -depends Init {
exec msbuild "/p:OutDir=""$buildartifacts_dir "" $sln_file"
}
task Test -depends Compile {
$old = pwd
cd $build_dir
exec ".\MbUnit.Cons.exe" "$build_dir\Rhino.Mocks.Tests.dll"
cd $old
}
task Merge {
$old = pwd
cd $build_dir
Remove-Item Rhino.Mocks.Partial.dll -ErrorAction SilentlyContinue
Rename-Item $build_dir\Rhino.Mocks.dll Rhino.Mocks.Partial.dll
& $tools_dir\ILMerge.exe Rhino.Mocks.Partial.dll `
Castle.DynamicProxy2.dll `
Castle.Core.dll `
/out:Rhino.Mocks.dll `
/t:library `
"/keyfile:$base_dir\ayende-open-source.snk" `
"/internalize:$base_dir\ilmerge.exclude"
if ($lastExitCode -ne 0) {
throw "Error: Failed to merge assemblies!"
}
cd $old
}
task Release -depends Test, Merge {
& $tools_dir\zip.exe -9 -A -j `
$release_dir\Rhino.Mocks.zip `
$build_dir\Rhino.Mocks.dll `
$build_dir\Rhino.Mocks.xml `
license.txt `
acknowledgements.txt
if ($lastExitCode -ne 0) {
throw "Error: Failed to execute ZIP command"
}
}
It is about 50 lines, all told, with a lot of spaces and is quite readable.
This handles the same tasks as the old set of scripts did, and it does this without undue complexity. I like it.