|
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
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 |
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
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
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.
This was written using CruiseControl version 2.2 and assumes you have already downloaded, built and installed CruiseControl.
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.
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.
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:
You will certianlly have to make many changes to this file. Here is the reference guide.
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.