Building a .net solution using RAKE [part 2]


As begone in the previous post lets look at the RAKE script.

Preparing the build

The first thing the script has to do is to initialize the file structure it is going to work with later on in the script and there are some global variables to set. And before you look at the code this is the biggest task in the build script.

task :prepare do |taks, args|
 require 'fileutils'
 require 'ftools'
 FileUtils.rm_rf Variables::OUTPUT_PATH if File.exist? Variables::OUTPUT_PATH
 Dir.mkdir(Variables::OUTPUT_PATH)
 Dir.mkdir(Variables::SOURCE_PATH)
 Dir.mkdir(Variables::WIKI_PATH)
 Dir.mkdir(@RELEASE_PATH = "#{Variables::OUTPUT_PATH}/#{Variables::PROJECT_NAME}_#{args["version"]}")
 Dir.mkdir(@DOCUMENTATION = @RELEASE_PATH + "/Documentation")
 Dir.mkdir(@VALY = @RELEASE_PATH + "/Valy")
 File.copy('nunit.xsl', "#{Variables::OUTPUT_PATH}/nunit.xsl")
end

Before we can begin our build and copy bonanza we have to prepare some directories into which we want to copy to. Just to avoid all those nasty exceptions later on.

The first to lines “import” some useful things that we require for our IO operations. The fileutils is a IO class that comes with rake and proves extremely useful. So we create a few directories and set the required variables for later use. The last thing we do is to copy the nunit.xsl to the output directory.

If you are wondering what those Variables are then let me disappoint you. There are just constants defined in another file so they do not clog up the main rake script.

module Variables
  ## Project paths
  OUTPUT_PATH = "../Release"
  SOURCE_PATH = "#{OUTPUT_PATH}/Code"
  LIB_PATH = "../Sources/Lib"
  WIKI_PATH = "#{OUTPUT_PATH}/Wiki"

  ## Third party tools
  NUNIT_EXE = "#{LIB_PATH}/NUnit/nunit-console.exe"
  DOT_NET_PATH = "#{ENV["SystemRoot"]}\\Microsoft.NET\\Framework\\v3.5"

  ## Build properties
  CONFIG = "Debug"
  PROJECT_NAME = "valy"
end

Getting the sources from the repository

As mentioned before valy is hosted with Google code in a mercurial repository. To make my life (and the scrip) easier I have tortoiseHG installed locally.

So let’s take a look at the script that gets my sources from the repository

desc "Gets the sources from the valy repository"
task :get_sources => [:prepare] do
   Helpers.clone_repository("https://valy.googlecode.com/hg", Variables::SOURCE_PATH)
   Helpers.clone_repository("https://wiki.valy.googlecode.com/hg/", Variables::WIKI_PATH)
end

To make it easier the Helper module defines a method clone_repository that looks quite simple.

# Clones the given mercurial repository
def Helpers.clone_repository(repository_path, output_path)
  puts "Cloning repository #{repository_path}"
  unless system "hg clone #{repository_path} #{output_path}"
    puts "Clone FAILED"
    puts $?
  end
end

I must say that I like this piece of code. It’s simple and effective. I take advantage of the fact that I can do a system call to hg and it will get the repository for me. I am using the system command for this because it returns a boolean and in the case of failure stores the error message in the $? global variable.

And that’s it. Now you have the sources on your local system.

Building the solution

This is actually represented in two tasks:

  1. Set the version number in the assemblyinfo.cs
  2. Build the solution using MsBuild

The two tasks again look “quite” easy.

task :set_assembly_attributes => [:get_sources] do |task, args|
  Helpers.set_assembly_version "#{Variables::SOURCE_PATH}/Sources/valy/CommonAssemblyInfo.cs", args["version"] if args["version"]
end

desc "Compile solutions using msBuild"
task :compile=> [:set_assembly_attributes] do
  solutions = FileList["#{Variables::SOURCE_PATH}/**/*.sln"]
  solutions.each do |solution|
   Helpers.build_solution(solution)
  end
end

Both tasks depend on the Helpers module that we will look at later. For now lets concentrate on the task at hand 🙂

The :set_assembly_attributes task is the only rake task (until now) that uses parameters represented by the args in the task block. So if the version is not given the default version will remain at 0.0.0.0. To illustrate lets look at some calls to the rake script:

c:\valy\build>rake version=1.0.0.1
=> dll version = 1.0.0.1

c:\valy\build>rake
=> dll version = 0.0.0.0

Before we look into how I set the assembly version let me state that I have seen all the libraries and custom task out there and found them to complicated form my purpose. So just 4 the fun of it I did it my way.

def Helpers.set_assembly_version assembly_info_file, version
  unless File.exist? assembly_info_file
    raise "File #{File.extend_path(assembly_info_file)}, not found";
  end
  buffer = ""
  buffer = File.open(assembly_info_file, 'r'){|f| f.read.gsub(/0.0.0.0/, version)}
  File.open(assembly_info_file, 'w'){|f| f.write buffer}
end

As is visible from the code above I use a simple regex replacement to change all occurrences of 0.0.0.0 to whatever I provided the script as the version. It is not perfect nor pretty. What I hate the most about it is the fact that I need the buffer, I would rather read and write in one single stroke. But this will do for now.

After seeing the clone_repository method the build_solution is no big surprise.

def Helpers.build_solution solution_path
  puts "Building solution : #{solution_path}"
  if system "#{DOT_NET_PATH}/msbuild.exe /noconlog /nologo /p:Configuration=#{CONFIG} #{solution_path} /t:Rebuild"
    puts "Build SUCCEEDED"
  else
    puts "Build FAILED"
    puts $?
  end
end

Looks familiar? Well it is. This is the first indication that I will have to refactor this in the future to make it “prettier”. But for now it works.

Running unit tests

This final tasks is not yet complete. After the tests are run the xml output is not transformed into HTML using the XSL file provided at the beginning 😦 But the rest of the task looks as following.

desc "Testing the build assemblies using NUnit"
task :test => [:compile, :prepare] do
  tests = FileList["#{Variables::SOURCE_PATH}/**/*tests.dll"].exclude(/obj\//)
  Helpers.run_tests tests, Variables::OUTPUT_PATH + "/TestReport.xml" unless tests.empty?
end

As mentioned before this is still work in progress…

Basically what happens is that I search for all Dlls which names end in tests and exclude the ones found in the obj folders.

Enter the Helper that actually runs the tests.

def Helpers.run_tests tests, report_file
  puts "Running tests"
  system "#{NUNIT_EXE} #{tests} /nologo /xml=#{report_file}"
end

Basically just runs the nunit console command with some parameters. So nothing special here. Yet.

And that is all I prepared for this post. Next time we will look at how I:

  • Get the test coverage
  • Harvest the build files
  • Package them into a zip file

thx for reading.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s