Chained Builds in CruiseControl.rb (Update)

Last Thursday I wrote about the kind of chained builds functionality that I want from CruiseControl.rb. I think I’ve got it hacked in, at least to the “good enough” stage.

The first big change was to change the @trigger instance variable for the Project class from a single trigger to an array of triggers. I modified its name to @triggers to reflect this change. So in the initialize method for the Project class, instead of saying:

@trigger = ChangeInSourceControlTrigger.new
We now say this:
@triggers = [ChangeInSourceControlTrigger.new]
Next, I modified the buildifnecessary method to collect information from all of the triggers about which revisions need to be built.
revisions = []
@triggers.each { |trigger| revisions += trigger.getrevisionstobuild(self) }
revisions = revisions.uniq.sort
if revisions.empty?
  ...
I also made a change to the triggeredby method for the Project class so that it accepts multiple triggers, and so that it always adds triggers to the existing set (instead of replacing them). Here’s what my modified version of triggeredby looks like.
def triggeredby(*things)
  things.each do |thing|
    if thing.is_a?(String) || thing.is_a?(Symbol)
      @triggers << SuccessfulBuildTrigger.new(thing)
    else
      @triggers << thing
  end
end
This allows me to declare dependencies in my cruise_config.rb file like so:
project.triggered_by 'projectA', 'projectB', 'projectC'
I had to modify the logic get_revisions_to_build method for the SuccessfulBuildTrigger class a little bit, so that it wouldn’t return any build numbers from triggering projects that were older than the latest build of the “triggered” project. Here’s what that block looks like now.
if last_successful_build.nil? || project.find_build(last_successful_build.label)
  []
elsif project.last_build && project.last_build.label.to_i >= last_successful_build.label.to_i
  []
else
  [Revision.new[last_successful_build.label]
end
The last step was to add the spaceship operator to the Revision class, so that I can sort arrays of them.
def <=>(other)
  number.to_i <=> other.number.to_i
end
Note that the number attribute for the Revision class sometimes refer to an integer, and other times to a string. I suspect that this is a bug, but calling to_i on it does the trick for this purpose.

It took a bit of experimenting to get it right, but this seems to be working properly now. It took maybe a couple of hours to get it working, and the code was easy to read and understand.

Posted July 23rd, 2007 in Ruby.

2 comments:

  1. Rolf:

    Hi Lyle,

    We’ve added chained builds to the head version of cc.rb. We haven’t gotten it out the door as a release yet, but if you take the latest from svn…

    In your cruisecontrol.rb file you specify:
    project.triggered_by ‘My Project-Fast’

    –Rolf

  2. Lyle:

    Rolf, I already know about the current support for chained builds in CC.rb. I wrote about it previously and identified some of its shortcomings (from my point of view). This article describes how I hacked in a fix to get it to do what I want.