Tuesday, 9 October 2007

Using rake to manage your software project

Do you have some of those projects where you have to be sure that you jump the same loops every time you edit some code? Take a look at the bio-graphics code. Every time I change anything in the code, I have to do the following things:

  1. regenerate the RDoc documentation
  2. regenerate the ruby gem
  3. check SVN status
  4. do an SVN update
  5. perform the SVN commit
  6. upload the new documentation to the website
That's a prime candidate for rake. Rake does the same as GNU make, which is dependency-based programming. The major advantage for us over GNU make is of course that it uses ruby syntax. With dependency-based programming, I mean that some tasks rely on other ones. GNU make is best know for managing the compilation of source files. But you can do other stuff with it as well: if I want to commit to SVN, I want to make sure that the latest RDoc has been generated as well as a new gem. Therefore, you can have the 'SVN commit' task depend on the 'generate RDoc' and 'generate gem' tasks. And the task 'generate RDoc' will depend on the freshness of the actual library files.

How's this work? You basically create a file containing tasks and tell rake to execute one or more of them, the Rakefile. There are several good tutorials on rake, like the one from Martin Fowler and from the Rails Envy guys. I'm not going into the nitty-gritty of how they're written. These tutorials are much better at that. What I will do here, is describe the Rakefile I use for Bio::Graphics. (Someone already asked in the comments on my post on using ActiveRecord outside of rails what the Rakefile was that I used. Actually, the one mentioned in that post was empty and just a place holder.)

Without further ado, here it is:

#
# Rakefile.rb
#
# Copyright (C):: Jan Aerts
# License:: The Ruby License
#
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'

task :default => :svn_commit

file_list = Dir.glob("lib/**/*.rb")

desc "Create RDoc documentation"
file 'doc/index.html' => file_list do
puts "######## Creating RDoc documentation"
system "rdoc --title 'Bio::Graphics documentation' -m TUTORIAL TUTORIAL README.DEV lib/"
end

desc "An alias for creating the RDoc documentation"
task :rdoc do
Rake::Task['doc/index.html'].invoke
end

desc "Create a new gem"
file 'bio-graphics-1.0.gem' => file_list do
puts "######## Creating new gem"
system "gem build bio-graphics.gemspec"
end

desc "An alias for creating the gem"
task :create_gem do
Rake::Task['bio-graphics-1.0.gem'].invoke
end

desc "Check SVN status"
task :check_svn_status do
puts "######## Checking SVN status"
message = String.new
message << "# SVN status requires manual intervention\n"
message << "# For items with '?': either svn add or svn propedit svn:ignore\n"
message << "# For items with '~': don't know yet\n"
message << "# Please see http://svnbook.red-bean.com/en/1.4/svn-book.html#svn.ref.svn.c.status"

output = `svn status`
puts output

allowed_status = ['A','D','M','R','X','I'] # See http://svnbook.red-bean.com/en/1.4/svn-book.html#svn.ref.svn.c.status

output.each do |line|
status = line.slice(0,1)
if ! allowed_status.include?(status)
raise message
end
end
end

desc "Check if SVN updates available"
task :check_svn_update do
puts "######## Checking SVN update"
output = `svn update`
puts output
if output !~ /^At revision [0-9]/
raise "Please update your working copy first"
end
end

desc "Commit to SVN repository"
task :svn_commit => [:check_svn_update, :check_svn_status, :create_gem, :rdoc] do
puts "######## Doing SVN commit"
system 'svn commit'
end


rake -T lists all available tasks:
rake bio-graphics-1.0.gem  # Create a new gem
rake check_svn_status # Check SVN status
rake check_svn_update # Check if SVN updates available
rake create_gem # An alias for creating the gem
rake doc/index.html # Create RDoc documentation
rake rdoc # An alias for creating the RDoc documentation
rake svn_commit # Commit to SVN repository
The file_list at the top contains all files in the library itself, and will be used to check all timestamps. The file 'doc/index.html' task looks at the timestamp of the index.html file and if it's older than any of the files in file_list, it will regenerate the documentation. If it's newer, nothing happens. Same goes for bio-graphics-1.0.gem.

The check_svn_update and check_svn_status tasks just check if subversion needs some manual intervention before being able to commit. This should be able to catch conflicts in the working copy and the repository, or files that you forgot to add the SVN.

Note: why didn't I use the special Rake::RDocTask instead of the one I use here? Because the built-in RDoc task first removes your whole doc directory, also deleting the subversion metadata contained in it.

2 comments:

Mike said...

That's a great post, I love Ruby and I love automated build scripts. They especially go well together with unit testing. You can change things and run the build script and see if anythings broken, and so can anyone else who wants to tinker with your code.

Jan Aerts said...

You're completely right. I forgot about the unit testing because this happens to be the one library where I don't have them... (due to the nature of the creature: creating PNGs; which can't be tested).

Post a Comment