Disclaimer don't get the wrong idea about what you've found here

What appears below are my personal notes I wish were part of my long-term memory but don't always seem to fit. I strive for accuracy and clarity and appreciate feedback. If applying any of this information anywhere, confirm for youself the correctness of your work as what you see below might very well be, albeit unintentionally, incorrect or misleading. These notes are here as an easy reference for myself.

Information worthy of a more formal presentation will appear elsewhere than this "Scratch" area. - ksb


KSB's Java, Ant, JUnit, CruiseControl 'Hello, world' example

Below is 'Hello, world' illustrating Java, Ant, JUnit and CruiseControl. Not the simplest examples but an attempt to demonstrate the basics of each tool.

Table of Contents References

Java

Hello.java: Note that it is in the hello package, in the src directory.

$ pwd
/home/ksb/hello
$ mkdir -p src/hello
$ cat src/hello/Hello.java
package hello;

public class Hello {

    public String greet() {
        return "Hello, world";
    }

    public static void main(String[] args) {
        Hello h = new Hello();
        System.out.println(h.greet());
    }
}

(This could be simpler but the greet method is used later when testing with JUnit.) To compile and run:

$ javac src/hello/Hello.java
$ java -cp src hello.Hello
Hello, world

Ant

Now write a build.xml file:

$ cat build.xml
<project name="hello" default="dist" basedir=".">
  <description>KSB's ant 'Hello, world' build.xml example</description>

  <property name="src.dir"   value="src"/>
  <property name="build.dir" value="build"/>
  <property name="dist.dir"  value="dist"/>

  <target name="init">
    <mkdir dir="${build.dir}"/>
    <mkdir dir="${dist.dir}"/>
  </target>

  <target name="build" depends="init" description="build everything under ${src.dir}" >
    <javac srcdir="${src.dir}" destdir="${build.dir}"/>
  </target>

  <target name="dist" depends="build" description="generate the distribution" >
    <jar jarfile="${dist.dir}/hello.jar" basedir="${build.dir}">
      <manifest>
        <attribute name="Main-Class" value="hello.Hello"/>
      </manifest>
    </jar>
  </target>

  <target name="run" depends="dist">
    <java jar="${dist.dir}/hello.jar" fork="true" />
  </target>

  <target name="clean" description="clean up" >
    <delete dir="${build.dir}"/>
    <delete dir="${dist.dir}"/>
  </target>
</project>

Note that this not only compiles with the javac task but also creates a jar file with the jar task. There is also a run target using the java task for executing the jar file.

$ ant
Buildfile: build.xml

init:
    [mkdir] Created dir: /home/ksb/hello/build
    [mkdir] Created dir: /home/ksb/hello/dist

build:
    [javac] Compiling 2 source files to /home/ksb/hello/build

dist:
      [jar] Building jar: /home/ksb/hello/dist/hello.jar

BUILD SUCCESSFUL
Total time: 2 seconds

Because we now have a jar file, created with the manifest target, we can now run the program with the java -jar flag:

$ java -jar dist/hello.jar
Hello, world

Or use the run target:

$ ant run
Buildfile: build.xml

init:

build:

dist:

run:
     [java] Hello, world

BUILD SUCCESSFUL
Total time: 1 second

junit

First create a unit test: TestHello.java. I'm putting this in the same dir as Hello.java (so it will be compiled along with Hello.java), though it could live somewhere else.

$ cat src/hello/TestHello.java
package hello;

import junit.framework.TestCase;

public class TestHello extends TestCase {

    public void testHello()
    {
        Hello h = new Hello();
        assertEquals(h.greet(), "Hello, world");
    }
}

Now add the JUnit parts to the build.xml file, which now looks like (modified parts in bold):

$ cat build.xml
<project name="hello" default="dist" basedir=".">
  <description>KSB's ant 'Hello, world' build.xml example</description>

  <property name="src.dir"   value="src"/>
  <property name="build.dir" value="build"/>
  <property name="dist.dir"  value="dist"/>
  <property name="junit.dir" value="junit-results"/>

  <property name="junit.jar" location="/usr/local/share/java/classes/junit.jar"/>
  <property name="test.class" value="hello.TestHello"/>

  <path id="classpath">
    <pathelement location="${build.dir}" />
    <pathelement location="${junit.jar}" />
  </path>

  <target name="init">
    <mkdir dir="${build.dir}"/>
    <mkdir dir="${dist.dir}"/>
    <mkdir dir="${junit.dir}"/>
  </target>

  <target name="build" depends="init" description="build everything under ${src.dir}" >
    <javac srcdir="${src.dir}" destdir="${build.dir}">
      <classpath refid="classpath"/>
    </javac>
  </target>

  <target name="test" depends="build" description="unit test" >
    <junit errorProperty="test.failed" failureProperty="test.failed">
      <test name="${test.class}" todir="${junit.dir}" />
      <formatter type="brief" usefile="false" />
      <formatter type="xml" />
      <classpath refid="classpath" />
    </junit>
    <fail message="Tests failed: check test reports." if="test.failed" />
  </target>

  <target name="dist" depends="build" description="generate the distribution" >
    <jar jarfile="${dist.dir}/hello.jar" basedir="${build.dir}">
      <manifest>
        <attribute name="Main-Class" value="hello.Hello"/>
      </manifest>
    </jar>
  </target>

  <target name="run" depends="dist">
    <java jar="${dist.dir}/hello.jar" fork="true" />
  </target>

  <target name="clean" description="clean up" >
    <delete dir="${build.dir}"/>
    <delete dir="${dist.dir}"/>
    <delete dir="${junit.dir}"/>
  </target>
</project>

This adds the test target using the junit task and directs xml formatted output of JUnit into a junit-results dir which will later be read by CruiseControl.

