IBM Rational ClearCase, Ant, and CruiseControl: The Java Developers Guide to Accelerating and Automating the Build Process

Chapter 6 described how to configure CruiseControl to monitor ClearCase branches for changes with the <modificationset> element and how to update specific files before building via the <clearcasebootstrapper> plug-in. In practice, this is probably the only additional setup that will be required to get CruiseControl working with ClearCase. However, there are other areas where you can start using CruiseControl to automate more of the activities you normally would carry out with ClearCase manually. One of these is the practice of labeling or baselining. This section describes how to use the build label that CruiseControl automatically generates to ClearCase label or baseline your builds. It also describes how to change the implementation of this build label so that it is more aligned with the recommendations in Chapter 3, "Configuring Your SCM Environment." I will show you how to do this through the implementation of a new ClearCase <labelincrementer> plug-in.

Automating Labeling or Baselining

In Figure 7.4 you might have noticed a field called label. CruiseControl creates and increments this label at each build. You can specify the label's format with the <labelincrementer> plugin, which you usually place under each project element as follows:

<project name="RatlBankModel" buildafterfailed="false"> <labelincrementer defaultLabel="RATLBANKMODEL.1" separator="-"/> ... </project>

In this example, the initial label that is created is RATLBANKMODEL-1; subsequent labels are RATLBANKMODEL-2 and so on. By default, this label is incremented only if the build succeeds. To use this label during the build process, CruiseControl makes it available to the Ant builder as a property called label. Therefore, when you execute your Ant build target, you can simply refer to this property in your Ant build.xml file. With Base ClearCase, you can use this property to create a label type and apply the label across the build directory structure, as shown in Listing 7.1.

Listing 7.1. Base ClearCase CruiseControl Baselining

<?xml version="1.0"?> <project name="RatlBankModel" default="help" basedir="." xmlns:ca="antlib:net.sourceforge.clearantlib"> ... <!-- CruiseControl Integration Build --> <target name="integration-baseline" description="execute CruiseControl Integration Build" depends="clearcase-pre, compile, junit-all, clearcase-post, javadoc"> <cc-apply-label label="${label}" plevel="BUILT"/> </target> </project>

This example uses the macro cc-apply-label that was created in Chapter 5, "Apache Ant Best Practices." With UCM you could use the property to create a baseline and apply it across the build directory structure, as shown in Listing 7.2

Listing 7.2. UCM CruiseControl Baselining

<?xml version="1.0"?> <project name="RatlBankModel" default="help" basedir="." xmlns:ca="antlib:net.sourceforge.clearantlib"> ... <!-- CruiseControl Integration Build --> <target name="integration baseline" description="execute CruiseControl Integration Build" depends="clearcase-pre, compile, junit-all, clearcase-post, javadoc"> <cc-apply-bl basename="${label}" plevel="BUILT" component="${name.build.component}"/> </target> </project>

This example uses the macro cc-apply-bl that was created in Chapter 5. In both of these examples, the label or baseline is applied after the build compiles successfully and the JUnit tests have passed. Given this fact, the Base ClearCase example in Listing 7.1 applies an attribute to the label to indicate a promotion level of BUILT (the creation of this attribute was explained in Chapter 3). Similarly, the UCM example in Listing 7.2 also promotes the baseline to BUILT. This is a good example of the level of automation that can be achieved using ClearCase, Ant, and CruiseControl.

Granular Source Code Checking

In Chapter 6, the CruiseControl example implemented a <modificationset> that checked for changes on the project integration branch and in the build top-level directory:

<property name="dir.ratlbankmodel" value="C:\Views\RatlBankModel_bld\RatlBankSources\model"/> ... <modificationset quietperiod="90"> <clearcase branch="RatlBankModel_Int" viewpath="${dir.ratlbankmodel}"/> </modificationset>

To calculate this modification set, CruiseControl constructs and executes a cleartool lshistory command, similar to the following:

[View full width]

>cleartool lshistory -branch RatlBankModel_Int -since [last build] -fmt ...C:\Views \RatlBankModel_bld\RatlBankSources\model

This command determines if any changes have occurred by examining every file recursively down the viewpath, which in some projects could take some time. Also, probably quite a number of files in this directory structure might not have a direct impact on the build, such as text files, documentation, and so on. There are a number of ways to optimize this. First, you could point the viewpath at a lower directory level, such as the src directory:

<modificationset quietperiod="90"> <clearcase branch="RatlBankModel_Int" viewpath="${dir.ratlbankmodel}/src"/> </modificationset>

Additionally, you could specify multiple <clearcase> elements in the <modificationset>. For example, to check both the src and lib directories for changes, you could use the following:

<modificationset quietperiod="90"> <clearcase branch="RatlBankModel_Int" viewpath="${dir.ratlbankmodel}/src" /> <clearcase branch="RatlBankModel_Int" viewpath="${dir.ratlbankmodel}/lib" /> </modificationset>

If you want to ignore certain files when the <modificationset> is calculated, as in the case of text or log files, you can include an additional ignoreFiles attribute to the <modificationset> element:

