Project Management Using Ant


In the Java world, Ant has established itself as the premier project management tool. To put a context to the situation, before Ant there was Make. Make is a traditional build tool. However, Make is a very complicated piece of software that is not very simple to use. Over time, many add-on utilities like configure and autoconf complicated the project management process even more.

Ant does away with all that by creating a new kind of project management infrastructure. Central to Ant is an XML file. The XML file contains a number of XML elements that represent instructions to do something. Ant has an XML configuration file in the Digester package that loads a number of properties and attributes. The properties and attributes are instructions to compile a Java file, to copy files, or to do any other task that is integrated as an instruction to the Ant infrastructure. The core of Ant is relatively small. The large number of instructions is what gives Ant its power and flexibility.

An Ant Hello World Example

Listing 11.7 shows a very simple Ant script that prints out "Hello World." Typically, Ant scripts are saved to the name build.xml , which is the default name.

Listing 11.7
start example
 <project name="Hello World" default="build" basedir="."> <target name="build"> <echo message="Hello World"/> </target> </project> 
end example
 

Listing 11.7 contains three XML elements: project , target and echo . Each Ant script needs a root XML element project . The XML element project usually has three attributes: name , default , and basedir . The attribute name is the friendly descriptor of the project. The attribute default defines the default target of the project. The attribute basedir defines the initial starting directory of the project. The value for the attribute basedir is a single period, which means the current directory where the Ant file is located.

The attribute default references the default target of the application. A target is a reference to a set of build instructions. Consider a target like a method in a class file. When a default target is specified, it is like saying, for this program, please first call the default method. In Listing 11.7, the default target references the target build . The target build is identified by the XML element target , with a name attribute value of build . Once the target has been found, all of the child elements within the target are executed like a script. In Listing 11.7, the XML element echo is an instruction to output some text. The text that is output is defined by the value of the attribute message . The instruction in Ant terminology is called an Ant task.

Using Variables

An Ant file can reference variables, which can be used like variables in a Java program. Ant variables can be created within the Ant project file, within a properties file, or within the environment variables like PATH . Listing 11.8 is an example that defines and outputs a variable.

Listing 11.8
start example
 <project name="Hello World" default="build" basedir="."> <property name="variable" value="hello" /> <target name="build"> <echo message="variable value=${variable}" />  </target> </project> 
end example
 

In Listing 11.8, a child XML element property is below the XML element project . The XML element property is a task used to define a property, which is akin to a variable. The attribute name defines the variable identifier. The attribute value defines the value of the variable. The XML element echo references the variable using the identifier within the curly brackets. The dollar sign in front of the open curly bracket indicates that a variable is being defined. The variable reference can be used only in the context of an attribute value, as is shown in Listing 11.8.

A property can be used to reference the environment variables, as shown in Listing 11.9.

Listing 11.9
start example
 <project name="Hello World" default="build" basedir="."> <property environment="env" /> <target name="build"> <echo message="Path=${env.PATH}" />  </target> </project> 
end example
 

In Listing 11.9, the XML element property has only one attribute: environment The attributes name and value are not applicable . The attribute environment's value env is prepended to whenever a specific environment variable is referenced. The attribute message has a contained reference to the environment variable PATH . The identifier env.PATH would denote that variables can be object-oriented since the period is used to reference methods or properties. However, that is not correct since a variable could be defined with a period, like the example module.variable . When the environment variable uses the period, it is an exception to the rule.

Another way of referencing variables is to define the individual variables in a properties file and then include the properties file in the Ant build file. Listing 11.10 is a sample properties file.

Listing 11.10
start example
 MyVariable = This is my value 
end example
 

Listing 11.10 is included in the Ant build file defined in Listing 11.11.

Listing 11.11
start example
 <project name="Hello World" default="build" basedir="."> <property file="sample.properties" /> <target name="build"> <echo message="Property Variable =${MyVariable}" />  </target> </project> 
end example
 

In Listing 11.11, the XML element property has only one attribute: file . The attribute file references the name of a file that is to be included. The properties file defined in Listing 11.10 contains one property, MyVariable, which is assigned a buffer of "This is my value" . When the property is referenced, it is referenced like any other Ant variable, as shown by the attribute message .

