A Basic Rakefile

Jul 30, 2013 at 19:00

I don’t like spending too much time setting up a new automated build, and rakefiles are an effective way to capture a lot of the boring repetition. Also, in the spirit of “don’t repeat yourself”, I have a rakefile that that I have been gradually building over the past few months. For my purposes, it now just requires a single edit to use in a new project.

It is now at the point where, if you stick with the fairly standard Visual Studio project layout (this is the only implicit hidden logic in the script), all I have to do each time is change the name of the solution I want to build, and drop the solution at the top of my folder tree.

The file can be downloaded from here.

The rakefile provides:

  • nuget package restore via rake setup
  • nuget self updating via rake update_nuget
  • automatic test location, and running via xunit (end your test names as .Tests.dll)
  • debug and release builds.
  • adding the git hash to assemblies.

The task list:

rake all                # Clean, rebuild and test entire build
rake build              # Build everything
rake clean              # Clean entire build
rake debug:build        # Build
rake debug:clean        # Clean
rake debug:tests        # Run tests
rake default            # Default is to build everything
rake release:build      # Build
rake release:clean      # Clean
rake release:tests      # Run tests
rake restore_packages   # Restore nuget packages
rake restore_xunit      # Restore xunit from nuget
rake setup              # Set up everything if checked out for the first time
rake tests              # Run all tests
rake update_nuget       # Update nuget
rake version            # Add git hash to assemblies

Currently, I am not a big fan of dependencies, especially since in this case, rake setup will go to nuget.org repeateedly, I do not need it to run every time I type rake tests.

On the other hand, tests require builds, so that is added as a task dependency.

I have chosen to use namespaces to nest the builds, with the build type as the outer namespace, rather than the reverse, i.e. rake debug:build rather than rake build:debug. This tends to lead to a more discoverable hierarchy of tasks.

In order to use it, change:

SOLUTION=File.join(ROOT, 'MY_SOLUTION_HERE.sln' )

and then:

rake setup
rake all

and you’re done. In fact these are the only two steps you need to add into your automated build system, and being able to repeat your automated build on your developer PC is “A Good Thing”.

Rake and albacore tasks are fairly boilerplate, so the resulting rakefile is extremely simple. The file in all its glory:

#  Call
#
#    rake setup
#
#  to restore nuget packages, and grab xunit

require 'albacore'
require 'find'

################################
#  Helper methods
################################

#  Find any tests that end with '.tests.dll' for the given build type
#  assuming that is built into bin/<build_type>
def find_tests(root, build_type)
    paths = []
    re = Regexp.new(".*/bin/#{build_type}.*Tests\.dll$", Regexp::IGNORECASE)
    Find.find(root) do |path|
        paths << path if path =~ re
    end
    return paths
end

################################
#  Variables/configuration
################################

#  Define the root relative to this file.
ROOT=File.expand_path('.', File.dirname(__FILE__))

PACKAGES_DIR=File.join(ROOT, 'packages')

#  What we're building
SOLUTION=File.join(ROOT, 'MY_SOLUTION_HERE.sln' )

#  Nuget binary
NUGET_EXE = File.join(ROOT, '.nuget/nuget.exe')

#  Nuget args to get XUnit
NUGET_XUNIT_PARAMS = "install xunit.runners -Version 1.9.1 -OutputDirectory " + PACKAGES_DIR

#  Path to xunit (gets installed by nuget)
XUNIT_EXE = File.join(PACKAGES_DIR, "xunit.runners.1.9.1/tools/xunit.console.clr4.x86.exe")

#  Path to xunit tests - auto discovered by convention
XUNIT_DEBUG_TESTS = find_tests(ROOT, 'Debug')
XUNIT_RELEASE_TESTS = find_tests(ROOT, 'Release')

#  Multi-cpu compile, no logo, low verbosity
COMPILER_SWITCHES = {
    'M' => true,
    'verbosity' => 'quiet',
    'nologo' => true
}

################################
#  Common top-most tasks.
################################


#  Relative to this file
desc 'Restore nuget packages'
task :restore_packages do
    FileList["**/packages.config"].each do |filepath|
        sh "#{NUGET_EXE} install #{filepath} -OutputDirectory #{PACKAGES_DIR}"
    end
end


desc "Restore xunit from nuget"
exec :restore_xunit do |cmd|
    cmd.command = NUGET_EXE
    cmd.parameters = NUGET_XUNIT_PARAMS
end

desc "Update nuget"
exec :update_nuget do |cmd|
    cmd.command = NUGET_EXE
    cmd.parameters = "update -Self"
end


desc 'Set up everything if checked out for the first time'
task :setup => [:restore_xunit , :restore_packages]


desc 'Clean entire build'
task :clean => ["release:clean", "debug:clean"]


desc 'Run all tests'
task :tests => ["debug:tests", "release:tests"]


desc 'Build everything'
task :build => ["debug:build", "release:build"]


desc 'Clean, rebuild and test entire build'
task :all => [:clean, :build, :tests]


desc 'Add git hash to assemblies'
task :version do |t|
    clean_clone = `git diff --shortstat #{ROOT}`.empty?

    if clean_clone
        abbrev_commit = `git log -1 --pretty="%h" #{ROOT}`.delete("\n")
        assemblies = []
        Find.find('.') do |path|
            assemblies << File.expand_path(path) if path =~ /AssemblyInfo\.cs$/
        end

        assemblies.each do |assembly|
            text = File.read(assembly)
            File.open(assembly, 'w') { |f|  f.write(text.gsub(/development\-version/, abbrev_commit)) }
        end
    else
        puts 'You have working copy changes'
    end
end


desc 'Default is to build everything'
task :default => :all


################################
#  Debug tasks
################################


namespace 'debug' do
    desc 'Build'
    msbuild :build do |msb|
        msb.properties = { :configuration => :Debug }
        msb.targets = [ :Build ]
        msb.other_switches = COMPILER_SWITCHES
        msb.solution = SOLUTION
    end

    desc 'Clean'
    msbuild :clean do |msb|
        msb.properties = { :configuration => :Debug }
        msb.targets = [ :Clean ]
        msb.other_switches = COMPILER_SWITCHES
        msb.solution = SOLUTION
    end

    desc 'Run tests'
    xunit :tests => "debug:build" do |xunit|
        xunit.command = XUNIT_EXE
        xunit.assemblies = XUNIT_DEBUG_TESTS
    end
end


################################
#  Release tasks
################################


namespace 'release' do
    desc 'Build'
    msbuild :build do |msb|
        msb.properties = { :configuration => :Release, :platform => 'Any CPU' }
        msb.targets = [ :Build ]
        msb.other_switches = COMPILER_SWITCHES
        msb.solution = SOLUTION
    end

    desc 'Clean'
    msbuild :clean do |msb|
        msb.properties = { :configuration => :Release, :platform => 'Any CPU' }
        msb.targets = [ :Clean ]
        msb.other_switches = COMPILER_SWITCHES
        msb.solution = SOLUTION
    end

    desc 'Run tests'
    xunit :tests => "release:build" do |xunit|
        xunit.command = XUNIT_EXE
        xunit.assemblies = XUNIT_RELEASE_TESTS
    end
end

If you have never used rakefiles before, simply download the latest version of Ruby, followed by gem install albacore in a command prompt, and that is it.