Ant Power, Cruise under Control and Selenium Fuel (part 3) - Projects that depends on others artifacts.

One thing I had to do when configuring CruiseControl for the first time was manage the dependencies between projects. It happens that my application project was a EAR of three other projects (see the diagram below).

Each of the modules (A,B and C) has a development tree and the JARs and WARs are assembled into the final EAR. For this reason I needed a way to identify the latest artifact of each project so it can be used to assemble the EAR.

Another thing that I had to manage is control the dependencies in the build loop, as you can see Module C depends on Module B, and Module B depends on Module A. If someone changes the Module A all other modules should be recompiled and assembled to ensure that the build is still possible. Now let's see one solution for this scenario.

Projects that depends on others artifacts

First let's look into Modules inter dependencies. CruiseControl has a option called veto and is explained in this wiki entry. I took some time to understand how it work but it worked well.

The key to understand the veto parameter is that instead of the "Module C depends on Module B" you have to think "Module B inhibits the build of Module C and Application EAR", in my case "Module A inhibits the build of Module B, Module C and Application EAR".

Another thing to note is the buildstatus parameter, which tell CruiseControl what triggers a build. In my case "Application EAR should be built if something changes on Module A, Module B, Module C or Application EAR". Below is listed the modificationset of the projects.

<!-- module A -->   
<modificationset quietperiod="10">
  <veto>
    <triggers>
      <svn localworkingcopy="projects/moduleB" />
    </triggers>
    <buildstatus logdir="logs/moduleB"/>
  </veto>
  <veto>
    <triggers>
      <svn localworkingcopy="projects/moduleC" />
    </triggers>
    <buildstatus logdir="logs/moduleC"/>
  </veto>
  <veto>
    <triggers>
      <svn localworkingcopy="projects/applicationEAR" />
    </triggers>
    <buildstatus logdir="logs/applicationEAR"/>
  </veto>
  <svn localworkingcopy="projects/moduleA"/>
</modificationset>

<!-- module B -->
<modificationset quietperiod="10">
  <veto>
    <triggers>
      <svn localworkingcopy="projects/moduleC" />
    </triggers>
    <buildstatus logdir="logs/moduleC"/>
  </veto>
  <veto>
    <triggers>
      <svn localworkingcopy="projects/applicationEAR" />
    </triggers>
    <buildstatus logdir="logs/applicationEAR" />
  </veto>

  <buildstatus logdir="logs/moduleA" />

  <svn localworkingcopy="projects/moduleB"/>
</modificationset>

<!-- module C -->
<modificationset quietperiod="10">
  <veto>
    <triggers>
      <svn localworkingcopy="projects/applicationEAR" />
    </triggers>
    <buildstatus logdir="logs/applicationEAR" />
  </veto>

  <buildstatus logdir="logs/moduleA" />
  <buildstatus logdir="logs/moduleB" />

  <svn localworkingcopy="projects/moduleC"/>
</modificationset>

<!-- application EAR -->
<modificationset quietperiod="10">

  <buildstatus logdir="logs/moduleA" />
  <buildstatus logdir="logs/moduleB" />
  <buildstatus logdir="logs/moduleC" />

  <svn localworkingcopy="projects/applicationEAR"/>
</modificationset>  

With this configuration we have perfect build order. Now, as I mentioned, one project depends on it's predecessor, so Module B requires the artifact of Module A. To achieve this is just add in the build.xml file the path of the last artifact of Module A. But what is this path?

When CruiseControl creates an artifact will use a timestamp directory and to add this directory to the build environment in Ant was (for me) complex. The solution was use the good and old symbolic link on Unix file systems. This pattern is not new, just add a symbolic link to current directory of interest, in this case the last artifact directory. So I've written another simple Perl script that does the job.

#!/usr/bin/perl
# vim: expandtab sw=2 ts=2 bg=dark
# this script links the latest artifact
# to a latest directory so we can use
# the artifact as dependence of another
# project.
use strict;
use warnings;

my $ARTIFACT_DIR = "/var/cc-sandbox/artifacts";
my $PROJECT_NAME = $ARGV[0];

die("Missing project name.") unless $PROJECT_NAME;

my $W_DIR = $ARTIFACT_DIR."/$PROJECT_NAME";

opendir(DIR,$W_DIR) or die $!;
  my @dirs = sort(grep { /^\d{14}$/ } readdir(DIR)); # list all dirs, sorted.
closedir(DIR);

# exit if we don't have artifacts.
exit if $#dirs == -1;

# latest directory is on the bottom of
# this array.
my $latest_dir = pop @dirs;

# if we have an latest link lets remove it.
if(-l "$W_DIR/latest") {
  unlink("$W_DIR/latest");
}

# now we create de link to the latest artifact.
symlink("$W_DIR/$latest_dir","$W_DIR/latest") or die ($!);

exit;

This script receive as parameter the project name, enters the artifacts directory and creates a symbolic link to the lastest directory called "latest". To run this script just put it in the section publishers of CruiseControl config.xml. Here's a example for Module A project.

<publishers>
  <onsuccess>
    <artifactspublisher dest="artifacts/${project.name}" file="projects/${project.name}/target/moduleA.jar" />
    <execute command="bin/link_latest.pl ${project.name}" />
  </onsuccess>
  </publishers>

After CruiseControl artifactspublishers task run I use execute task to create the link, now I will find the last artifact easily and any build.xml can refer to this directory.

That's it.

Published in Nov 19, 2008