Including Other XML Elements

When you're programming using the C or C++ programming language, there is an instruction called #include . Using the #include instruction, you can include the contents of one file in another file. Java does not have that ability, which is generally not a problem. However, with Ant files, and XML files in particular, this is never a problem. Using XML Document Type Definitions (DTDs), you can define generic targets in another file and then have then included for further referencing. Listing 11.12 defines a fragment of an XML document.

Listing 11.12
start example
 <property name="dtd.identifier" value="value in the DTD"/> <property name="another.identifier" value="other value"/> 
end example
 

In Listing 11.12, an XML fragment is defined because there is no single root XML element. Listing 11.13 is an XML document that includes the XML fragment defined in Listing 11.12.

Listing 11.13
start example
 <!DOCTYPE project [ <!ENTITY common SYSTEM "file:XmlFragment.xml"> ]> <project name="Hello World" default="build" basedir="."> &common; <target name="build"> <echo message="DTD Value is ${dtd.identifier}"/> </target> </project> 
end example
 

In Listing 11.13, the DTD is defined using the < ! and ] > characters . Within the DTD declaration is an ENTITY declaration. The ENTITY declaration is called common and references the file "file:XmlFragment.xml" . This declaration means that whenever the identifier & common; is used in the XML document, the contents of the file XmlFragment.xml are inserted. This means that in Listing 11.13, even though the & commons; identifier exists, when the XML document is processed , the XML fragment is inserted and made part of the parent XML document as if it had been typed directly into the XML document. No other XML technique available allows a developer to inject one piece of XML into another.

Task Definition: Compiling Java Files

Thus far in our examples, the Ant build script file has done nothing interesting other than output text to the console. Originally, Ant was used to compile Java class files, which is shown in Listing 11.14.

Listing 11.14
start example
 <target name="default-compile" depends="init"> <javac classpath="${standard-jars}:${build}" srcdir="${src}" destdir="${build}" debug="${debug}" deprecation="on" >  </javac>  </target> 
end example
 

Listing 11.14 contains the XML element javac , which is the Ant task for the tool javac (the Java compiler). The javac task is an example of how a tool such as the Java compiler command line tool can be converted into an Ant task. A specific Ant task implements the Ant-required interfaces and bridges the XML attributes to the Java compiler. The Ant manual that comes with every distribution of Ant contains specifics of the javac target. What should be noticed is that almost every attribute is defined by some variable. This is typically the case because it allows a developer to custom-define the actual values.

In the Ant infrastructure are tasks to do basically whatever is necessary. The simplest is compiling the Java class files. However, there are tasks to create an archive, or tasks to generate a jar file. The types of task range from core to optional to user -added tasks. Core tasks are tasks that are distributed with the Ant distribution. Optional tasks are those that are distributed in a jar that can be download and installed where the Ant distribution resides. User-added tasks are tasks that are defined somewhere on the Java classpath and then defined as additional tasks in the Ant build file.

Handling a Set of Files

When the javac task executes, the source files defined by the attribute srcdir in Listing 11.14 are compiled to the directory defined by the attribute destdir . In a traditional build environment, you compile the files by specifying them individually and compiling each file. Ant is more clever in that files are sets that operations are executed on. Compare this to how the programming languages Java and SQL handle data. Java references another object individually, even if there is a collection. SQL, on the other hand, references data as a set. Referencing an individual record is more complicated in SQL than referencing an entire set. The same could be said about Ant. In Ant, data is referenced using a set. Consider Listing 11.15, which copies an individual file to a specific directory.

Listing 11.15
start example
 <target name="deploy"> <copy file="afile.txt" todir="${axisDirectory}"/> </target> 
end example
 

In Listing 11.15, the XML element copy has two attributes: file and todir . The attribute file selects a specific file. The attribute todir defines the directory to where the file is to be copied . To copy two files, another XML element copy would have to be added. In the Ant paradigm, the attribute file can be replaced with a fileset as defined in Listing 11.16.

Listing 11.16
start example
 <target name="deploy"> <copy todir="${axisDirectory}"> <fileset dir="${build}"> <exclude name="*.class" /> </fileset> </copy> </target> 