<modificationset quietperiod="90" ignoreFiles="*.txt,*.log"> <clearcase branch="RatlBankModel_Int" viewpath="${dir.ratlbankmodel}/src" /> <clearcase branch="RatlBankModel_Int" viewpath="${dir.ratlbankmodel}/lib" /> </modificationset>

You might be wondering if this is a good idea, because any changes in the top-level directories will not be found. However, this directory normally contains just the controlling build files, such as build.xml, default.properties, and so on. In this case, although they aren't returned as part of the modification set, they are at least updated as part of the bootstrapping activity for a snapshot view. For a dynamic view, they obviously are up to date already.

ClearCase Build Label Plug-in

Chapter 3 defined a convention for applying ClearCase labels or baselines. By default, Cruise Control-generated labels do not conform to this convention. However, there is no reason why this can't be rectified by implementing a new <labelincrementer> plug-in. The requirements for such a plug-in might be as follows:

  • The label must be composed of three elements: a user-generated prefix, an automatically incremented build number, and a postfix that indicates the label's type or maturity (such as Release, Integration, or Private Build).

  • The separator between the elements must be an underscore (_).

  • The label must be in uppercase.

To implement this plug-in, two Java classes need to be implemented. The first (which I will call ClearCaseLabelIncrementer.java) will contain the code to generate the label. The second (ClearCaseLabelIncrementerTest.java) will contain the JUnit code required to test the first class. Any <labelincrementer> plug-in class must implement the LabelIncrementer interface, which contains four methods:

  • incrementLabel Takes an existing label and increments its numeric part.

  • isPreBuildIncrementer Used to ascertain whether the label is to be incremented before or after the build. In this example the label is incremented after the build, so the implementation of this method returns false.

  • isValidLabel Checks whether a given label meets the convention.

  • getdefaultLabel Returns a default, system-generated label.

Additionally, a single set method called setDefaultLabel is required so that a parameter can be passed to the ClearCase <labelincrementer> indicating what the initial label should be set to, such as RATLBANKMODEL-1-INT.

Let's adopt a test-driven development approach and implement the JUnit test class first. This will help us identify all the success and failure conditions for the class. The JUnit test class is illustrated in Listing 7.3.

Listing 7.3. ClearCaseLabelIncrementerTest.java Source Code

package net.sourceforge.cruisecontrol.labelincrementers; import junit.framework.TestCase; public class ClearCaseLabelIncrementerTest extends TestCase { private ClearCaseLabelIncrementer incrementer; public ClearCaseLabelIncrementerTest(String name) { super(name); } public void setUp() { incrementer = new ClearCaseLabelIncrementer(); } public void testValidLabel() { assertTrue(incrementer.isValidLabel("X_88_INT")); } public void testInValidLabel() { assertFalse(incrementer.isValidLabel("x_y")); assertFalse(incrementer.isValidLabel("x88")); } public void testIncrementLabel() { assertEquals("X_89_REL", incrementer.incrementLabel("X_88_REL", null)); } public void testGetDefaultLabel() { assertEquals("CC_1_INT", incrementer.getDefaultLabel()); assertTrue(incrementer.isValidLabel( incrementer.getDefaultLabel())); } public void testDefaultLabel() { incrementer.setDefaultLabel("FOO_69_REL"); assertEquals("FOO_69_REL", incrementer.getDefaultLabel()); incrementer.setDefaultLabel("bar_69_REL"); assertEquals("BAR_69_REL", incrementer.getDefaultLabel()); } }

You might want to add tests and failure conditions to this class yourself, but for our purposes this is good enough. This file should be created in the CruiseControl test directory structure. For example, navigate to the JavaTools src/cruisecontrol-2.x.x directory that was created in Chapter 6, and place the file in

main/test/net/sourceforge/cruisecontrol/labelincrementers

Now that we have the test case, we can implement the class itself. With the knowledge of how it should pass and fail (from the JUnit test case), we make a better job of it too. Listing 7.4 shows the source code required to implement the ClearCase <labelincrementer>.

Listing 7.4. ClearCaseLabelIncrementer.java Source Code

