Apache's Ant has taken the Java development community by storm, supplementing traditional Java IDEs and outright replacing Makefiles on most Java development projects. Ant is a build tool, similar to the make utility, only it uses XML files instead of Makefiles. In addition to a portable build file based on XML, Ant itself is written in Java and has few platform-specific dependencies. Finally, since Ant can reuse the same running instance of the Java Virtual Machine for nearly every step of the build process, it is blazingly fast. Ant can be downloaded from http://jakarta.apache.org and is open source software.
Ant is driven by an XML build file, which consists of one project. This project contains one or more targets, and targets can have dependencies on one another. The project and targets are represented as <project> and <target> in the XML build file; <project> must be the document root element. It is common to have a "prepare" target that builds the output directories and a "compile" target that depends on the "prepare" target. If you tell Ant to execute the "compile" target, it first checks to see that the "prepare" target has created the necessary directories. The structure of an Ant build file looks like this:
<?xml version="1.0"?> <project name="SampleProject" default="compile" basedir="."> <!-- global properties --> <property name="srcdir" value="src"/> <property name="builddir" value="build"/> <target name="prepare" description="Creates the output directories"> ...tasks </target> <target name="compile" depends="prepare"> ...tasks </target> <target name="distribute" depends="compile"> ...tasks </target> </project>
For each target, Ant is smart enough to know if files have been modified and if it needs to do any work. For compilation, the timestamps of .class files are compared to timestamps of .java files. Through these dependencies, Ant can avoid unnecessary compilation and perform quite well. Although the targets shown here contain only single dependencies, it is possible for a target to depend on several other targets:
<target name="X" depends="A,B,C">
Although Ant build files are much simpler than corresponding Makefiles, complex projects can introduce many dependencies that are difficult to visualize. It can be helpful to view the complete list of targets with dependencies displayed visually, such as in a hierarchical tree view. XSLT can be used to generate this sort of report.
Since the build file is XML, XSLT makes it easy to generate HTML web pages that summarize the targets and dependencies. Our stylesheet also shows a list of global properties and can easily be extended to display anything else contained in the build file.
Although this stylesheet creates several useful HTML tables in its report, its most interesting feature is the ability to display a complete dependency graph of all Ant build targets. The output for this graph is shown in Example 3-13.
clean all (depends on clean, dist) prepare tomcat (depends on prepare) j2ee (depends on tomcat) j2ee-dist (depends on j2ee) main (depends on tomcat, webapps) dist (depends on main, webapps) dist-zip (depends on dist) all (depends on clean, dist) webapps (depends on prepare) dist (depends on main, webapps) dist-zip (depends on dist) all (depends on clean, dist) main (depends on tomcat, webapps) dist (depends on main, webapps) dist-zip (depends on dist) all (depends on clean, dist) targets
This is actually the output from the Ant build file included with Apache's Tomcat. The list of top-level targets is shown at the root level, and dependent targets are indented and listed next. The targets shown in parentheses list what each target depends on. This tree view is created by recursively analyzing the dependencies, which appear in the Ant build file as follows:
<target name="all" depends="clean,dist">
Figure 3-1 shows a portion of the output in a web browser. A table listing all targets follows the dependency graph. The output concludes with a table of all global properties defined in the Ant build file.
The comma-separated list of dependencies presents a challenge that is best handled through recursion. For each target in the build file, it is necessary to print a list of targets that depend on that target. It is possible to have many dependencies, so an Ant build file may contain a <target> that looks like this:
<target name="docs" depends="clean, prepare.docs, compile">
In the first prototype of the Antdoc stylesheet, the algorithm to print the dependency graph uses simple substring operations to determine if another target depends on the current target. This turns out to be a problem because two unrelated targets might have similar names, so some Ant build files cause infinite recursion in the stylesheet. In the preceding example, the original prototype of Antdoc says that "docs" depends on itself because its list of dependencies contains the text prepare.docs.
In the finished version of Antdoc, the list of target dependencies is cleaned up to remove spaces and commas. For example, "clean, prepare.docs, compile" is converted into "|clean|prepare.docs|compile|". By placing the pipe (|) character before and after every dependency, it becomes much easier to locate dependencies by searching for strings.
The complete XSLT stylesheet is listed in Example 3-14. Comments within the code explain what happens in each step. To use this stylesheet, simply invoke your favorite XSLT processor at the command line, passing antdoc.xslt and your Ant build file as parameters.
<?xml version="1.0" encoding="UTF-8"?>
<!--
**************************************************************
** Antdoc v1.0
**
** Written by Eric Burke ([email protected])
**
** Uses XSLT to generate HTML summary reports of Ant build
** files.
***********************************************************-->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"
doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"
doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
indent="yes" encoding="UTF-8"/>
<!-- global variable: the project name -->
<xsl:variable name="projectName" select="/project/@name"/>
<xsl:template match="/">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Ant Project Summary -
<xsl:value-of select="$projectName"/></title>
</head>
<body>
<h1>Ant Project Summary</h1>
<xsl:apply-templates select="project"/>
</body>
</html>
</xsl:template>
<!--
***************************************************************
** "project" template
************************************************************-->
<xsl:template match="project">
<!-- show the project summary table, listing basic info
such as name, default target, and base directory -->
<table border="1" cellpadding="4" cellspacing="0">
<tr><th colspan="2">Project Summary</th></tr>
<tr>
<td>Project Name:</td>
<td><xsl:value-of select="$projectName"/></td>
</tr>
<tr>
<td>Default Target:</td>
<td><xsl:value-of select="@default"/></td>
</tr>
<tr>
<td>Base Directory:</td>
<td><xsl:value-of select="@basedir"/></td>
</tr>
</table>
<!-- show all target dependencies as a tree -->
<h3>Target Dependency Tree</h3>
<xsl:apply-templates select="target[not(@depends)]" mode="tree">
<xsl:sort select="@name"/>
</xsl:apply-templates>
<p/>
<!-- Show a table of all targets -->
<table border="1" cellpadding="4" cellspacing="0">
<tr><th colspan="3">List of Targets</th></tr>
<tr>
<th>Name</th>
<th>Dependencies</th>
<th>Description</th>
</tr>
<xsl:apply-templates select="target" mode="tableRow">
<xsl:sort select="count(@description)" order="descending"/>
<xsl:sort select="@name"/>
</xsl:apply-templates>
</table>
<p/>
<xsl:call-template name="globalProperties"/>
</xsl:template>
<!--
***************************************************************
** Create a table of all global properties.
************************************************************-->
<xsl:template name="globalProperties">
<xsl:if test="property">
<table border="1" cellpadding="4" cellspacing="0">
<tr><th colspan="2">Global Properties</th></tr>
<tr>
<th>Name</th>
<th>Value</th>
</tr>
<xsl:apply-templates select="property" mode="tableRow">
<xsl:sort select="@name"/>
</xsl:apply-templates>
</table>
</xsl:if>
</xsl:template>
<!--
***************************************************************
** Show an individual property in a table row.
************************************************************-->
<xsl:template match="property[@name]" mode="tableRow">
<tr>
<td><xsl:value-of select="@name"/></td>
<td>
<xsl:choose>
<xsl:when test="not(@value)">
<xsl:text disable-output-escaping="yes">&nbsp;</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="@value"/>
</xsl:otherwise>
</xsl:choose>
</td>
</tr>
</xsl:template>
<!--
***************************************************************
** "target" template, mode=tableRow
** Print a target name and its list of dependencies in a
** table row.
************************************************************-->
<xsl:template match="target" mode="tableRow">
<tr valign="top">
<td><xsl:value-of select="@name"/></td>
<td>
<xsl:choose>
<xsl:when test="@depends">
<xsl:call-template name="parseDepends">
<xsl:with-param name="depends" select="@depends"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>-</xsl:otherwise>
</xsl:choose>
</td>
<td>
<xsl:if test="@description">
<xsl:value-of select="@description"/>
</xsl:if>
<xsl:if test="not(@description)">
<xsl:text>-</xsl:text>
</xsl:if>
</td>
</tr>
</xsl:template>
<!--
***************************************************************
** "parseDepends" template
** Tokenizes and prints a comma separated list of dependencies.
** The first token is printed, and the remaining tokens are
** recursively passed to this template.
************************************************************-->
<xsl:template name="parseDepends">
<!-- this parameter contains the list of dependencies -->
<xsl:param name="depends"/>
<!-- grab everything before the first comma,
or the entire string if there are no commas -->
<xsl:variable name="firstToken">
<xsl:choose>
<xsl:when test="contains($depends, ',')">
<xsl:value-of
select="normalize-space(substring-before($depends, ','))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="normalize-space($depends)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="remainingTokens"
select="normalize-space(substring-after($depends, ','))"/>
<!-- output the first dependency -->
<xsl:value-of select="$firstToken"/>
<!-- recursively invoke this template with the remainder
of the comma separated list -->
<xsl:if test="$remainingTokens">
<xsl:text>, </xsl:text>
<xsl:call-template name="parseDepends">
<xsl:with-param name="depends" select="$remainingTokens"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<!--
***************************************************************
** This template will begin a recursive process that forms a
** dependency graph of all targets.
************************************************************-->
<xsl:template match="target" mode="tree">
<xsl:param name="indentLevel" select="'0'"/>
<xsl:variable name="curName" select="@name"/>
<div style="text-indent: {$indentLevel}em;">
<xsl:value-of select="$curName"/>
<!-- if the 'depends' attribute is present, show the
list of dependencies -->
<xsl:if test="@depends">
<xsl:text> (depends on </xsl:text>
<xsl:call-template name="parseDepends">
<xsl:with-param name="depends" select="@depends"/>
</xsl:call-template>
<xsl:text>)</xsl:text>
</xsl:if>
</div>
<!-- set up the indentation -->
<xsl:variable name="nextLevel" select="$indentLevel+1"/>
<!-- search all other <target> elements that have "depends"
attributes -->
<xsl:for-each select="../target[@depends]">
<!-- Take the comma-separated list of dependencies and
"clean it up". See the comments for the "fixDependency"
template -->
<xsl:variable name="correctedDependency">
<xsl:call-template name="fixDependency">
<xsl:with-param name="depends" select="@depends"/>
</xsl:call-template>
</xsl:variable>
<!-- Now the dependency list is pipe (|) delimited, making
it easier to reliably search for substrings. Recursively
instantiate this template for all targets that depend
on the current target -->
<xsl:if test="contains($correctedDependency,concat('|',$curName,'|'))">
<xsl:apply-templates select="." mode="tree">
<xsl:with-param name="indentLevel" select="$nextLevel"/>
</xsl:apply-templates>
</xsl:if>
</xsl:for-each>
</xsl:template>
<!--
***************************************************************
** This template takes a comma-separated list of dependencies
** and converts all commas to pipe (|) characters. It also
** removes all spaces. For instance:
**
** Input: depends="a, b,c "
** Ouput: |a|b|c|
**
** The resulting text is much easier to parse with XSLT.
************************************************************-->
<xsl:template name="fixDependency">
<xsl:param name="depends"/>
<!-- grab everything before the first comma,
or the entire string if there are no commas -->
<xsl:variable name="firstToken">
<xsl:choose>
<xsl:when test="contains($depends, ',')">
<xsl:value-of
select="normalize-space(substring-before($depends, ','))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="normalize-space($depends)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- define a variable that contains everything after the
first comma -->
<xsl:variable name="remainingTokens"
select="normalize-space(substring-after($depends, ','))"/>
<xsl:text>|</xsl:text>
<xsl:value-of select="$firstToken"/>
<xsl:choose>
<xsl:when test="$remainingTokens">
<xsl:call-template name="fixDependency">
<xsl:with-param name="depends" select="$remainingTokens"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:text>|</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
One of the first things this stylesheet does is set the output method to "xml" because the resulting page will be XHTML instead of HTML. The doctype-public and doctype-system are required for valid XHTML and indicate the strict DTD in this case:
<xsl:output method="xml" doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" indent="yes" encoding="UTF-8"/>
The remaining XHTML requirement is to declare the namespace of the <html> element:
<xsl:template match="/"> <html xmlns="http://www.w3.org/1999/xhtml"> ... </html> </xsl:template>
Because of these XSLT elements, the result tree will contain the following XHTML:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> ... </html>
The most interesting and difficult aspect of this stylesheet is its ability to display the complete dependency graph for all Ant build targets. The first step is to locate all of the targets that do not have any dependencies. As shown in Example 3-13, these targets are named clean, prepare, and targets for the Tomcat build file. They are selected by looking for <target> elements that do not have an attribute named depends:
<!-- show all target dependencies as a tree --> <h3>Target Dependency Tree</h3> <xsl:apply-templates select="target[not(@depends)]" mode="tree"> <xsl:sort select="@name"/> </xsl:apply-templates>
The [not(@depends)] predicate will refine the list of <target> elements to include only those that do not have an attribute named depends. The <xsl:apply-templates> will instantiate the following template without any parameters:
<xsl:template match="target" mode="tree"> <xsl:param name="indentLevel" select="'0'"/> <xsl:variable name="curName" select="@name"/>
If you refer to Example 3-14, you will see that this is the second-to-last template in the stylesheet. Since it is broken up into many pieces here, you may find it easier to refer to the original code as this description progresses. Since the indentLevel parameter is not specified, it defaults to '0', which makes sense for the top-level targets. As this template is instantiated recursively, the level of indentation increases. The curName variable is local to this template and contains the current Ant target name. Lines of text are indented using a style attribute:
<div style="text-indent: {$indentLevel}em;">
CSS is used to indent everything contained within the <div> tag by the specified number of em s.[13] The value of the current target name is then printed using the appropriate indentation:
[13] An em is approximately equal to the width of a lowercase letter "m" in the current font.
<xsl:value-of select="$curName"/>
If the current <target> element in the Ant build file has a depends attribute, its dependencies are printed next to the target name as part of the report. The parseDepends template handles this task. This template, also part of Example 3-14, is instantiated using <xsl:call-template>, as shown here:
<xsl:if test="@depends"> <xsl:text> (depends on </xsl:text> <xsl:call-template name="parseDepends"> <xsl:with-param name="depends" select="@depends"/> </xsl:call-template> <xsl:text>)</xsl:text> </xsl:if>
To continue with the dependency graph, the target template must instantiate itself recursively. Before doing this, the indentation must be increased. Since XSLT does not allow variables to be modified, a new variable is created:
<xsl:variable name="nextLevel" select="$indentLevel+1"/>
When the template is recursively instantiated, nextLevel will be passed as the value for the indentLevel parameter:
<xsl:apply-templates select="." mode="tree"> <xsl:with-param name="indentLevel" select="$nextLevel"/> </xsl:apply-templates>
The remainder of the template is not duplicated here, but is emphasized in Example 3-14. The basic algorithm is as follows:
Use <xsl:for-each> to select all targets that have dependencies.
Instantiate the "fixDependency" template to replace commas with | characters.
Recursively instantiate the "target" template for all targets that depend on the current target.
The final template in the Antdoc stylesheet is responsible for tokenizing a comma-separated list of dependencies, inserting pipe (|) characters between each dependency:
<xsl:template name="fixDependency"> <xsl:param name="depends"/>
The depends parameter may contain text such as "a, b, c." The template tokenizes this text, producing the following output:
|a|b|c|
Since XSLT does not have an equivalent to Java's StringTokenizer class, recursion is required once again. The technique is to process the text before the first comma then recursively process everything after the comma. The following code assigns everything before the first comma to the firstToken variable:
<xsl:variable name="firstToken"> <xsl:choose> <xsl:when test="contains($depends, ',')"> <xsl:value-of select="normalize-space(substring-before($depends, ','))"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="normalize-space($depends)"/> </xsl:otherwise> </xsl:choose> </xsl:variable>
If the depends parameter contains a comma, the substring-before( ) function locates the text before the comma, and normalize-space( ) trims whitespace. If no commas are found, there must be only one dependency.
Next, any text after the first comma is assigned to the remainingTokens variable. If there are no commas, the remainingTokens variable will contain an empty string:
<xsl:variable name="remainingTokens" select="normalize-space(substring-after($depends, ','))"/>
The template then outputs a pipe character followed by the value of the first token:
<xsl:text>|</xsl:text> <xsl:value-of select="$firstToken"/>
Next, if the remainingTokens variable is nonempty, the fixDependency template is instantiated recursively. Otherwise, another pipe character is output at the end:
<xsl:choose> <xsl:when test="$remainingTokens"> <xsl:call-template name="fixDependency"> <xsl:with-param name="depends" select="$remainingTokens"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:text>|</xsl:text> </xsl:otherwise> </xsl:choose>
Ideally, these descriptions will help clarify some of the more complex aspects of this stylesheet. The only way to really learn how this all works is to experiment, changing parts of the XSLT stylesheet and then viewing the results in a web browser. You should also make use of a command-line XSLT processor and view the results in a text editor. This is important because browsers may skip over tags they do not understand, so you might not see mistakes until you view the source.
Copyright © 2002 O'Reilly & Associates. All rights reserved.