end example
 

In Listing 11.16, the XML element fileset is not a task but an inherent operation that exists within Ant. The XML element fileset has an attribute dir , which specifies the directory. The directory, including any subdirectories that are encountered , is iterated. Whatever is found is added into the file set. A child XML element exclude exists, so the files defined by the attribute name are removed from the file set. Notice how the name attribute has a value of *.class , which uses an asterisk wildcard character to match anything. When the name attribute is evaluated, all files that end with the extension class are removed from the file set. The remaining files are then passed to the copy command.

The file set defined in Listing 11.16 added all files that were encountered. You can specifically include files, as shown in Listing 11.17.

Listing 11.17
start example
 <fileset dir="${build}"> <include name="**/*.java"/> <exclude name="**/*Main*"/> </fileset> 
end example
 

In Listing 11.17, the XML element include selects all files that have a Java extension and creates a file set. Then, the XML element exclude removes from the file set all of the files that contain the text Main. There are other types of sets, such as directory sets. However, for the vast majority of tasks, the file set is the most useful construct. A file set is used in three different contexts. The first context, which we have already covered, is the copying, moving, or deleting of files.

Another context is definition of the classpath when you're executing any task that requires a classpath. The task junit shown in Listing 11.18 is an example.

Listing 11.18
start example
 <junit dir="./" printSummary="yes"  fork="true" haltonerror="true"> <sysproperty key="basedir" value="src/test"></sysproperty> <formatter type="xml"></formatter> <formatter usefile="true" type="plain"></formatter> <classpath> <fileset dir="lib"> <include name="*.jar"></include> </fileset> <pathelement location="target/${final.name}.jar" /> <pathelement path="${testclassesdir}" /> </classpath> <batchtest todir="${testreportdir}"> <fileset dir="src/test"> <include name="**/*Test*.java" /> </fileset> </batchtest> </junit> 
end example
 

Listing 11.18 is an excellent example of using fileset to reference paths that cannot be defined in the attributes. The XML element junit is a task used to run JUnit tasks defined in JUnit section earlier in this chapter. In Listing 11.18, are two XML elements that use file sets: classpath and batchtest . The XML element classpath defines a Java classpath for the task junit to use. The classpath has been split into three parts . In command line terms, this would be the same as putting three pieces of the Java classpath into one overall path. The XML element fileset, which we have already explained, shows how to include a number of jar files that exist in a specific directory. The XML element pathelement is an individual reference to either a jar file or a path that contains a number of classes. For the XML element batchtest, a file set is used to reference the Java test files.

Another way of referencing a Java classpath is to use a reference ID, a file set identified somewhere in the build script that is referenced by a classpath or other task that requires a file set. Listing 11.19 shows how to reference file sets.

Listing 11.19
start example
 <project name="Hello World" default="build" basedir="."> <path id="compile.classpath"> <fileset dir="lib"> <include name="*.jar"></include> </fileset> <pathelement location="${build.home}/classes"/> </path> <target name="build"> <javacsrcdir="${source.home}"  destdir="${build.home}/classes"  debug="${compile.debug}"  deprecation="${compile.deprecation}"  optimize="${compile.optimize}"> <classpath refid="compile.classpath"/> </javac> </target> </project> 
end example
 

In Listing 11.19, the file set is defined by the XML element path , which has one attribute, id . The attribute id is the identifier that is used to uniquely identify the file set. Then, to use the file set, it is referenced using the attribute refid . In the case of Listing 11.19, the reference is part of the XML element classpath . The advantage of using file sets is that you can reference a file set multiple times without having to explicitly type out the file set. When the file set changes, so do all of the places where the reference is used.

We used the wildcard asterisk character to select whatever files are found. However, you can select the files using various techniques. Table 11.3 shows the different way matches can be made.

Table 11.3: Different ways of making a selection in a file set.

Pattern

File Set That Is Selected

*.java

Select all the files from the current directory that have the extension java. Matches: something.java Fails: something.javanext

??.java

Select all the files from the current directory that have exactly two letters for a name and have an extension of Java. Matches: aa.java Fails: aaa.java

