Thursday, July 06, 2006

Ruby Continuous Integration

My new project is starting up and we needed to make a decision on what type of Continuous Integration we were going to use. On the previous project we used Bitten, but it constantly caused us problems. We were going to use DamageControl, but it doesn't quite appear to be ready for use. We even tried using the Continuous Builder plugin, but it causes Subversion to hang while it runs the build.

So, what's left? Rolling your own, of course. I'm not a big fan of re-inventing the wheel, but it seemed like my other options were still prototype or busted wheels.

We chose a fairly simple solution. We are using Subversion, so we added the following post-commit hook:
sudo -H -u builder /bin/bash --login -c "cd /builds/commits && /usr/bin/touch $2"
This creates a file from the revision number passed as an argument to post-commit.

Then, in the /builds folder we have builder.rb
require File.expand_path(File.dirname(__FILE__) + "/builder_config")
require 'fileutils'
require 'rubygems'
require 'ruby-growl'

loop do
unbuilt = Dir["#{BuilderConfig.commits_folder}/*"].collect { |path| File.basename(path).to_i }.sort
exit if unbuilt.size == 0
latest_revision_number = unbuilt.pop
FileUtils.rm "#{BuilderConfig.commits_folder}/#{latest_revision_number}" "#{BuilderConfig.source_folder}"
system "svn update -r#{latest_revision_number}"
FileUtils.mkdir_p "#{BuilderConfig.output_folder}/#{latest_revision_number}"
status = system("rake > #{BuilderConfig.output_folder}/#{latest_revision_number}/rake-output.log") ? :success : :failure
system "svn log -r#{latest_revision_number} -v > #{BuilderConfig.output_folder}/#{latest_revision_number}/svn-log.log"
system "touch #{BuilderConfig.output_folder}/#{latest_revision_number}/#{status.to_s}"
['',''].each do |ip|
devbox = ip, 'Builder', ['success', 'failure'], nil, 'grrbabygrr'
devbox.notify status.to_s, "Build #{latest_revision_number} #{status.to_s}", ""
Builder.rb loops through the commits folder pulling all files that are in commits. When it finds a file it removes it and stores the latest_revision_number. It then updates the code to that revision, creates a directory for that revision in the output, runs rake and saves the output to the revision folder, saves the svn info to the revision output folder, saves a file called success or failure, and sends a Growl message to the pairing stations. We use cron to run builder.rb every minute. It's very low tech, but it seems to work well for us.

For displaying build results we created a rails project that's quite simple. All we needed was one controller to pull the file info from the /builds dir, one view to display the info, and one model to logically group data from each build.

Like I said, I'm not a big fan of rolling your own solutions, but it's nice when you are using tools that let you easily come up with a solution.
Post a Comment