View RSS Feed

My Java Tips

ANT's best practices

Rate this Entry
by , 11-07-2011 at 06:28 PM (2031 Views)
ANT makes building complex Java applications easier. But many people argue that managing ANT scripts is itself a cumbersome task and involves a lot of effort. In the next few posts, I will talk about how one can adopt best practices for creating and managing ANT scripts that will make the life of developers and configuration managers easier.


First thing is, one should follow a consistent style while writing XML for ANT files. If you format your ANT script, it will be visually appealing and will be more readable. Using a good XML editor that highlights the syntax will also help. One important thing is that you should use meaningful target and property names in the ANT script and you should be consistent with the naming convention.

ANY buildfile can be places anywhere but itís a convention to place the buildfile (build.xml) in the top-level project directory. This will keep things simple and clean. Normally developers expect to find build.xml in this location. This also helps see how relative paths point to different directories in your project tree. Some developers prefer to have more than one build files in case of big and complex project. Even in that case, there should be a primary build file that should manage the other build files. And this primary build file should reside in the top-level project directory.



Build file should be self-documented.

Add target descriptions with targets is the easiest way to accomplish this. To view the help text of each target, use the following command:
ant Ėprojecthelp


description="Compiles code, output goes to the build dir.">

The conventions is to include descriptions for all of the targets that you wish programmers to invoke from the command line. There are some internal targets like generating code and targets that perform intermediate processing. These internal targets are not to be documented. Some developers prefer to define a target named help that prints detailed usage information. Programmers use ant help command to view the help in this case.


description="Display detailed usage information">
Detailed help...

There should be a target defined that removes all generated files and directories and brings everything back to its original state. This target is usually referred as clean target. The files remaining after a clean execution should be those found in version control.


description="Destroys all generated files and dirs.">





Build file should be self-documented.

Add target descriptions with targets is the easiest way to accomplish this. To view the help text of each taret, use the following command:
ant Ėprojecthelp


description="Compiles code, output goes to the build dir.">

The conventions is to include descriptions for all of the targets that you wish programmers to invoke from the command line. There are some internal targets like generating code and targets that perform intermediate processing. These internal targets are not to be documented. Some developers prefer to define a target named help that prints detailed usage information. Programmers use ant help command to view the help in this case.


description="Display detailed usage information">
Detailed help...

There should be a target defined that removes all generated files and directories and brings everything back to its original state. This target is usually referred as clean target. The files remaining after a clean execution should be those found in version control.


description="Destroys all generated files and dirs.">

Here I have discussed how dependency management can make build process faster and more manageable.


Build script should have dependency management to keep thing simple and fast. Complex Java applications comprise of various tiers like a Swing GUI, a web interface, an EJB tier, and shared utility code. You need to clearly define which Java packages belong to which layer of the system. If you donít do this, then hundreds or thousands of files will be compiled each time you change something, which will slow down the build process. If you clearly define the dependency in your build script, then changing the layout of a GUI panel will not cause you to recompile your servlets and EJBs. This is what we require.

A good practice is to compile large projects in stages. For instance first compile shared utility code and place the results into a JAR file. Then, compile a higher level portion of the project against the JAR file(s) created in the first step. Repeat this process until you reach the highest level of the system. So itís a like a chain and you go from bottom to up. This approach of building in stages enforces dependency management.

Another good practice is to define and reuse paths. A buildfile makes more sense if paths are defined once in a central location and we can use these paths throughout the buildfile. Its like declaring and defining the variable at the start of the Java class and using them thought the class. Time for an example:
<project name="sample" default="compile" basedir=".">
<path id="classpath.common">
<pathelement location="${jdom.jar.withpath}"></pathelement>
...etc

</path>
<path id="classpath.client">
<pathelement location="${guistuff.jar.withpath}"></pathelement>
<pathelement location="${another.jar.withpath}"></pathelement>
<!-- reuse the common classpath -->
<path refid="classpath.common"></path>
</path>
<target name="compile.common" depends="prepare">
<javac destdir="${dir.build}" srcdir="${dir.src}">
<classpath refid="classpath.common">
<include name="com/oreilly/common/**"></include>
</classpath></javac>
</target>
</project></pre>



Defining proper target dependency is also very helpful. Lets discuss this with a simple example. There is target call dist which depends on the jar target. The jar target depends on compile target which depends on prepare.

dist DEPENDS_ON jar DEPENDS_ON compile DEPENDS_ON prepare