**/*.java

Select all files from the current directory and subdirectories that have the extension java. Matches: /something/dir/something.java Fails: /something/another.txt

**/src/*.java

From the current directory find all of the directories that have src child directory. And from the src directory select all of the files that have an extension of java. Matches: /something/dir/src/something.java Fails: /something/dir/src/dir/something.java

**/src/**/*.java

From the current directory find all of the directories that have src child directory. From that directory select all of the child directories that contain java files Matches: /something/src/dir/something.java Fails: /something/dir/something.java

Defining Custom Tasks and Paths

In the Axis toolkit are a number of helper routines to generate WSDL and to generate Java files from WSDL. The functionality is exposed from the axis-ant.jar file. To use the additional Ant tasks, you have to add the jar file to the classpath and then reference it in the build script, as shown in Listing 11.20.

Listing 11.20
start example
 <taskdef name="wsdl2java" classname="org.apache.axis.tools.ant.wsdl.Wsdl2javaAntTask" classpath="${standard-jars}:${build}" /> <taskdef name="java2wsdl" classname="org.apache.axis.tools.ant.wsdl.Java2WsdlAntTask" classpath="${standard-jars}:${build}" /> 
end example
 

In Listing 11.20, there are two XML elements with the same name, taskdef . The task taskdef is used to load an Ant-compliant task and make it available as another task. In the case of Listing 11.20, the names of the other tasks are identified by the attribute name. The attribute classname identifies the class that implements the Ant interfaces for Ant task purposes. The attribute classpath identifies the Java classpath used when loading the value of the attribute classname .

Listing 11.21 shows how to use either of the tasks defined in Listing 11.20.

Listing 11.21
start example
 <wsdl2java output="${project-src}/${package}/gen-wsdl-2-java" deployscope="application" testcase="false" verbose="true" skeletondeploy="false" serverside="true" helpergen="true" url="${project-src}/${package}/webservice.wsdl" > </wsdl2java> <java2wsdl classname="${classname}" output="${project-src}/${package}/webservice.wsdl" location="${location}" namespace="${namespace}" > <mapping namespace="${namespace}" package="${package}.def" />  </java2wsdl> 
end example
 

Calling Other Ant Files or Targets

When setting up an Ant build file, you need to be able to execute multiple targets. The reason is that, for most build processes, there is an initialization, compilation, and packaging phase. Maybe all phases will be called; maybe not. Regardless, the idea is to be able to split up the build process and be able to call individual targets. There are three ways of calling another target: using dependencies, making a target method call, and calling a target into another file.

The easiest way of chaining targets together is to use the dependency attribute shown in Listing 11.22.

Listing 11.22
start example
 <project name="Hello World" default="compile" basedir="."> <target name="init">   <echo message="initialization" /> </target>  <target name="compile" depends="init"> <javac classpath="${standard-jars}:${build}" srcdir="${src}" destdir="${build}" debug="${debug}" deprecation="on" >  </javac>  </target> </project> 
end example
 

In Listing 11.22, the project has a default target of compile . The Ant process will execute the compile target; however, there is the attribute depends with a value of init . The attribute depends tells the Ant process to first process the target init before processing the target compile . The attribute depends is a list of comma-separated targets that are executed first.

The second way to execute an Ant target is to call it like a method call, as shown in Listing 11.23.

Listing 11.23
start example
 <target name="default-java-to-wsdl"> <antcall target="compile"> <param name="src"  value="${project-src}/${package}/def"/> </antcall> <java classpath="${standard-jars}:${build}" classname="org.apache.axis.wsdl.Java2WSDL" fork="on" dir="."> <arg  value="-o${project-src}/${package}/webservice.wsdl"/> <arg value="-l${location}"/> <arg value="-n${namespace}"/> <arg value="-p${package}.def" /> <arg value="${namespace}" /> <arg value="-wAll"/> <arg value="${classname}"/> </java> </target> <target name="java2wsdl"> <antcall target="default-java-to-wsdl" > <param name="package" value="${package-name}" /> <param name="namespace" value="${namespace-name}" /> <param name="classname" value="${gen-classname}" /> <param name="location" value="${endpoint-name}" /> </antcall> </target> 
end example
 

