Archive for the ‘Ant’ Category

Three degrees of freedom in ant

Friday, June 15th, 2007

I've just finished updating the build process at Handmark (again). This time, the goal was auditability and repeatability. We wanted to have a permanent record of exactly which revision was built when, and the ability to quickly get back to that exact build.

The prior build process had two degrees of freedom. On one axis, we have the set of projects. On the other, we have the set of build steps. We represented these two degrees of freedom with two separate ant scripts. build.xml lists the many different jar and war files and the relationships among them. It calls actions.xml via the <ant> task, which lists the build steps like clean, get, compile, unit test, jar, and war. For details, please see Separation of Concerns in Ant.

The command line to run the build took the branch and revision, like this:

ant -Dbranch=trunk -Drevision=24516 clean,war

It would pull the source code and build the war files. When it was finished, the branch and revision number were embedded within the manifest of the war files, but there was no permanent record that this build occurred. In addition, if we wanted to go back to a previous build, we would have to find the branch and revision number and reissue the command. And to be absolutely safe, we would have to pull the previous versions of the build scripts and properties files to ensure that we got exactly the same results.

Here's my solution
I wrapped this build process within another layer. We now have a build script that loads a file called tag.properties. This file contains two lines:

branch=trunkrevision=24516

It then creates a temporary working directory, pulls the build scripts by that branch and revision into that directory, and launches them.

<!-- Load the tag file. --><property prefix="tag" file="${basedir}/tag.properties" />
<!-- Get the project files. --><svn username="${username}" password="${password}">    <export srcUrl="${svnbase}/${tag.branch}/Projects/MyProject/" revision="${tag.revision}" destPath="${work}" /></svn>

<!-- Build the war files. --><ant dir="${work}" target="war" inheritall="false">    <property name="branch" value="${tag.branch}" />    <property name="revision" value="${tag.revision}" />    <property name="username" value="${username}" />    <property name="password" value="${password}" />    <property name="thirdparty" location="${thirdparty}" /></ant>

Now we can change tag.properties and check it in. We can go back to any prior build by checking out the tag.properties file that was current at the time. We can be sure that the build process is repeatable because even the build scripts themselves are pulled by revision. And best of all is the new command line:

ant

Separation of Concerns in Ant

Monday, September 25th, 2006

In a previous post, I wrote about separation of concerns in various software tools. One of these was Ant. Since that time, I have come up with a solution to the problem.

To recap, the problem is that an Ant script defines two perpendicular dimensions of a build: actions and projects. Actions depend upon other actions, and projects on other projects. The work occurs at the intersections of these two dimensions, where an action is applied to a project.

Here's my solution
The first step is to completely parameterize the actions so that they can be applied to any project. The locations of source files, the names of target jar files, and the classpath can all be specified as properties. Create a separate "actions.xml" file for these targets. The actions do not depend upon one another. These dependencies will be handled at a higher level. For example:

 <property prefix="project" file="${home}/project.properties" />  <!-- Get the source files. -->
 <target name="get">
  <!-- Set Defaults -->
  <property name="branch" value="trunk" />
  <property name="revision" value="HEAD" />
  <svn username="${username}" password="${password}">
   <checkout url="https://svn.mallardsoft.com/svn/productline/${branch}/${project}/" revision="${revision}" destPath="${temp}" />
  </svn>
 </target>

 <!-- Compile the java sources. -->
 <target name="compile">
  <javac srcdir="${src}" destdir="${classes}" debug="${debug}">
   <classpath>
    <pathelement path="${project.libs}" />
    <pathelement path="${project.depends}" />
   </classpath>
   <include name="**/*.java" />
  </javac>
 </target>

 <!-- Jar the classes. -->
 <target name="jar">
  <jar destfile="${dist}/${project.jar}.jar">
   <fileset dir="${classes}" />
   <fileset dir="${src}">
       <exclude name="**/*.java"/>
   </fileset>
  </jar>
 </target>

The second step is to create a properties file for each project. Paths are specified as colon-separated lists. I've already loaded two additional properties files that specify the locations of library and project jar files:

jar=business_layer
libs=${lib.logging-log4j}:${lib.xerces}
depends=${proj.database_layer}

The third step is to create a target for each project in your regular "build.xml" file. The target sets the "project" property and calls a target in "actions.xml":

 <target name="database_layer">
  <ant antfile="actions.xml" target="${target}">
   <property name="project" value="database_layer" />
  </ant>
 </target>

 <target name="business_layer" depends="database_layer">
  <ant antfile="actions.xml" target="${target}">
   <property name="project" value="business_layer" />
  </ant>
 </target>

 <target name="web_interface" depends="business_layer">
  <ant antfile="actions.xml" target="${target}">
   <property name="project" value="web_interface" />
  </ant>
 </target>

 <target name="swing_interface" depends="business_layer">
  <ant antfile="actions.xml" target="${target}">
   <property name="project" value="swing_interface" />
  </ant>
 </target>

 <!-- Invoke the same target in all projects. -->
 <target name="all" depends="web_interface,swing_interface" />

The fourth step is to define targets in build.xml that invoke the same action on all projects. These targets specify the dependencies among actions"

 <!-- Get all projects. -->
 <target name="get">
  <antcall target="all">
   <param name="target" value="get" />
  </antcall>
 </target>

 <!-- Compile all projects. -->
 <target name="compile" depends="get">
  <antcall target="all">
   <param name="target" value="compile" />
  </antcall>
 </target>

 <!-- Jar all projects. -->
 <target name="jar" depends="compile">
  <antcall target="all">
   <param name="target" value="jar" />
  </antcall>
 </target>

The designers of Ant probably never intended their tool to be used in this way, but it separates the actions from the projects. Now I can tweak an action for all projects in one place. Or I can add new projects and get all actions applied to it.

Separate Concerns

Thursday, September 7th, 2006

A spreadsheet contains both formulas and the data on which they operate. This makes it difficult to share formulas with someone else without sharing the data. If you want the same calculations performed on a different set of data, you have to make a copy of the spreadsheet. If you later need to update the calculations, you have to make the change in both places.

A Visual Source Safe database contains one tree of folders. A folder can represent a project, a package, a branch, or a shared library. Through discipline, programmers keep these different degrees of freedom on different levels of the tree. We will create a folder under a project to represent the trunk, and another to represent a branch. But what about branches of the shared libraries? Where do they go? Sometimes it is not easy to place one concern neatly below another.

An Ant build script contains several targets. A target can represent an action (clean, compile, deploy, etc.), or it can represent a project (shared library, business logic, web application, etc.). Targets depend upon other targets: deploy depends upon compile, and the web application depends upon the business logic. I need to clean and compile each of my projects, but only deploy my web applications. The web application deployment should include all dependent projects. When describing this to Ant, I end up with a cartesian product of actions for each project. If I later have to modify an action, I have to do so for every project. Similarly, to add a new project, I have to copy and paste all of the actions from another one.

All three of these scenarios suffer from a coupling of concerns. Different dimensions of the problem should be allowed to vary independently. But when the design of the solution mixes these dimensions, it becomes difficult to pull them apart later. Unfortunately, this sort of design flaw is usually not detected until the system has been in use for some time. Please take the time during the design phase to ask if yourself if you can separate concerns.