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
And there goes your ability to build on mono. Of course, if they'd fix xbuild so that it actully worked...
@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.
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.
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.
@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.
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
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.
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#.
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.
@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.)
Sean,
PowerSheel is a programing language
Mike,
I have absolutely no idea, it wasn't necessary so far
Stuart,
No idea, I apply YAGNI here.
There is small enough infrastructure that I don't really worry about this
Wow - that looks really nice.
That looks like it would be very easy to maintain/extend.
Enjoy!
Jeffrey Snover [MSFT]
Distinguished Engineer
Visit the Windows PowerShell Team blog at:
http://blogs.msdn.com/PowerShell
Visit the Windows PowerShell ScriptCenter at:
www.microsoft.com/.../msh.mspx
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.
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.
If I need to write OO code to handle my build, I already lost
Hi Ayende, may you found this post also interesting:
www.lanwin.de/.../a-powershell-make-clone-poshmake
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,
I don't know how to include files :-)
And my ext file is really lousy:
github.com/.../psake_ext.ps1
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.
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.
Lyndsay,
You said it, you are using MSBuild & Nant to build it.
Chris,
Just tried include, and it didn't work, I guess it isn't bringing it to the local scope or something like that.
Maybe you have an older version of psake? This works with the latest psake: http://gist.github.com/179067
Okay, figured it out, it depends where you put it.
When I put it in a task, it didn't work
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.
Comment preview