CruiseControl Best Practices: Keep your dependencies to yourself

This is the second of ten practices for CruiseControl

The average Java project has many dependencies - open source tools and frameworks, third party libraries, libraries that come from your project or organization - the list is endless. When I wrote this article, my current project had 84 jar files that it depended on (or could have depended on!). Much pain can come from the way you manage these dependencies. Sometimes it's harder than you think to make software build or execute if it's not clear what the dependencies are. A lot of the pain of joining a new project and taking a day to make the code build comes from unclear code or environmental dependencies.

It's a good idea to keep your CruiseControl installation and its dependencies clearly separated from your projects and their dependencies. So for the most basic example, lay out your CruiseControl install something like this:

|-- cruise
| |-- 2.7.0
| `-- 2.7.1
|-- logs
| |-- blowfish
| `-- scorpion
`-- projects
|-- blowfish
`-- scorpion

Upgrading CruiseControl should be the work of minutes.If you need to add libraries to CruiseControl itself, this should be a warning sign that your project has dependencies it can't itself satisfy. The only exception to this rule that I can think of is custom CruiseControl bootstrappers or publishers and the like.

There are two things in particular to take note of here: the logs and the '.ser' files. The logfiles represent the history of your project: this is why I always try to keep them in a different directory hierarchy from the CruiseControl installs. CruiseControl will also by default persist the state of its projects in files with the extension '.ser'. Make sure to keep those when you upgrade. If you do this, and your project has no dependencies on CruiseControl, it should be simple to upgrade.

Next, think about the dependencies you have baked into your build tools. There's a common pattern of putting dependencies into your Apache Ant's 'lib' directory. For things that are hard to wire in like Junit, fine. But if your build depends on something that is actually resident in your build tool, then you have a problem. It will work today, but not necessarily tomorrow. If you upgrade libraries that your code depends on in the build tool, you can't build yesterday's code - which makes things interesting when you're trying to resolve a production bug!

The good news is, it's easy to fix: make the project contain its dependencies. For example, if your Ant build depends on Oracle (say, to support rebuild the database with the 'sql' task, and your standard project Ant build contains the Oracle drivers, your build may look like this:

			<target name="drop_all_tables">

			<sql driver="oracle.jdbc.driver.OracleDriver"
			userid="user" password="donttell"
			url="jdbc:oracle:thin:@localhost:1521:orcl"
			delimiter=";">

			<transaction src="${sql.dir}/drop_all_tables.sql"/>

			</sql>

			</target>
			

However in this case, the dependency on the 'in the
default classpath' Oracle driver isn't stated; Ant will just look for the 'oracle.jdbc.driver.OracleDriver'
class in the default classpath. The first thing to do is put the driver jar file
into the project and set a classpath:

lib/
`-- ojdbc14.jar

			<path id="buildtime">
			<pathelement location="${lib.dir}/oracle.jar"/>
			</path>



			<target name="drop_all_tables">
			<sql driver="oracle.jdbc.driver.OracleDriver" userid="user" password="donttell"
			url="jdbc:oracle:thin:@localhost:1521:orcl"
			delimiter=";" classpathref="buildtime">
			<transaction src="${sql.dir}/drop_all_tables.sql"/>
			</sql>

			</target>
			

Note the addition of the 'classpathref' attribute in the 'sql'
element. This useful little attribute allows you to refer to paths elsewhere
within the build, reducing duplication. When you have moved a dependency into a
project, take to ceremonially deleting the dependency the moment that you can.
On a project where I recently undertook this process, I was fortunate to have a
regular release working in my favour. I fixed the dependencies in the trunk and
revisited a few weeks later, once the release branches that depended on things
outside the project weren't used.

So in summary, think about your project's dependencies, including the ones that
are satisfied because they just happen to be satisfied. If you make those
dependencies explicit, I promise you that you'll be asked less questions from
your colleagues about why they can't compile! If you have many projects with the
same dependencies, that's something that I hope to address in a future post.

DevOps New Zealand