Continuous Integration is one of the buzzwords most people have probably heard of but surprisingly few are actually following this XP best practice. Keeping this in mind, I'll begin this tutorial by briefly describing what Continuous Integration actually means, why you should consider doing it, and finally, showing step by step how to do it using one of the most used Continuous Integration products, the open source CruiseControl developed by a bunch of ThoughtWorkers.
Technically, the term "Continuous Integration" means that everyone on the team integrates their changes back into the source repository frequently, verifying that the changes didn't break anything. These days, most people associate Continuous Integration to be highly automated as well, i.e. having an automatic build system in place to continuously verify that the code in the repository compiles and passes all its tests. The exact definition for "frequently" and "continuously" depends on the team but it serves as a pretty good rule of thumb to say that the frequency should be at least multiple times a day. Personally, I prefer running the integration build at least once an hour.
When I'm referring to the term "Continuous Integration" in this article, I'm talking about running a fully automated build over and over again throughout the day, day after day, against the latest version of the source code and having each developer integrate their changes into the repository as frequently as necessary for keeping your team going fast.
You also may have heard of Daily Builds or Nightly Builds. Continuous Integration is not the same thing as Daily or Nightly Builds. The main differences being three-fold:
"Once an hour, huh?", you might say. "That sounds like a lot of work to integrate up to 7-8 times a day. I mean, we all know how merging one's changes can be a real pain in the behind, right?"
There are some very convincing benefits a development team can gain from using Continuous Integration. Some of those benefits can really be understood only by experiencing it for real, but I'll try my best to convey the most important ones right now, before we get into the "how" part and get our hands all dirty.
Remember when I just said that Continuous Integration aims to provide fast feedback? Let me tell you a little story to explain what I'm talking about.
A friend of mine joined a small software development team that had a relatively long history behind it, having produced a number of bi-yearly releases of an in-house project management software. The codebase was relatively big and messy, the developers were unfamiliar with some of the worst spider webs, the build process took ages, and the testing team was often faced with a broken build that couldn't be tested beyond the first screen or so.
Fortunately, the team had gotten a configuration manager, another friend of mine, with enough energy and will to make things better and the team decided to try something new. That something was Continuous Integration.
The configuration manager set up an automated build system that periodically, once every hour, checked out the latest version of all files in the version control and ran the build script, effectively attempting to compile all code and run any automated tests it could find. The build system would send an email to each and every member of the development team whenever the automated build failed.
Obviously the build system was constantly sending out those build failure emails to the whole team as people repeatedly forgot to check in all their changes, failed to compile the source code locally after a small edit, and neglected running the automated tests before checking in.
What was the team's reaction to this new stream of nasty emails complaining about even the smallest error they could make? Yes, you guessed correctly. The team members asked the configuration manager to stop running the automated build so frequently, arguing that their mailboxes were so full of "build failed" emails that they couldn't do any real work from deleting all that spam. After all, they knew the build is broken already from the first copy of that email so there was no need to send another one.
This reaction was no surprise, considering how annoying it is to get the same whining email an hour after hour. So, how did the configuration manager react to this sudden burst of dissatisfaction? He scheduled the automatic builds to run twice as often as they used to.
My friend -- not the configuration manager, the other one -- told me that that was the moment when he realized how wonderful Continuous Integration actually was. The revelation he had at that time was that the purpose of a Continuous Integration system is not just to inform the developers about the build being broken but to keep the build from being broken for extended periods of time. Keeping the build green.
Eventually, after a day or two of constant nagging about the annoyance, it all suddenly stopped. No "build failed" messages twice in an hour, no whining about getting "spammed", no nothing. The build had started to stay green. Of course the build broke every now and then, but it had become an exception instead of the rule and the developers started to value those "build failed" messages as valuable feedback instead of worthless spam. I've heard of developers initially creating mail filters to send anything coming from the build machine to the trash folder only to find themselves addicted to the invaluable feedback a Continuous Integration system can provide.
To me, all this seems to be convincing evidence about there being something about Continuous Integration that is worth exploring further. If you feel the same, please continue reading.
So, how can we automate this Continuous Integration thing? Are there any tools I can use or do I need to come up with my own home-grown solution?
Building your own piece of software for the job could be a lot of fun. The end result just might be a perfect fit for your project. However, since there are some existing products -- both commercial and open source -- that can do the job and do exhibit a certain degree of configurability, I'm pretty sure you'll be better off starting with an established product. Let's take a brief look at some of them, shall we?
CruiseControl is the clear winner in mind share among CI products for Java developers. It's an open source project run by a bunch of ThoughtWorks employees and remains fairly active. CruiseControl is the product I've chosen to use in this article and it is probably the safest choice for most Java projects to use, considering its significant user base.
AntHill, developed by Urbancode is another reasonably popular alternative for CruiseControl. AntHill comes in two editions, commercial and open source. The commercial version adds a wide array of features but the open source version should be more than enough to get you going. AntHill is not as common as CruiseControl, but it is often quoted as being significantly easier to set up and use than CruiseControl.
CruiseControl.NET is more or less a port of the original Java version for the Microsoft.NET platform. The feature set is pretty close to what the Java version provides.
Draco.NET makes sure that CruiseControl.NET is not the only option for .NET projects either. It's another open source Continuous Integration server for .NET and is definitely a good candidate along with CruiseControl.NET.
DamageControl is the lone wolf among other Continuous Integration servers. It is developed by the CodeHaus open source community (although the developers are currently working for ThoughtWorks). DamageControl earns its lone wolf title by being implemented in Ruby, and provides a fresh alternative for Continuous Integration that should fit both worlds (Java and .NET) equally well.
Tinderbox by the Mozilla project represents a distributed approach to automated, continuous builds on multiple platforms and is worth a look if you're dealing with platform-dependent functionality.
This is not quite all, however. Aslak Hellesoy from ThoughtWorks/Codehaus has started a nice matrix for comparing different CI products, including the ones listed above.
So we have some choices but how do these products differ from each other? Well, generally speaking, not much. They all employ the same overall approach of integrating with a source control repository to get the latest sources, compiling the code and running tests using a build tool like Ant or NAnt, and finally publishing the results via email, a web page, or both. Some tools also provide handy system tray utilities for pushing the green/red information directly to the developers' desktops!
Here's a little diagram for illustrating how these Continuous Integration servers are typically set up in a project environment. Notice how the build server is just another "client" for the repository server.
Next, we're going to set up a Continuous Integration system following the overall architecture illustrated above. In order to solve that puzzle, we need to have all the parts available:
...and, of course:
Since I'm sure not everyone has 4 machines just waiting for their next experiment, we're going to set up all of these "virtual" machines on one box. Here's a screenshot of the directory structure on my Windows box:
As you can see, I've created a separate directory structure for each of our four virtual machines. The build server needs to have CruiseControl installed, a web container (I'm using Jakarta Tomcat 5.0) for CruiseControl to publish its build results with, and a working area where to check out the projects to put on CruiseControl. The developer machines only need to have a checked out copy of the project so the "workingcopy" folder is all they need. Finally, the repository server needs to have the source control repository installed. I've used Subversion for the purposes of this article but any SCM system supported by CruiseControl would do (i.e. one of CVS, Subversion, ClearCase, MKS, Perforce, PVCS, Starteam, Visual SourceSafe).
There's one piece of software I left out. You need to have Ant installed and available for at least the build server. I've installed mine under "C:/Apps" but yours could be anywhere as long as Ant's "bin" directory is in your PATH.
Something to build, right? Since this article is about setting up CruiseControl, we're going to use a very simple project with only a couple of classes, a couple of tests, and a trivial Ant build script. See the Resources section for downloading the sample project as a .zip file.
The project's directory structure is essentially as follows:
src/main | Production source code. All we need is a single class, com.javaranch.journal.cruisecontrol.Calculator. |
src/test | Unit tests. All we need is a single JUnit TestCase, com.javaranch.journal.cruisecontrol.CalculatorTest. |
lib | 3rd party libraries. In our case, this includes only junit.jar. |
build.xml | The project's Ant script, build.xml, is located in the project's root directory. |
build-cc.xml | This is a "wrapper" build script for CruiseControl to use during the build cycle. It's good practice to separate the CruiseControl stuff into a separate file in order to keep the project's main build script as clean as possible. Basically, this script is called by CruiseControl to 1) update the project from the repository, and to 2) build the project. Step 2 is achieved by delegating to appropriate targets inside the project's build.xml. |
The two pieces of this sample project we're really interested of are the build script and the Calculator class.
Here's the source code for the Calculator class:
package com.javaranch.journal.cruisecontrol; /** * The most unimaginative class one could come up with for demonstrating * continuous integration. * * @author Lasse Koskela */ public class Calculator { public int add(int x, int y) { return x + y; } public int subtract(int x, int y) { return x - y; } }
And here's the accompanying test class, CalculatorTest:
package com.javaranch.journal.cruisecontrol; import junit.framework.TestCase; /** * Just a sample test case for demonstrating how the automated build runs unit * tests and acts according to the results. * * @author Lasse Koskela */ public class CalculatorTest extends TestCase { private Calculator calculator; protected void setUp() throws Exception { calculator = new Calculator(); } public void testAddition() throws Exception { assertEquals(3, calculator.add(1, 2)); } public void testSubtraction() throws Exception { assertEquals(1, calculator.subtract(3, 2)); } }
For our purposes, this is really all we need. The fact that we have two tests will help us to experiment with how CruiseControl's Continuous Integration process reacts to failing tests (we can make one of them temporarily fail on purpose), or possible compilation errors.
Since we most definitely need a repository to put our project into, we need to do some more installing. As I mentioned earlier, I've chosen to use Subversion. The free online book about Subversion includes a nice little quick start section for getting you up and running in minutes of downloading Subversion in case you can't make sense of my admittedly brief instructions below.
Assuming you have unzipped/installed Subversion somewhere and added its "bin" directory into your PATH environment variable, these are the steps you need to do to set up the repository for our sample project (if you installed the "virtual machines" under a directory different from "C:/CIA", adjust the paths in these examples according to where you decided to place the "buildserver", "repositoryserver", etc. directories).
First, we need to create the repository itself:
C:/> svnadmin create c:/cia/repositoryserver/svnrepository
Next, we need to create our project into the repository. That's a bit trickier thing to do so follow the instructions carefully.
Create a temporary directory somewhere, e.g. "C:/tmp/project", and create the Subversion-specific directory structure as follows (don't worry about the meaning of these directories -- just think of them as something Subversion needs for doing its job).
C:/> mkdir tmp/project C:/> cd tmp/project C:/tmp/project> mkdir branches C:/tmp/project> mkdir tags C:/tmp/project> mkdir trunk C:/Tmp/project> dir Volume in drive C is GWCS60 Volume Serial Number is E472-052C Directory of C:/tmp/project 12.09.2004 11:53. 12.09.2004 11:53 .. 12.09.2004 11:53 branches 12.09.2004 11:53 tags 12.09.2004 11:53 trunk 0 File(s) 0 bytes 5 Dir(s) 7?98?42?76 bytes free C:/tmp/project>
At this point, you need to copy the project's contents under the newly created "trunk" directory as follows:
trunk/build.xml trunk/src/... trunk/lib/...
After creating this "skeleton" of our project, we need to import the directory structure into our repository with the following command:
C:/tmp/project> svn import C:/tmp/project file:///c:/cia/repositoryserver/svnrepository -m "Initial import" Adding C:/tmp/project/trunk Adding C:/tmp/project/trunk/lib Adding (bin) C:/tmp/project/trunk/lib/junit.jar Adding C:/tmp/project/trunk/src Adding C:/tmp/project/trunk/src/test Adding C:/tmp/project/trunk/src/test/com Adding C:/tmp/project/trunk/src/test/com/javaranch Adding C:/tmp/project/trunk/src/test/com/javaranch/journal Adding C:/tmp/project/trunk/src/test/com/javaranch/journal/cruisecontrol Adding C:/tmp/project/trunk/src/test/com/javaranch/journal/cruisecontrol/CalculatorTest.java Adding C:/tmp/project/trunk/src/main Adding C:/tmp/project/trunk/src/main/com Adding C:/tmp/project/trunk/src/main/com/javaranch Adding C:/tmp/project/trunk/src/main/com/javaranch/journal Adding C:/tmp/project/trunk/src/main/com/javaranch/journal/cruisecontrol Adding C:/tmp/project/trunk/src/main/com/javaranch/journal/cruisecontrol/Calculator.java Adding C:/tmp/project/trunk/build.xml Adding C:/tmp/project/trunk/build-cc.xml Adding C:/tmp/project/branches Adding C:/tmp/project/tags Committed revision 1. C:/tmp/project>
If you're seeing output similar to the above, you've successfully imported our sample project under Subversion's control. You're now free to delete the temporary directory, "C:/tmp/project", since everything is safely stored in the repository.
Now, let's check out some working copies for our developer machines!
You check out a project from a Subversion repository using the "svn checkout" command. Here's an example of how you can check out a working copy for a "developer machine" located at "C:/Developer1":
C:/Developer1> svn checkout file:///c:/cia/repositoryserver/svnrepository/trunk workingcopy A workingcopy/lib A workingcopy/lib/junit.jar A workingcopy/src A workingcopy/src/test A workingcopy/src/test/com A workingcopy/src/test/com/javaranch A workingcopy/src/test/com/javaranch/journal A workingcopy/src/test/com/javaranch/journal/cruisecontrol A workingcopy/src/test/com/javaranch/journal/cruisecontrol/CalculatorTest.java A workingcopy/src/main A workingcopy/src/main/com A workingcopy/src/main/com/javaranch A workingcopy/src/main/com/javaranch/journal A workingcopy/src/main/com/javaranch/journal/cruisecontrol A workingcopy/src/main/com/javaranch/journal/cruisecontrol/Calculator.java A workingcopy/build.xml A workingcopy/build-cc.xml Checked out revision 1. C:/Developer1>
Well that wasn't too difficult, was it? Repeat the "svn checkout" command for the second developer machine.
Now that we're on the roll with Subversion checkouts, why not create a working copy for our build server as well:
C:/BuildServer/WorkArea> svn checkout file:///c:/cia/repositoryserver/svnrepository/trunk SampleCCProject A SampleCCProject/lib A SampleCCProject/lib/junit.jar A SampleCCProject/src A SampleCCProject/src/test A SampleCCProject/src/test/com A SampleCCProject/src/test/com/javaranch A SampleCCProject/src/test/com/javaranch/journal A SampleCCProject/src/test/com/javaranch/journal/cruisecontrol A SampleCCProject/src/test/com/javaranch/journal/cruisecontrol/CalculatorTest.java A SampleCCProject/src/main A SampleCCProject/src/main/com A SampleCCProject/src/main/com/javaranch A SampleCCProject/src/main/com/javaranch/journal A SampleCCProject/src/main/com/javaranch/journal/cruisecontrol A SampleCCProject/src/main/com/javaranch/journal/cruisecontrol/Calculator.java A SampleCCProject/build.xml A SampleCCProject/build-cc.xml Checked out revision 1. C:/BuildServer/WorkArea>
This is the project checkout CruiseControl will later use to synchronize with the repository and do the automated builds with. Note that I've used a different name for the checkout directory than with the developer machines. The reason for doing this is that while it doesn't really matter what the developer decides to name his working copy, it is a good idea to keep CruiseControl's working area clean by using descriptive names -- if you're running multiple projects on CruiseControl and they're named "project1", "project2", and so on, you're bound to have trouble with your CruiseControl directories full of meaningless names and your CruiseControl configuration file extra-vulnerable to copy-paste errors.
All set? Good. We've now got a project on the repository server and a couple of developers hooked up. All we need now is the build server which we're going to set up next!
CruiseControl downloads are available via SourceForge.net. I've written this article based on version 2.1.6 of CruiseControl but you should be able to follow through with a slightly older or newer version if you're not afraid of hacking your way through possible differences in the configuration file syntax, etc.
Once you've downloaded cruisecontrol-2.1.6.zip somewhere on your hard drive, unzip its contents into our build server directory as follows:
If you're wondering what those directories inside the CruiseControl distribution are, you're in luck, because we're just about to explore them a bit further.
The "docs" directory, as you might've guessed by now, contains some CruiseControl documentation in HTML format. These documents are not really essential, however, since the really useful documentation is inside the other two directories, namely "main/docs" and "reporting/jsp/docs". Feel free to take a peek but you shouldn't need to look at them in order to get started with CruiseControl. That is, assuming I'm doing a good job explaining the tricks of the trade in this article...
So, next up is the "main" directory. This directory contains the CruiseControl "engine", along with all source code, tests and build scripts for building it from a clean slate. The "main" directory contains too many directories to warrant a detailed description for each of them so I'll just mention those you need to know about.
main/bin | This directory contains a batch/shell script for launching the CruiseControl process, i.e. cruisecontrol.bat or cruisecontrol.sh depending on your operating system. |
main/dist | This directory contains the cruisecontrol.jar, i.e. the CruiseControl engine we're going to launch in just a few more minutes. If you can't see such a file, back up to the "main" directory and run "ant jar" to compile CruiseControl and to create the cruisecontrol.jar under "dist". |
main/logs | This is the directory where we're going to make CruiseControl collect all the historical build results for each project. Well, since we're only going to put one project on CruiseControl, there will be just one subdirectory under "logs", namely "SampleCCProject". |
All clear? So what's the remaining "reporting" directory next to "main"? Right now, I'll just say it contains the J2EE web application used for reporting CruiseControl build results online after each automated build. We'll get back to it in Part 2 of this tutorial.
Oh, did I mention that you've now "installed" CruiseControl? Well, you have. Now we just need to configure it to build our sample project...
In order for CruiseControl to be able to do anything sensible, you need to provide a configuration file telling which projects to build and, most importantly, how.
This configuration file, which is an XML document, is traditionally named "config.xml" although you can use a different name using the "-configfile" command-line argument for the cruisecontrol.bat/.sh script to point to your custom named configuration.
Instead of going through the CruiseControl config.xml "DTD" (there's not a real DTD document available, just an informal HTML page describing each allowed element), I'll just throw the file we're going to use at you and explain what each of the elements we're using mean and how you could tweak them for slightly different behavior or for a different environment. For more details about the possibilities provided by the CruiseControl configuration file, please refer to the online documentation for config.xml.
One more thing before diving in, though, and something you might want to keep in mind while going through the different configurations. The goal behind all this work is to get timely feedback to the development team about the status of the code in our source repository. If the configuration currently in version control doesn't build at all, stumbles on a missing library, or doesn't pass its unit tests, we want to let the developers know. We want this feedback on two levels: high and low. The high-level feedback is a binary answer to the question "is the build ok?" The low-level feedback is the possibility for a developer to dig in deeper to figure out what exactly went wrong with the build that turned out "red".
Without further delay, the config.xml for our sample project:
The root element of the configuration file,
The
Now, a
The
The
In our example, we're using the
Most projects will suffice with the SCM-spesific task checking for changes in a repository, but that's not quite all you can do with
We've already seen how to tell CruiseControl the 'what' and part of 'when'. The
The children of
The
The multiple attribute can be used to tell CruiseControl to execute the builder in question only every nth time. This comes in handy if you have a huge project taking a long time to compile from scratch (a "clean build" in the geek slang). In those cases, it's often a good idea to specify two separate
I lied. In fact, the
Alright. We've now gotten all modifications from the repository and executed an automated build (if deemed necessary). What's left to do is to interpret the results and publish them for humans to see. The
The
At this point, we know that all those log files are going to the directory specified by the dir attribute. Now how do those compilation errors and unit test results end up into CruiseControl's log files? The answer lies in the only child elements recognized by
The
"All XML files?" you ask. Yes, all XML files. The way CruiseControl works is that it wire-taps the build script recording any output into one huge log file and lets the reporting applications worry about picking the information they want out of the mammoth log file. Once we've got CruiseControl running, you can take a peek at the log files under "BuildServer/CruiseControl/main/logs/SampleCCProject" to see for yourself what information is CruiseControl harvesting from the project's build process.
The
Probably the most common publisher is the
There's also an FTP version named
I already mentioned that many projects tend to use email as a channel for publishing their build results. CruiseControl supports this with two types of email publishers: the "link email publisher" (
I've included the regular
Here's the
What the attributes of the
These are not all the attributes you can specify, however. There are plenty more, including defaultsuffix with which you can specify the default domain name for recipient addresses picked up from the SCM, failasimportant with which you can put the red "important" flag up in the developers' mail client in case a build failed, a username and password in case you need to authenticate to the mail server, returnname which you typically use to give your CruiseControl server a little personality, and spamwhilebroken which CruiseControl uses to decide whether it should continue sending "build failed" emails for subsequent failing builds.
Again, the full potential available to you is revealed in the official documentation.
Now what about those child elements? Our little example is using two very similar child elements for
Finally, the
The "fat" version of the email publisher,
If you want to specify a single XSL file for transforming the CruiseControl build results into a nice little HTML document, you point to the stylesheet using the xslfile attribute. Alternatively, if you don't feel like writing such a monster for a stylesheet, you can fall back on the standard stylesheets by pointing the xsldir attribute to the "xsl" directory holding the XSL files that came as part of the CruiseControl distribution and by pointing the css attribute to the CSS stylesheet in the adjacent "css" directory. Of course, you can tweak these XSL and CSS documents as you please!
If you feel like trying out the HTML email publisher, replace the earlier
Changing the primitive link email publisher configuration to the fancier HTML version above produces the following type of an email after intentionally making our unit test fail:
Approximately 1 minute and 22 seconds later, after fixing that failing test and committing my changes to the repository, I got the following "build successful" email:
Quite nice -- and useful -- isn't it?
By tweaking the XSL stylesheets you can do pretty much customization regarding what the email being sent out contains. However, sometimes you'd like to make certain by-products of the build available along with the build results. In those situations, and as I've done in our example, the
To be more accurate, the files under "dist" end up into a subdirectory named after the build timestamp, e.g. "logs/SampleCCProject/20040914190929". The artifacts archived as part of the build history can be made available for later retrieval or viewing through the reporting web application, for example.
If none of the above publishers quite satisfy your need to publicize, you can always plug in your own implementation or delegate to an external program using the built-in
Perhaps one of the most famous custom CruiseControl publishers to date has been the quite innovative application of lava lamps and X10 automation electronics. What makes such a configuration really cool isn't the fact that someone has gone through the trouble of hooking up these seemingly remote objects -- a CruiseControl server and two lava lamps -- but how the team in question has made use of the "unused bandwidth" of their environment, the gentle visual signal of the red lava lamp slowly starting to bubble when someone has broken the build.
Another clever, although far from radical, way to publish build results is to install an always-on client-side widget -- usually a system tray application -- that shows the green/red state of the current build and possibly informs the developers when the next build is due.
I know of a couple of such implementations, namely Ivan Moore's Python script which screen-scrapes the CruiseControl reporting web application to figure out build status and Dashboard which consists of a server-side component that hooks up to CruiseControl's AntBuilder and broadcasts build events to IDE plugins (currently only Eclipse and IDEA) within the local area network over multicast.
There are similar utilities for other Continuous Integration products as well CruiseControl and it shouldn't be too difficult to implement your own with some spare time and willingness to dive into CruiseControl's source code.
That's about enough talking. It's time to see our carefully crafted CruiseControl configuration in action!
With the configuration file in place under "main/bin", you can finally launch CruiseControl with the following command:
C:/CruiseControl/main/bin> cruisecontrol
The output should look like this:
C:/CIA/BuildServer/CruiseControl/main/bin>cruisecontrol "C:/java/j2se/bin/java" -cp "..." CruiseControl [cc]syys-14 20:42:31 Main - CruiseControl Version 2.1 Compiled on September 12 2004 1552 [cc]syys-14 20:42:31 trolController- projectName = [SampleCCProject] [cc]syys-14 20:42:31 Project - Project SampleCCProject: reading settings from config file [C:/CIA/BuildServer/CruiseControl/main/bin/config.xml] [cc]syys-14 20:42:31 Project - Project SampleCCProject starting [cc]syys-14 20:42:31 Project - Project SampleCCProject: idle [cc]syys-14 20:42:31 BuildQueue - BuildQueue started [cc]syys-14 20:42:31 Project - Project SampleCCProject started [cc]syys-14 20:42:31 Project - Project SampleCCProject: next build in 1 minutes
After a few minutes, you should be looking at an output similar to mine:
[cc]syys-14 20:46:11 Project - Project SampleCCProject: in build queue [cc]syys-14 20:46:11 BuildQueue - now building: SampleCCProject [cc]syys-14 20:46:11 Project - Project SampleCCProject: reading settings from config file [C:/CIA/BuildServer/CruiseControl/main/bin/config.xml] [cc]syys-14 20:46:11 Project - Project SampleCCProject: bootstrapping At revision 39. [cc]syys-14 20:46:13 Project - Project SampleCCProject: checking for modifications [cc]syys-14 20:46:14 Project - Project SampleCCProject: No modifications found, build not necessary. [cc]syys-14 20:46:14 Project - Project SampleCCProject: idle [cc]syys-14 20:46:14 Project - Project SampleCCProject: next build in 1 minutes [cc]syys-14 20:47:14 Project - Project SampleCCProject: in build queue [cc]syys-14 20:47:14 BuildQueue - now building: SampleCCProject [cc]syys-14 20:47:14 Project - Project SampleCCProject: reading settings from config file [C:/CIA/BuildServer/CruiseControl/main/bin/config.xml] [cc]syys-14 20:47:14 Project - Project SampleCCProject: bootstrapping At revision 39. [cc]syys-14 20:47:16 Project - Project SampleCCProject: checking for modifications [cc]syys-14 20:47:17 Project - Project SampleCCProject: No modifications found, build not necessary. [cc]syys-14 20:47:17 Project - Project SampleCCProject: idle [cc]syys-14 20:47:17 Project - Project SampleCCProject: next build in 1 minutes [cc]syys-14 20:48:17 Project - Project SampleCCProject: in build queue [cc]syys-14 20:48:22 BuildQueue - now building: SampleCCProject [cc]syys-14 20:48:22 Project - Project SampleCCProject: reading settings from config file [C:/CIA/BuildServer/CruiseControl/main/bin/config.xml] [cc]syys-14 20:48:22 Project - Project SampleCCProject: bootstrapping At revision 39. [cc]syys-14 20:48:24 Project - Project SampleCCProject: checking for modifications [cc]syys-14 20:48:25 Project - Project SampleCCProject: No modifications found, build not necessary. [cc]syys-14 20:48:25 Project - Project SampleCCProject: idle [cc]syys-14 20:48:25 Project - Project SampleCCProject: next build in 1 minutes
Notice how the same loop repeats time and time again? That's CruiseControl idling because nobody has checked in anything since the last build. Why don't we get some action by introducing some build failures...
Edit src/main/com/javaranch/journal/cruisecontrol/Calculator.java in one of the developer machines' working directory by changing the subtract() method from:
public int subtract(int x, int y) { return (x - y); }
to:
public int subtract(int x, int y) {
return (x - y) + 5;
}
Then, commit your intentional build-breaker to the repository with
C:/Developer1/workingcopy> svn commit . -m "I did it" Sending src/main/com/javaranch/journal/cruisecontrol/Calculator.java Transmitting file data . Committed revision 40. C:/Developer1/workingcopy>
After a couple of minutes of anxious waiting, you should see output similar to the following:
[cc]syys-14 20:58:54 Project - Project SampleCCProject: in build queue
[cc]syys-14 20:58:54 BuildQueue - now building: SampleCCProject
[cc]syys-14 20:58:54 Project - Project SampleCCProject: reading settings from config file
[C:/CIA/BuildServer/CruiseControl/main/bin/config.xml]
[cc]syys-14 20:58:54 Project - Project SampleCCProject: bootstrapping
At revision 39.
[cc]syys-14 20:58:56 Project - Project SampleCCProject: checking for modifications
[cc]syys-14 20:58:57 Project - Project SampleCCProject: No modifications found, build not necessary.
[cc]syys-14 20:58:57 Project - Project SampleCCProject: idle
[cc]syys-14 20:58:57 Project - Project SampleCCProject: next build in 1 minutes
[cc]syys-14 20:59:57 Project - Project SampleCCProject: in build queue
[cc]syys-14 20:59:57 BuildQueue - now building: SampleCCProject
[cc]syys-14 20:59:57 Project - Project SampleCCProject: reading settings from config file
[C:/CIA/BuildServer/CruiseControl/main/bin/config.xml]
[cc]syys-14 20:59:57 Project - Project SampleCCProject: bootstrapping
At revision 40.
[cc]syys-14 20:59:59 Project - Project SampleCCProject: checking for modifications
[cc]syys-14 21:00:00 odificationSet- 1 modification has been detected.
[cc]syys-14 21:00:00 odificationSet- A modification has been detected in the quiet period.
[cc]syys-14 21:00:00 odificationSet- Sleeping for 49 seconds before retrying.
[cc]syys-14 21:00:51 odificationSet- 1 modification has been detected.
[cc]syys-14 21:00:51 odificationSet- A modification has been detected in the quiet period.
[cc]syys-14 21:00:51 odificationSet- Sleeping for 0 seconds before retrying.
[cc]syys-14 21:00:52 odificationSet- 1 modification has been detected.
[cc]syys-14 21:00:52 Project - Project SampleCCProject: now building
Buildfile: build-cc.xml
update:
[exec] U src/main/com/javaranch/journal/cruisecontrol/Calculator.java
[exec] Updated to revision 40.
build:
setup.properties:
setup.paths:
setup:
compile.main:
[javac] Compiling 1 source file to C:/CIA/BuildServer/WorkArea/SampleCCProject/classes/main
compile.tests:
compile:
jar:
[jar] Building jar: C:/CIA/BuildServer/WorkArea/SampleCCProject/dist/sample.jar
test:
[delete] Deleting directory C:/CIA/BuildServer/WorkArea/SampleCCProject/reports/junit/data
[mkdir] Created dir: C:/CIA/BuildServer/WorkArea/SampleCCProject/reports/junit/data
[junit] Running com.javaranch.journal.cruisecontrol.CalculatorTest
[junit] Tests run: 2, Failures: 1, Errors: 0, Time elapsed: 0,05 sec
[junit] TEST com.javaranch.journal.cruisecontrol.CalculatorTest FAILED
BUILD FAILED
file:C:/CIA/BuildServer/WorkArea/SampleCCProject/build.xml:67: Some unit tests failed
Total time: 5 seconds
[cc]syys-14 21:00:57 Project - Project SampleCCProject: merging accumulated log files
[cc]syys-14 21:00:57 Project - Project SampleCCProject: publishing build results
[cc]syys-14 21:00:58 EmailPublisher- Sending mail notifications.
[cc]syys-14 21:01:02 Project - Project SampleCCProject: idle
[cc]syys-14 21:01:02 Project - Project SampleCCProject: next build in 1 minutes
Notice how at 21:00:00 CruiseControl's build loop detects our modification in the repository, waits for a short, random time after detecting that a modification (the same one...) has happened during the quiet period (which we specified to be 60 seconds), and finally proceeds by executing our build-cc.xml wrapper Ant script.
You can see from the output how build-cc.xml invokes the project's main build script, how the CalculatorTest fails, and how CruiseControl processes the build results by sending email, finally going back to sleep for the specified build interval (again, 60 seconds in our example).
Also notice that CruiseControl keeps on doing builds every 60 seconds even though no further modifications have been detected in the repository -- this behavior is due to the fact that we omitted the buildafterfailed attribute from our configuration file's
So, now that we know CruiseControl detects our failures, let's see whether it detects our fixes... Edit the Calculator class again, returning the subtract() method to its valid implementation, and commit your changes with the same command as before -- except for the commit comment, of course, which should indicate that our edit was to fix the defect we introduced earlier:
C:/Developer1/workingcopy> svn commit . -m "I fixed it" Sending src/main/com/javaranch/journal/cruisecontrol/Calculator.java Transmitting file data . Committed revision 41. C:/Developer1/workingcopy>
Soon, CruiseControl should again pick up our commit from the repository and, hopefully, verify that our "fix" was correct:
[cc]syys-14 21:16:54 Project - Project SampleCCProject: in build queue
[cc]syys-14 21:16:54 BuildQueue - now building: SampleCCProject
[cc]syys-14 21:16:54 Project - Project SampleCCProject: reading settings from config file
[C:/CIA/BuildServer/CruiseControl/main/bin/config.xml]
[cc]syys-14 21:16:54 Project - Project SampleCCProject: bootstrapping
At revision 41.
[cc]syys-14 21:16:57 Project - Project SampleCCProject: checking for modifications
[cc]syys-14 21:16:58 odificationSet- 4 modifications have been detected.
[cc]syys-14 21:16:58 odificationSet- A modification has been detected in the quiet period.
[cc]syys-14 21:16:58 odificationSet- Sleeping for 30 seconds before retrying.
[cc]syys-14 21:17:30 odificationSet- 4 modifications have been detected.
[cc]syys-14 21:17:30 Project - Project SampleCCProject: now building
Buildfile: build-cc.xml
update:
[exec] U src/main/com/javaranch/journal/cruisecontrol/Calculator.java
[exec] Updated to revision 41.
build:
setup.properties:
setup.paths:
setup:
compile.main:
[javac] Compiling 1 source file to C:/CIA/BuildServer/WorkArea/SampleCCProject/classes/main
compile.tests:
compile:
jar:
[jar] Building jar: C:/CIA/BuildServer/WorkArea/SampleCCProject/dist/sample.jar
test:
[delete] Deleting directory C:/CIA/BuildServer/WorkArea/SampleCCProject/reports/junit/data
[mkdir] Created dir: C:/CIA/BuildServer/WorkArea/SampleCCProject/reports/junit/data
[junit] Running com.javaranch.journal.cruisecontrol.CalculatorTest
[junit] Tests run: 2, Failures: 0, Errors: 0, Time elapsed: 0,04 sec
all:
BUILD SUCCESSFUL
Total time: 4 seconds
[cc]syys-14 21:17:34 Project - Project SampleCCProject: merging accumulated log files
[cc]syys-14 21:17:34 Project - Project SampleCCProject: publishing build results
[cc]syys-14 21:17:35 EmailPublisher- Sending mail notifications.
[cc]syys-14 21:17:36 Project - Project SampleCCProject: idle
[cc]syys-14 21:17:36 Project - Project SampleCCProject: next build in 1 minutes
Alright! We've got a Continuous Integration server up and running, responding timely and correctly to our human errors and the resulting corrective actions!
If you had the
A .zip file of the sample project.