One thing that really tired me in the past with build scripts was repetition. I may have several apps each with their own build script and for each one I would have to specify to compile it, tokenise any configs for other environments, run the unit test projects and code coverage, generate the same directories etc… It is also dangerous as it allows scope for something to be missed or forgotten. A test project not included, or config not tokenised.

Doing this in NAnt meant quite large build scripts due to the verbosity of using xml. I decided to fix this. Firstly, I switched to using Ruby along with Rake tasks. This cut down the scripts (a lot), but didn’t get rid of the repetition. Ruby being a lot more powerful than a simple build app (NAnt) gives much more potential though to be clever with build scripts.


Here’s a few common scenarios I noticed with our build scripts:

  • Solution file– This always lived alongside the rakefile.rb for each app. And there was only ever one solution file
    • CONVENTION – the rakefile.rb searches the directory it’s located in for the solution file. From the solution file I can get the name of the solution, and also use the path to the solution file for compilation. This means we don’t need to specify the solution to build nor its name when reporting.
  • Test projects– We normally list these in the build script, despite them living, and being detailed in the solution file! All of our test projects end in “.UnitTests”.
    • CONVENTION – Use Ruby to parse the solution file (the path of which we already know). Find all projects ending in “.UnitTests.csproj”. Convert the path to the “.csproj” file to a dll path (ie src/MyProj.csproj => src/bin/#{BuildType}/MyProj.dll. We can then just loop through each one found and pass it to a test runner.
  • Configs– Having to specify each config to tokenise is a pain to maintain and remember to do. We have a Web.master.config, Web.tokens.config which together output a Web.config (this can apply to any “.config” type).
    • CONVENTION – Use ruby to search the “src” directory for all “*.tokens.config”. When finding a file we can figure out the name of the .master and .config file to generate. This means that any configs automatically get tokenised.
  • Css/JavaScript Validation & Compression– We currently use Juicer for this, but have to specify each file to be “juiced”.
    • CONVENTION – place all of the css/js files that juicer will run in one directory, or add a pattern to them so that Ruby can search each folder and pattern match any files we want to juice.
  • Compiling– Most projects just use a simple MsBuild command line to compile. Some however may do a publish, or manifest signing etc… The majority of the MsBuild line though is common between all apps.
    • CONVENTION – Set up a common MsBuild rake task that also uses a Global variable for any build arguments. If a solution needs to compile with specific arguments, they can be specified at the top of the rakefile.rb using the Global variable.
  • Project Types– We often have to specify different actions for each project. For example a web project needs css/js compression to take place.
    • CONVENTION – All of this information is stored inside the solution file. If a project is a WebApp for example, we can match it via the project type Guid. This way we can dynamically apply css compression to a Web application project rather than needing to specify it.

I won’t go into any more conventions, but you should be able to get the idea. It gets harder to apply more around packing up a build, or deploying it, but it can be done.

So this leaves me with a set of common scripts and tasks, all tested of course using RSpec. It means all our projects will follow a similar layout and execution, which is a good thing. If you know one project, switching to another shouldn’t be a problem. And best of all it means our build scripts per project now look like:

require ‘src/build/common’

task :default => [:compression, :configs, :compile, :tests]

A matter of no more than 10 lines per project! There’s very little implementation, I mainly have to specify the common tasks I want to run. We can of course add in any specific Rake tasks should we need to. Pre and post build tasks, tasks for generating things etc… which will bloat them out a bit.

It should be possible though, using the common tasks, to drop a rakefile.rb into a directory and it will know how to build the whole solution. Responsibility for finding the .sln file, compiling it, tokenising any configs, compressing css etc… is all passed from the developer to the build scripts. All the developer has to do is make sure they follow the conventions that have been set out. If they don’t it won’t build, which is far better than it building and an entry had been forgotten for something.


Having been programming in Ruby and Rake for some time now in our build environment, my first piece of advice would be if you’re using NAnt, start to look into how you could switch away from it. I know it does the job, but it does so with a lot more lines, repetition, and no testing coverage.

By switching to Ruby, it has reduced the amount of build code we have, our build has become more intelligent and dynamic, and less responsibility is placed on the developer to remember to add every entry required. And best of all I’ve got dozens of tests covering all of the build scenarios, so should I need to ammend or change a convention I can be sure I (most likely) won’t break anything.

Request a Demo

© 2006 - 2021. All Rights Reserved.