Ant buildfiles define a dependency graph, which must be carefully defined and maintained.

Omitting dependencies in an effort to "optimize" the build is another common mistake. The right thing is to review the build files periodically to ensure that the dependencies are right and valid. Its not a good idea to compile a code that is not needed to be compiled. For instance, use made changes in the GUI and executed a target to compile the GUI target. The GUI target also compiled the EJB tier which was not required.

One should use properties for configurations. What should be defined as property? Any information that needs to be configured, or that might change, should be defined as an Ant property. The values that are used in more than one place in the buildfile can be defined as property. Many developers are confused about where to define these properties. You have following two choices:

Define the properties at the top of a buildfile.
Define the properties in a standalone properties file. This provides maximum flexibility.

The example presented below shows how to define properties in a build file:

<project name="sample" default="compile" basedir=".">
<property name="dir.build" value="build"></property>
<property name="dir.src" value="src"></property>
<property name="jdom.home" value="../java-tools/jdom-b8"></property>
<property name="jdom.jar" value="jdom.jar"></property>
<property name="jdom.jar.withpath"></property> value="${jdom.home}/build/${jdom.jar}"/&gt;
etc...
</project>




Try your best not to refer to external paths and libraries. Also donít depend on the programmer's CLASSPATH setting. Use relative paths throughout the build file and define your own paths. This will make your build file independent and portable. Itís a bad idea to refer to an explicit path such as C:\java\tools because then the other developers will have to follow the same directory structure in order to execute the build file.
If you are deploying an open source project, provide a distribution that includes all JAR files necessary to compile the code. And for internal projects, dependent JAR files should be managed under version control and checked out to a well-known location.

You may define path as properties when you do have to refer to external paths. In this way, the programmers can override those settings to conform to their own machines. Refer to environment variables is done as follows:

<property environment="env"></property>
<property name="dir.jboss" value="${env.JBOSS_HOME}"></property>

It is common to use Ant to create WAR, JAR, ZIP, and EAR files. These files generally require a particular internal directory structure. Its normal that the archive files directory structure wonít match directory structure of your source code and build environment.




A common practice is to write an Ant target that copies a bunch of files to a temporary holding area using the desired directory structure, and then create the archive from there. This approach will work but its not that efficient.

A better approach is to use a zipfileset. This allows you to select files from any location and place them in the archive file using a different directory structure. Here is a small example:

<ear earfile="${dir.dist.server}/payroll.ear"> appxml="${dir.resources}/application.xml"&gt;
<fileset dir="${dir.build}" includes="commonServer.jar">
<fileset dir="${dir.build}">
<include name="payroll-ejb.jar"></include>
</fileset>
<zipfileset dir="${dir.build}" prefix="lib">
<include name="hr.jar"></include>
<include name="billing.jar"></include>
</zipfileset>
<fileset dir=".">
<include name="lib/jdom.jar"></include>
<include name="lib/log4j.jar"></include>
<include name="lib/ojdbc14.jar"></include>
</fileset>
<zipfileset dir="${dir.generated.src}" prefix="META-INF">
<include name="jboss-app.xml"></include>
</zipfileset>
</fileset></ear>

Review the code above. A JAR files are placed in the lib directory of the EAR. The hr.jar and billing.jar files are copied from our build directory, therefore we use zipfileset to move them to the lib directory inside of the EAR. The prefix attribute specifies the destination directory in the EAR.

Testing the build file is important. You donít want other coders complaining about your ANT script.


Lets assume that the build file has clean and compile targets. In this case plan the test as follows:

> Type ant clean
> Type ant compile
> Type ant compile again. This should do nothing and if files compile second time then it means that there is something wrong with your buildfile.

A build file should perform work only when input files change as compared to corresponding output files. If your build file compiles, copies, or performs some other work when it is not necessary to perform the work, then it means that your build file is inefficient. This is inefficient and should be dealt with properly.

Another tips is to avoid platform specific Ant wrappers. For whatever reason, some people like to ship their products with simple batch files or scripts called something like compile. Its not a good idea to include a platform-specific script that does nothing but invoke Ant. It is possible that you will fail to provide scripts for every operating system which is not you intend.

I hope this will help you write better ANT scripts for your build process.

Submit "ANT's best practices" to Facebook Submit "ANT's best practices" to Digg Submit "ANT's best practices" to del.icio.us Submit "ANT's best practices" to StumbleUpon Submit "ANT's best practices" to Google

Tags: None Add / Edit Tags
Categories
Ant , XML

Comments