ONLamp.com    
 Published on ONLamp.com (http://www.onlamp.com/)
 See this if you're having trouble printing code examples


Improve Your Build Process with Ant

by Michael Kimsal
12/21/2005

Web applications today are much more complex beasts than they were even just a few years ago. The largest sites may constitute thousands of files with complex directory structures, and migrating those between development, staging, and production environments can be difficult to say the least. My own experience with web applications dates back to 1996. While I've seen a lot of changes over the years, keeping a large project in check never seems to get much easier, despite advances in CPU speed, RAM prices, broadband, and communication tools.

A few years ago I started using Ant to help bring some structure to my project's packaging and maintenance. As always, I kept everything in CVS, but I'd grown tired of making sure that every step I'd outlined happened in the correct order to create a working product. Between copying files, setting permissions, and editing configuration files, I found many times that one simple omission would cost hours of lost time. Using Ant to automate those tasks was the first step in regaining control of the build process.

Ant Is Java

Yes, Ant is a tool written in Java. Many PHP advocates like to defend PHP against other technologies in the web space by claiming, "Use the best tool for the job!" PHP on the Web is a shining example of this. However, I've yet to find any tools written in PHP that can do everything Ant can do. The closest I've found is a project called Phing, but even this tool lacks some advanced aspects of Ant. (Maybe they'll catch up!)

How Does It Work?

This article isn't a complete Ant tutorial--the Ant home page has much more information than I can provide here. Additionally, this article assumes you have Java and Ant installed already.

When invoked, the Ant program looks for an XML configuration file (build.xml by default), which contains the instructions about what tasks to perform. Each set of tasks is a target and has a name in the XML file.

Example Build File

Here's an example build.xml file:

<?xml version="1.0"?>
<project name="Sample Project" default="init" basedir=".">

        <description>Example project</description>

        <target name="init">
                <property name="sample" value="Hello world!"/>
        </target>
</project>

Assuming the file is in the current directory, type ant at a command line:

Buildfile: build.xml

init:

BUILD SUCCESSFUL
Total time: 0 seconds

Ant processed the build file, ran the init target (because it is the project's default target), and then exited successfully. Now change the file a little more and add a bit of output:

<?xml version="1.0"?>
<project name="Sample Project" default="init" basedir=".">

        <description>Example project</description>

        <target name="init">
                <property name="sample" value="Hello world!"/>
                <echo message="${sample}"/>
        </target>
</project>

The output this time is:

Buildfile: build.xml

init:
     [echo] Hello world!

BUILD SUCCESSFUL
Total time: 0 seconds
Ant: The Definitive Guide

Related Reading

Ant: The Definitive Guide
By Steve Holzner

Building from a Basic Package

Here's another interesting thing Ant can provide--tar and gzip features. tar, short for "tape archive," is a standard way of packaging groups of files for later untarring. gzip is a GNU utility that compresses a file. Many others distribute much software, including PHP itself, as .tar.gz--gzipped tar--files.

Nothing shows this off as well as a real-life example of using Ant to install a tar.gz file. For this example, I'll use the LogiCampus project. Using this build.xml file:

<?xml version="1.0"?>
<project name="Sample Project" default="init" basedir=".">

        <description>Example project</description>

        <target name="init">
                <mkdir dir="install"/>
                <!-- don't forget the compression attr -->
                <untar src="logicampus-1.1.0.tar.gz" dest="install"
                          compression="gzip"/>
        </target>
</project>

and running ant I get the output:

Buildfile: build.xml

init:
    [mkdir] Created dir: /home/user/install
    [untar] Expanding: /home/user/logicampus-1.1.0.tar.gz into
        /home/user/install

BUILD SUCCESSFUL
Total time: 1 second

The above file told Ant to make a directory called install and unpack the original LogiCampus project into that directory.

I'm hoping that you can already see the need for the variables introduced in the first example. Ant also provides a simple way of including files that contain your variables. Ant refers to these variables as properties and uses the property tag to include these files.

Consider now two files, config.properties and build.xml, respectively:

#########################################################
install.dir  = install
temp.dir     = temp
package.name = logicampus-1.1.0
#########################################################
<?xml version="1.0"?>
<project name="Sample Project" default="init" basedir=".">

        <description>Example project</description>

        <property file="config.properties"/>

        <target name="init">
                <mkdir dir="${install.dir}"/>
                <!-- don't forget the compression attr -->
                <untar src="${package.name}.tar.gz" dest="${install.dir}"
                          compression="gzip"/>
        </target>
</project>

Notice the separation of the installation directory and package name into the properties file. As you do more configuration with Ant, keep your configurations in a separate file. So far, this is fairly basic, admittedly. This is just a small script that will untar a file into a directory.

Reconcilable Differences

A big problem many developers face is keeping track of custom changes for specific clients. Many of you have installed various CMS packages for people and needed to make small tweaks, whether code or templates or whatever. How do you keep track of those changes in a sane way? This next Ant technique shows how to handle these very situations.

Assume that you've installed the LogiCampus system in the install directory and are doing some work for a particular client. For argument's sake, suppose that this client wants the time and date displayed at the bottom of every page. You can simply update the public_html/index.php page to add this information at the end of the page request.

Think about this a bit more: what are you trying to accomplish? You're trying to determine the differences between this client-specific version and the core package file you started with. If you can determine just those files that have changed, you can import them into CVS on their own without having to import the entire code base. You can then rebuild by simply untarring the original package file and overlaying the changed files from CVS on top of the original files. The following properties and build files illustrate setting up an Ant task to do just this.

install.dir     = install
temp.dir        = temp
package.name    = logicampus-1.1.0
overlay.dir     = overlay
<?xml version="1.0"?>
<project name="Sample Project" default="init" basedir=".">

        <description>Example project</description>

        <property file="config.properties"/>

        <target name="init">
                <mkdir dir="${install.dir}"/>
                <!-- don't forget the compression attr -->
                <untar src="${package.name}tar.gz" dest="${install.dir}"
                          compression="gzip"/>
        </target>

        <target name="updateoverlay">
                <mkdir dir="${temp.dir}"/>
                <mkdir dir="${overlay.dir}"/>
                <untar src="${package.name}.tar.gz" dest="${temp.dir}"
                          compression="gzip"/>
                <copy todir="${overlay.dir}/">
                        <!-- find files in install that have a modified
                                timestamp later than those in overaly -->
                        <fileset dir="${install.dir}">
                                <and>
                                        <different targetdir="${temp.dir}"/>
                                        <type type="file"/>
                                </and>
                        </fileset>
                </copy>
                <delete dir="${temp.dir}"/>
        </target>
</project>

If you remember, I mentioned that you changed the public_html/index.php file in the install directory. Running ant updateoverlay to call the new Ant task, you should get the output:

Buildfile: build.xml

updateoverlay:
    [mkdir] Created dir: /home/user/temp
    [mkdir] Created dir: /home/user/overlay
    [untar] Expanding: /home/user/logicampus-1.1.0.tar.gz into /home/user/temp
     [copy] Copying 1 file to /home/user/overlay
   [delete] Deleting directory /home/user/temp

BUILD SUCCESSFUL
Total time: 5 seconds

If you dig into the overlay directory, you'll find just the one file that you changed (index.php) sitting in the public_html directory. There are no other files or directories copied over into this overlay area--only the differences between the original tar.gz file and the installed version's changes. It's simply a matter of adding or updating these changed files in your overlay directory to CVS or whatever version-control program you use.

How about going in reverse? How would you copy the entire overlay directory into the install/ directory? A simple copy command might work, but assuming you were using CVS, you'd copy over a whole mess of those pesky CVS directories. Ant has a way to exclude those from the copy. Add another target to your build.xml file:

<target name="applyoverlay"
            description="Copy the contents of overlay/ onto the installed core.">
            <copy todir="${install.dir}/" overwrite="true">
                    <fileset dir="overlay/">
                            <include name="**"/>
                            <exclude name="**/CVS" />
                            <exclude name="**/CVS/**"/>
                    </fileset>
            </copy>
    </target>

You Might Say the Secret Ingredient Is ...

It's not salt but the fileset tag (apologies to Marge Simpson). This tag is at the heart of how Ant deals with a group of files. Ant evaluates the tag to determine which files from a directory to include. Ant will look at several attributes, such as includes, excludes, and followsymlinks in the fileset tag's children, then will process all matching files by passing them to the tag above the fileset tag for specific processing. In this case, it copies the files to the directory specified in the todir attribute of the copy tag.

Notice the fileset tag has other tags below it. The and tag does just what it says--it performs an and operation on the contents of the indicated set of files and the nested tags below. The nested tags are selector tags. Use them to perform logical conditional checks on the files in the fileset group. Again, the case above uses a selector tag to indicate that you want to copy only files that differ from the files in the same relative location of the temp.dir area.

The Ant manual web site has much more information about the ins and outs of fileset and selector tags. This process is just a small indication of what combining fileset and selector tags can do for you.

Recap

What have you ended up with? You can now:

I've seen many techniques for managing multiple clients from one code base, but most techniques took that too literally and tried to do things with symlinks. With that technique, if you need to change one file for one client (perhaps a different authentication system), you have an uphill battle with keeping those changes from affecting other clients using--literally--the same code.

With this approach, you can know that you build each client from the same core system, but you are free to make any required changes or additions to the code, and you know you have a standard mechanism for resolving the changes you've made--even if it's days later.

I've only touched on the idea of making custom changes against one version of a core package. With a little tweaking, the build file could resolve differences between two versions of a package (1.1.0 and 1.1.1, for example) and place those files in a separate area for review. This can be a great alternative to reviewing a changelog on a project you're joining.

Certainly Ant offers more than this--I've barely scratched the surface. I hope this has opened some eyes as to what non-PHP technologies can accomplish, and maybe made Ant a bit more accessible to some of you.

Alternatives to Ant?

As an aside, there are some alternatives to XML-based build tools such as Ant. NAnt and phing are two I've come across. NAnt is a .NET-based version of Ant, and phing is a PHP tool modeled on Ant. I don't use these tools, but from what I can tell in the documentation, neither one supports the concept of the selector tags inside the fileset tag, which is what's at the heart of the diffing technique of this article.

This article was originally inspired by Mark Kimsal from www.fossradio.com.

Michael Kimsal is the host of www.webdevradio.com.


Return to the PHP DevCenter.

Copyright © 2009 O'Reilly Media, Inc.