Build Refactoring: Extract Target

Your build files probably need some TLC. Sorry. It's true. If you're lucky enough to work on a project with clean code (and let's face it, most codebases aren't too pretty), then you're even luckier to have a nice clean build. Wait! Don't go getting upset, I can help you.

I'll be introducing some build refactorings in this series. You can refactor code, so why not your build files? I wrote an article about this in the ThoughtWorks Anthology. The Pragmatic Programmers own that now so I'm writing some short articles for this blog. Please consider using the Amazon link above if you want to buy a copy of the book - it will help the cause.

Today's refactoring is extract target. This example is written in Apache Ant syntax. It holds equally true for NAnt and even other build tools. Consider the following build file:

<project name="unrefactored" default="compile_and_configure_web_app">

	<target name="compile_and_configure_web_app">
		<delete dir="build" />
		<mkdir dir="build/classes" />
		<javac srcdir="src" destdir="build/classes"/>
		<!-- the config file  -->
		<copy file="src/web.xml" todir="build/web.xml">
			<filterset>
				<filter token="DB_HOST" value="oradvdb17"/>
			</filterset>
		</copy>
		<mkdir dir="build/WEB-INF/lib" />
		<!-- build the jar file -->
		<jar  jarfile="build/WEB-INF/lib/webapp.jar"
				basedir="build/classes"
				manifest="src/manifest.mf">
		</jar>
		<!-- and the war file  -->
		<war file="build/webapp.war"
					webxml="build/web.xml"
					manifest="src/manifest.mf"
					basedir="build">
			<include name="**/*.jar" />
		</war>
	</target>

</project>

It's a bit obtuse, isn't it? It's doing several things at once and it's difficult to see what's going on.
Let's start by teasing it apart into it's constituent parts:

<project name="refactored" default="default">

	<target name="clean">
		<delete dir="build" includeemptydirs="true"/>
		<mkdir dir="build/classes" />
	</target>

	<target name="web.xml">
		<copy file="src/web.xml" todir="build/web.xml">
			<filterset>
				<filter token="DB_HOST" value="oradvdb17"/>
			</filterset>
		</copy>
	</target>

	<target name="webapp.jar">
		<javac srcdir="src" destdir="build/classes"/>
		<mkdir dir="build/WEB-INF/lib" />
		<jar  jarfile="build/WEB-INF/lib/webapp.jar"
				basedir="build/classes"
				manifest="src/manifest.mf">
		</jar>
	</target>

	<target name="webapp.war" depends="webapp.jar,web.xml">
		<war file="build/webapp.war"
					manifest="src/manifest.mf"
					webxml="build/web.xml"

					basedir="build">
			<include name="**/*.jar" />
		</war>
	</target>

	<target name="default" depends="clean, webapp.war" />

</project>

I think that's much nicer. You don't need XML comments in the middle of your target line because each smaller target can have a descriptive name.

When you make a minor change, you're likely only to change one target. You can contain your changes, and if you're debugging you can run just one target if you need to. And most importantly, it's readable. I find that I tend to naturally do this as I evaluate a complex build. Tool support would be nice.

DevOps New Zealand