In Listing 11.23, there are two targets: java2wsdl and default-java-to-wsdl . The idea behind this strategy is to define a generic target that compiles a number of Java class files, which are then used to generate a WSDL file. This is the strategy we defined in Chapter 10. The target java2wsdl found at the bottom of Listing 11.23 can execute another target by using the XML element antcall , where the attribute target represents the target to execute. Within the XML element antcall are a number of XML param elements. The XML element param represents a parameter. A parameter is an incorrect term to use here, though, since Ant does not understand the concept of parameters. Instead, consider the XML element param as the definition of properties that are valid only for the scope of the Ant call. We could consider the variables defined in Listing 11.2 to be global variables, and the properties defined by the XML element param to be local variables.

Notice how when the target default-java-to-wsdl is executed an Ant call is made to the compile target. We could have achieved this same effect by setting a dependency to the compile target for the target default-java-to-wsdl . There is a big difference between making an Ant call and using dependencies. In Chapter 10, we used classes to define what the Web Service should look like. Those classes were compiled and a WSDL file generated. The actual implementation of the Web Service would be similar classes, but not the same classes. Therefore, the Web Service definition classes and Web Service implementation classes are located in two entirely different directories. When the Ant process started, some properties were set, including the main sources. The secondary sources, which are the Web Service definition files, would not be set. Therefore, the only solution is to call the compile target explicitly and redefine the location of the sources using an Ant call parameter.

Also shown in Listing 11.23 is the use of the java task. Notice how parameters can be passed to the Java command line using the XML element arg .

The last way of calling another target is to explicitly call a target in another file, as shown in Listing 11.24.

Listing 11.24
start example
 <target name="GenerateWSDLStub"> <ant antfile="build.xml" dir="design" target="deploy"/> <ant antfile="build.xml" dir="server"  target="GenerateWSDLStub"/> <ant antfile="build.xml" dir="client"  target="GenerateWSDLStub"/> </target> 
end example
 

In Listing 11.24, the target GenerateWSDLStub has three child XML ant elements, which are used to execute three different Ant tasks in the same file. Listing 11.24 is shown for illustrational purposes. The XML element ant is a task that calls a target in another file. The ant task has three attributes: antfile , dir , and target . The attribute antfile defines the name of the Ant file to be loaded. The attribute dir defines the directory where the Ant file to be loaded is located. The attribute target defines the target that is called on the Ant file to be loaded. Once the target destination has been called, the target destination is subject to all target-defined dependencies and Ant calls.

Some Tips for When You Use Ant

Here are some tips on building good Ant files:

  • Structure your code so that it is logical. This means that sources should be stored in the source directory, tests in the test directory, and configuration information in the conf directory. You can see multiple good examples of how to do this right in the Jakarta Commons and Commons Sandbox projects.

  • Use property files or Ant properties to define the location of specific jar files. In addition, when defining them, use an abstract syntax similar to that used in Listing 11.25. (Note that Listing 11.25 uses the properties file notation, which could also have been the Ant property task.)

Listing 11.25
start example
 xml-rpc.jar=${jars-dir}/xmlrpc-1.1.jar 
end example
 

In Listing 11.25, the variable xml-rpc.jar is defined and references the specific file xmlrpc-1.1.jar . This is very useful because it allows a developer to abstractly define the jar files that are being used, without defining a specific version number. When a new version of the jar file arrives, only one location needs updating.

  • Define abstract targets that are very generic and that are then referenced using specific dependencies or Ant calls. The abstract target should be defined in a file called target.xml and included into main build.xml file using the DTD technique described earlier.

  • Use obvious identifiers, even if the identifiers are verbose. Using obvious identifiers make it simpler to debug a problem in an Ant script.

  • If there are problems when you are running the Ant program, run the program with the “verbose or “debug flag. In those modes, a large amount of information that can help a developer figure out what is not working is generated. For example, finding dead classpath references is very simple using these modes.




Applied Software Engineering Using Apache Jakarta Commons
Applied Software Engineering Using Apache Jakarta Commons (Charles River Media Computer Engineering)
ISBN: 1584502460
EAN: 2147483647
Year: 2002
Pages: 109

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net