Ayende @ Rahien

It's a girl

On PSake

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.

Comments

Orb
08/30/2009 01:22 PM by
Orb

And there goes your ability to build on mono. Of course, if they'd fix xbuild so that it actully worked...

beefarino
08/30/2009 01:33 PM by
beefarino

@Orb:

There is a cross-platform powershell implementation based on the 1.0 release available:

igorshare.wordpress.com/.../pash-cross-platform...

I have not used it (caveat emptor); however it is reported to support the entire 1.0 language and syntax set. The items that are not supported are the windoze-specific items, like WMI.

John Teague
08/30/2009 03:20 PM by
John Teague

you can use ruby + rake without requiring Ruby to be installed. AllInOneRuby packages Ruby and your Gems into an executable. Ruby+Rake is about 3mb.

Jeremy B
08/30/2009 03:46 PM by
Jeremy B

Out of curiousity -- how many lines would that have been in C#?

Going through reworking our current build system at work, I keep wondering if it wouldn't be more straightforward to write the majority of the build in C# and have a very small script that built that project and ran it...

Added benefit is people don't have to grok a different language like Ruby, msbuild, nant, powershell to use it.

Ayende Rahien
08/30/2009 04:05 PM by
Ayende Rahien

@Orb,

Compiling on Mono is not a priority for me.

When it does, I'll take care of it.

@John,

Psake is actually smaller & easier, I find.

Ayende Rahien
08/30/2009 04:07 PM by
Ayende Rahien

Jeremy,

Probably not that much longer, but using the shell is much easier than a programming language. Most of the tools already have command line interface

Sean Scally
08/30/2009 05:55 PM by
Sean Scally

I think I still prefer the rake solution because it includes everything you need. When you need shell constructs, they're available in rake. When you need programming constructs, they're also there right in the buildfile, instead of having to use one language to sew up a patchwork quilt of shell scripts in a 2nd language and 3rd party tools in yet a 3rd language.

Mike Brown
08/30/2009 06:06 PM by
Mike Brown

I've been intrigued by powershell for some time now. From what I read ,the pipe structure is very powerful because you're piping .NET objects instead of just strings.

I'd imagine that the PSake tasks are defined as commandlets allowing you to define new tasks in C#.

Tobin Harris
08/30/2009 06:30 PM by
Tobin Harris

I'm currently having some success with IronRuby + Rake (click my link to see recent blog post about that).

I like the fact that you easily can write Rake tasks that manipulate your own .NET business or utility objects. The downside of IronRuby is the extra dependency and it's still a touch slow.

Stuart
08/30/2009 10:05 PM by
Stuart

@Ayende If/when compiling on Mono becomes a priority for you and you go to take care of it, what will you do? Do you reckon it will be easy enough to add Mono as a psake target, or do you expect you might have to change build systems?

(I have no interest in powershell, but after a cursory look at psake.ps1 it seems to me that adding Mono support might not be too difficult.)

Ayende Rahien
08/30/2009 11:56 PM by
Ayende Rahien

Sean,

PowerSheel is a programing language

Ayende Rahien
08/30/2009 11:57 PM by
Ayende Rahien

Mike,

I have absolutely no idea, it wasn't necessary so far

Ayende Rahien
08/30/2009 11:58 PM by
Ayende Rahien

Stuart,

No idea, I apply YAGNI here.

There is small enough infrastructure that I don't really worry about this

Peter
08/31/2009 03:01 AM by
Peter

I like psake and have used it, but I already had some "owie" moments where psake did something unexpected. The next time I need a build script, I'll just do raw PowerShell--the only thing I'm losing is the task dependency management, which isn't a loss to me--tasks can be reformulated as function calls (or maybe cmdlets, I haven't thought this through much). Instead of making tasks dependent on each other, you'd just call the function.

I haven't totally thought through this, but it seems like dependency management for tasks isn't necessary for build scripts.

Peter
08/31/2009 03:11 AM by
Peter

Also Sean, PowerShell is a great dynamic language, and you're only going to miss Ruby's OO-ness. If you're writing scripts, I'd say all the little touches done for PowerShell make it superior to Ruby.

As for the OO-ness, PowerShell has the ability in PowerShell V1 to add members and methods to an object at runtime via the add-member cmdlet, but it's verbose and all an object's members are public, so I acknowledge this isn't ideal. I've abused the add-member cmdlet here: www.pseale.com/.../...terScriptingGamesEvent5.aspx

PowerShell V2 lets you write C# classes inline in your PowerShell scripts that can be compiled and instantiated like any other .NET object. I haven't used this feature so I won't vouch for it.

Ayende Rahien
08/31/2009 07:39 AM by
Ayende Rahien

If I need to write OO code to handle my build, I already lost

Chris Bilson
08/31/2009 12:03 PM by
Chris Bilson

You can still build using the mono tools on windows, and guess what: mono can even use assemblies not built using the mono tools. Even if you cared about building Rhino Tools for mono, whether or not to use powershell in your build is the least of your concerns.

Any idea when/if you are going to put this in Rhino-Tools? I recently started using psake and would like to take a look at your psake_ext.ps1. How come you dot-sourced it and didn't just "include" it?

Chris Bilson
08/31/2009 12:20 PM by
Chris Bilson

You can just:

include psake_ext.ps1

But then you need to put all your functions in global:, which kind of sucks. See groups.google.com/.../7fc875b5dca3502b7

Not that there are any advantages to include, I guess, other than it seems a little more readable.

Lyndsay
08/31/2009 09:03 PM by
Lyndsay

What problem do you have building WPF apps with NAnt? I'm using VS2008, MSBuild and NAnt to build a WPF ClickOnce app quite successfully at the moment. Perhaps I'm missing something.

Ayende Rahien
08/31/2009 09:13 PM by
Ayende Rahien

Lyndsay,

You said it, you are using MSBuild & Nant to build it.

Ayende Rahien
09/01/2009 09:15 AM by
Ayende Rahien

Chris,

Just tried include, and it didn't work, I guess it isn't bringing it to the local scope or something like that.

Ayende Rahien
09/01/2009 12:07 PM by
Ayende Rahien

Okay, figured it out, it depends where you put it.

When I put it in a task, it didn't work

alberto
09/11/2009 12:03 PM by
alberto

Re Nant & MSBuild, you are also using psake and msbuild right now, I don't see a difference. It's now like you had to create another xml file to configure msbuild.

Comments have been closed on this topic.