Before you will be able to run the new test target, junit.jar must be available to ant in order to understand the junit task. This can be done by either adding it to your $CLASSPATH (before ant is run) or make junit.jar appear in ant's lib dir. I've done the latter via a symlink by making /usr/local/ant/lib/junit.jar -> /usr/local/share/java/classes/junit.jar.

So, now using the new test target:

$ ant test
Buildfile: build.xml

init:

build:
    [javac] Compiling 1 source file to /home/ksb/hello/build

test:
    [junit] Testsuite: hello.TestHello
    [junit] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 0.006 sec


BUILD SUCCESSFUL
Total time: 2 seconds

Modify Hello.java to break the test and run ant test again to convince yourself that it is indeed testing the class.

Note also that there is now a junit-results/TEST-hello.TestHello.xml file holding the results of JUnit in xml.

CruiseControl

This was written using CruiseControl version 2.2 and assumes you have already downloaded, built and installed CruiseControl.

Set up cvs and CruiseControl dirs

First put everything in cvs, and setup & get into new dir for using CruiseControl:

$ ant clean
$ cvs -d ~/cvs_repo init
$ cvs -qd ~/cvs_repo import -I \*.class -m "initial import" hello ksb start
N hello/build.xml
N hello/src/hello/Hello.java
N hello/src/hello/TestHello.java

No conflicts created by this import

Now create the dir tree in which CruiseControl will run:

$ mkdir -p ~/cc/{checkout,logs/hello}
$ cd ~/cc/checkout
$ cvs -qd ~/cvs_repo checkout hello
U hello/build.xml
U hello/src/hello/Hello.java
U hello/src/hello/TestHello.java

The checkout dir is where the hello project is built and the logs dir is where CruiseControl will write it's log files.

Delegate ant build file

Back in the cc dir (where CruiseControl will run) write a wrapper build-hello.xml file which CruiseControl will use to build and rebuild the hello project:

$ cd ..
$ cat build-hello.xml
<project name="build-hello" default="build" basedir="checkout">
  <target name="build">
    <delete dir="hello"/>
    <cvs cvsroot="/home/ksb/cvs_repo" package="hello" />
    <ant antfile="build.xml" dir="hello" target="test"/>
  </target>
</project>

And test this by running it directly in ant:

$ ant -f build-hello.xml
Buildfile: build-hello.xml

build:
      [cvs] U hello/build.xml
      [cvs] U hello/src/hello/Hello.java
      [cvs] U hello/src/hello/TestHello.java
      [cvs] cvs checkout: Updating hello
      [cvs] cvs checkout: Updating hello/src
      [cvs] cvs checkout: Updating hello/src/hello

init:
    [mkdir] Created dir: /home/ksb/cc/checkout/hello/build
    [mkdir] Created dir: /home/ksb/cc/checkout/hello/dist
    [mkdir] Created dir: /home/ksb/cc/checkout/hello/junit-results

build:
    [javac] Compiling 2 source files to /home/ksb/cc/checkout/hello/build

test:
    [junit] Testsuite: hello.TestHello
    [junit] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 0.001 sec


BUILD SUCCESSFUL
Total time: 0 second

Note, by running it again, that this will start from a completely clean slate, removing the hello dir, checking it out fresh from cvs, then running the unit test.

CruiseControl config file

Now write the CruiseControl config file config.xml:

$ cat config.xml
<cruisecontrol>
  <project name="hello" buildafterfailed="false">

    <bootstrappers>
      <currentbuildstatusbootstrapper file="logs/hello/buildstatus.txt"/>
    </bootstrappers>

    <schedule interval="30">
      <ant antscript="/usr/bin/ant"
            buildfile="build-hello.xml"
            target="build" />
    </schedule>

    <modificationset quietperiod="10">
      <cvs localworkingcopy="checkout/hello" />
    </modificationset>

    <log dir="logs/hello">
      <merge dir="checkout/hello/junit-results" />
    </log>

    <publishers>
      <currentbuildstatuspublisher file="logs/hello/buildstatus.txt" />

      <htmlemail mailhost="localhost"
             returnaddress="CC-reply@example.com"
             subjectprefix="[CuiseControl]"
             skipusers="true"
             css="/path/to/cruisecontrol/install/reporting/jsp/css/cruisecontrol.css"
             xsldir="/path/to/cruisecontrol/install/reporting/jsp/xsl"
             logdir="logs/hello">
        <always address="build-master@example.com" />
        <failure address="dev-group@example.com" />
      </htmlemail>

    </publishers>

  </project>
</cruisecontrol>

Briefly, and in order, this configures CruiseControl to:

  1. Have one project named hello which won't be rebuilt after a failure unless new changes are ready in cvs.
  2. Log beginning of build loop to logs/hello/buildstatus.txt.
  3. Every 30 seconds do a build (run 'ant -f build-hello.xml build').
  4. Wait for cvs project in checkout/hello to be idle for 10 seconds before building (to avoid cvs commit race conditions).
  5. Log to logs/hello, merging in JUnit results.
  6. Log end of build loop to logs/hello/buildstatus.txt and send out HTML formatted email about results of each build.

You will certianlly have to make many changes to this file. Here is the reference guide.

Run CruiseControl

Now start CruiseControl (assuming that it is on your ${PATH} and executable):

$ cruisecontrol.sh
[long output deleted...]

You should see CruiseControl doing it's thing: building the hello project and sending out emails. Try checking out the hello project (not the one CruiseControl is using) and try making the changes and breaking the build to see what happens.


Keith S. Beattie is responsible for this document, located at http://dst.lbl.gov/~ksb/Scratch/jajucc_hw.html, which is subject to LBNL's Privacy & Security Notice, Copyright Status and Disclaimers.

Last Modified: Monday, 25-Feb-2013 16:57:57 PST