package net.sourceforge.cruisecontrol.labelincrementers; import net.sourceforge.cruisecontrol.LabelIncrementer; import org.apache.log4j.Logger; import org.jdom.Element; /** * This class provides a label incrementation for ClearCase labeling. */ public class ClearCaseLabelIncrementer implements LabelIncrementer { private static final Logger LOG = Logger.getLogger(DefaultLabelIncrementer.class); private boolean preIncrement = false; private String defaultPrefix = "CC"; private int defaultBuildNum = 1; private String defaultSuffix = "INT"; private String separator = "_"; public ClearCaseLabelIncrementer() { setSeparator(separator); } /** * Increments the label when a successful build occurs. */ public String incrementLabel(String oldLabel, Element buildLog) { String prefix1 = oldLabel.substring(0, oldLabel.lastIndexOf(separator)); String prefix2 = prefix1.substring(0, prefix1.lastIndexOf(separator)); String suffix = oldLabel.substring( oldLabel.lastIndexOf(separator) + 1, oldLabel.length()); String buildnum = prefix1.substring( prefix1.lastIndexOf(separator) + 1, prefix1.length()); int i = Integer.parseInt(buildnum); String newLabel = prefix2.toUpperCase() + separator + ++i + separator + suffix.toUpperCase(); LOG.debug("Incrementing label: " + oldLabel + "->" + newLabel); return newLabel; } public boolean isPreBuildIncrementer() { return preIncrement; } /** * Verify that the label specified is a valid label. */ public boolean isValidLabel(String label) { if (label.indexOf(separator) < 0) { return false; } try { String prefix1 = label.substring(0, label.lastIndexOf(separator)); String prefix2 = prefix1.substring(0, prefix1.lastIndexOf(separator)); String suffix = label.substring( label.lastIndexOf(separator) + 1, label.length()); String buildnum = prefix1.substring( prefix1.lastIndexOf(separator) + 1, prefix1.length()); // check for consistent suffix if (suffix.equals("BLD") || suffix.equals("INT") || suffix.equals("REL")) { Integer.parseInt(buildnum); return true; } else { return false; } } catch (NumberFormatException e) { return false; } catch (StringIndexOutOfBoundsException e) { return false; } } public String getDefaultLabel() { return defaultPrefix + separator + defaultBuildNum + separator + defaultSuffix; } public void setDefaultLabel(String label) { int separatorIndex = label.lastIndexOf(separator); defaultSuffix = label.substring(separatorIndex + 1, label.length()).toUpperCase(); defaultPrefix = label.substring(0, separatorIndex); separatorIndex = defaultPrefix.lastIndexOf(separator); defaultBuildNum = Integer.parseInt( defaultPrefix.substring(separatorIndex + 1, defaultPrefix.length())); defaultPrefix = defaultPrefix.substring(0, separatorIndex).toUpperCase(); } }

This file should be created in the CruiseControl src directory structure. For example, navigate to the JavaTools src/cruisecontrol-2.x.x directory that was created in Chapter 6 and place the file in

main/src/net/sourceforge/cruisecontrol/labelincrementers

Now that the source code is in place, the CruiseControl application can be recompiled. This was covered in detail in Chapter 6. Basically you go to the command line, navigate to the CruiseControl main directory, and execute the build.bat or build.sh command. (The unit test created earlier also is automatically executed.) Then when the build has finished, copy the new cruisecontrol.jar file in the dist directory into the JavaTools cruisecontrol/dist directory.

Using the ClearCaseLabelIncrementer with UCM Baseline Templates

If you are using UCM and you have a baseline naming template of COMPONENT,BASENAME (as in Chapter 3), you might want to change the ClearCaseLabelIncrementer so that it has only two elements and does not generate a prefix. An example is 01_INT instead of RATLBANKMODEL_01_INT. This is because the generated label can be used as the BASENAME part of the UCM baseline template, whereas the COMPONENT part is automatically generated (by ClearCase) depending on which UCM component the baseline is being applied to.

To use this plug-in, you need to register it first with CruiseControl. To do this, edit your existing CruiseControl config.xml file: directly under the <cruisecontrol> element, add the following:

[View full width]

<cruisecontrol> <plugin name="clearcaselabelincrementer" classname="net.sourceforge.cruisecontrol. labelincrementers. ClearCaseLabelIncrementer"/> ...

Using Different Labelincrementers in a Project

As well as at the <cruisecontrol> level, you can specify different <labelincrementer> plug-ins at the project level. This way you can override the default <labelincrementer> with a project-specific one.

Typically you will configure the <clearcaselabelincrementer> plug-in for every project, specifying the default label string to use as follows:

<project name="RatlBankModel" buildafterfailed="false"> <clearcaselabelincrementer defaultLabel="RATLBANKMODEL_1_INT"/> ...

This creates labels in the format that was specified at the beginning of this sectionRATLBANKMODEL_1_INT, RATLBANKMODEL_2_INT, and so on.

Adding CruiseControl Shortcuts to the ClearCase Explorer

On Windows, ClearCase Explorer can act as your entry into the world of ClearCase, presenting everything you might need to know about ClearCase from a single interface. It is also possible to customize this interface by adding new shortcuts. Since you can actually link into Web pages from inside ClearCase Explorer, this is a great way to see the CruiseControl build status directly alongside your source code. Figure 7.8 shows an example of the CruiseControl Build Results web running in ClearCase Explorer.

Figure 7.8. CruiseControl build results inside ClearCase Explorer

To create these shortcuts, you first create a new shortcut page by right-clicking any existing shortcut page, selecting Add Page, and naming the page "CruiseControl" or something similar. You can right-click this new shortcut page and select Add Tool Shortcut or Add URL Shortcut, depending on whether you are adding a shortcut to a physical program or a Web page. For example, you can link to the CruiseControl Status Page by entering the URL http://build-server:8080/cruisecontrol. If you want to make these shortcuts available for other users, you can export them to a file and configure each user's ClearCase Explorer so that it automatically loads the shortcuts at startup.

Категории