Use Ant for build scripts
Ant is an excellent tool for creating build scripts.
Ant has these characteristics:
- it's cross-platform, and implemented in Java
- it's fairly easy to learn and use
- it uses an XML file to define the steps of a build
- like JUnit, it has become part of the standard tool kit for many Java programmers
- it allows build scripts to be constructed fairly quickly and cleanly (but it does have some quirks)
Every Java programmer should at least be familiar with Ant.
Using Ant, a build file is made up of tasks, which perform operations commonly needed for builds. The list of tasks is extensive:
- compile source files
- copy, rename, and delete files
- run unit tests
- create javadoc
- create jar and zip files
- send emails
- fetch source code from CVS or other source code management tools
- and many other tasks as well
Example Build File
Here's an example of an Ant build file:<project name="test-ant-builds" default='all' basedir="." > <description> Demonstrate the use of the Ant build tool with a simple Java project. </description> <!-- First define properties, datatypes, and default tasks; then define targets. Any Ant tasks placed outside of any target are always executed first. --> <!-- Override default property values with an external properties file, if present. --> <property file='build.properties'/> <!-- Default property values, if not overridden elsewhere: --> <property name='build' location='build' /> <property name='app.version' value='1.0'/> <property name='app.name' value='Example App'/> <property name='distro-name' value='example-app-${app.version}'/> <tstamp><format property='build.time' pattern='yyyy-MM-dd HH:mm:ss'/></tstamp> <path id='compile.classpath'> <fileset dir='lib'> <include name='*.jar'/> </fileset> </path> <!-- Simply extends the compile.classpath with your own compiled classes. --> <path id='run.classpath'> <path refid='compile.classpath'/> <path location='src'/> </path> <fileset id='class.files' dir='src'> <include name='**/*.class'/> </fileset> <fileset id='files.for.jar' dir='src'> <exclude name='**/*.java'/> <exclude name='**/doc-files/'/> </fileset> <fileset id='test.classes' dir='src'> <include name='**/TEST*.java'/> </fileset> <!-- Text files using Ant's '@' syntax are here called template files. --> <fileset id='template.files' dir='.'> <include name='**/*_template.*'/> </fileset> <!-- Inspect the environment, to see if a deployment host is currently running. --> <condition property='deployment.server.running' value='true' else='false'> <socket port='8081' server='127.0.0.1' /> </condition> <!-- A connection to this URL is used when building javadoc. --> <condition property='jdk.javadoc.visible' value='true' else='false'> <http url='http://java.sun.com/javase/6/docs/api/' /> </condition> <echo> Application: ${app.name} ${app.version} Build File : ${ant.file} Run Date : ${build.time} Run by : ${user.name} Build Dir : ${build} Base Dir : ${basedir} Java Home : ${java.home} Deployment host running: ${deployment.server.running} Connected to the web : ${jdk.javadoc.visible} </echo> <echo message='Create build directory, and its subdirectories.'/> <mkdir dir="${build}/javadoc"/> <mkdir dir="${build}/dist"/> <mkdir dir="${build}/templates"/> <!-- Now define the targets, which use the properties and datatypes defined above. --> <target name='clean' description="Delete all build artifacts." > <delete dir='${build}'/> <delete> <fileset refid='class.files'/> </delete> <mkdir dir="${build}/javadoc"/> <mkdir dir="${build}/dist"/> <mkdir dir="${build}/templates"/> </target> <target name='compile' description='Compile source files and place beside source.'> <javac srcdir="src"> <classpath refid='compile.classpath'/> </javac> <!-- Here's a simple way of debugging a path, fileset, or patternset, using its refid: --> <echo>Classpath: ${toString:compile.classpath}</echo> </target> <target name='test' description='Run all JUnit tests.' depends='compile'> <junit haltonfailure='false'> <classpath> <pathelement location="src"/> </classpath> <batchtest> <fileset refid='test.classes'/> </batchtest> <formatter type='brief' usefile='no'/> </junit> </target> <target name='launch' description='Build and run the program.' depends='compile, test'> <java classname='hirondelle.ante.Launcher' classpathref='run.classpath' failonerror='true'> <arg value="Solar System"/> </java> </target> <target name='jar' description='Create a jar file for distribution.' depends='compile'> <jar destfile='${build}/dist/${distro-name}.jar' manifest='MANIFEST.MF' duplicate='preserve'> <fileset refid='files.for.jar'/> <!-- The static manifest.mf file is merged with additional dynamic items, specified here : --> <manifest> <attribute name='Specification-Version' value='${app.version}'/> <attribute name='Specification-Title' value='${app.name}' /> <attribute name='Implementation-Version' value='${app.version}'/> <attribute name='Implementation-Title' value='${app.name}' /> </manifest> </jar> </target> <target name='javadoc' description='Generate javadoc.' > <javadoc use='true' author='true' version='true' overview='overview.html' access='package' sourcepath='src' packagenames='*.*' destdir='${build}/javadoc' windowtitle='${app.name} ${app.version}' noqualifier='java.*:javax.*:com.sun.*' linksource='true' > <classpath refid='compile.classpath'/> <link href='http://java.sun.com/javase/6/docs/api/'/> <header><![CDATA[<h1>${app.name} ${app.version}</h1>]]></header> </javadoc> </target> <target name='text-templates' description='Process template files, and assign values to @ variables.'> <copy overwrite='true' todir='${build}/templates'> <fileset refid='template.files'/> <!-- New files have 'template' removed from their name : --> <globmapper from='*_template.txt' to='*.txt'/> <filterset> <filter token='app.name' value='${app.name}'/> <filter token='app.version' value='${app.version}'/> <filter token='build.time' value='${build.time}'/> </filterset> </copy> </target> <target name='distro-binary' description='Create zip file with executable jar, docs.' depends='jar, javadoc, text-templates'> <zip destfile='${build}/dist/${distro-name}-binary.zip' duplicate='preserve'> <zipfileset dir='${build}/dist/' includes='${distro-name}.jar'/> <zipfileset dir='${build}/javadoc' prefix='javadoc' /> <zipfileset dir='${build}/templates' includes='README.txt'/> </zip> </target> <target name='distro-source' description='Create zip file with project source code.'> <zip destfile='${build}/dist/${distro-name}-src.zip' duplicate='preserve' > <!-- exclude items specific to the author's IDE setup: --> <zipfileset dir='.' excludes='.classpath, .project'/> </zip> </target> <!-- Add mail.jar and activation.jar to your ANT_HOME/lib! --> <!-- Please edit, using values appropriate to your environment. --> <target name='email' > <mail mailhost='smtp.blah.com' mailport='25' user='blah@blah.com' password='blah' messageMimeType='text/html' tolist='blah@whatever.com' from='blah@blah.com' subject='Build completed.' > <message> <![CDATA[ Test email. <P><a href='http://www.google.com'>link</a> ]]> </message> </mail> </target> <target name='all' description='Create all build artifacts.' depends='clean, compile, test, jar, javadoc, distro-binary, distro-source'> <echo>Finished creating all build artifacts.</echo> </target> </project>Example of running the above build script:
C:\johanley\Projects\ant-test>ant -file build.xml all Buildfile: build.xml [echo] [echo] Application: Example Appl 1.1 [echo] Build File : C:\johanley\Projects\ant-test\build.xml [echo] Run Date : 2010-07-21 19:14:11 [echo] Run by : John [echo] Build Dir : C:\@build [echo] Base Dir : C:\johanley\Projects\ant-test [echo] Java Home : C:\jdk1.5.0\jre [echo] Deployment host running: true [echo] Connected to the web : true [echo] [echo] Create build directory, and its subdirectories. [mkdir] Created dir: C:\@build\javadoc [mkdir] Created dir: C:\@build\dist [mkdir] Created dir: C:\@build\templates clean: [delete] Deleting directory C:\@build [delete] Deleting 3 files from C:\johanley\Projects\ant-test\src [mkdir] Created dir: C:\@build\javadoc [mkdir] Created dir: C:\@build\dist [mkdir] Created dir: C:\@build\templates compile: [javac] Compiling 5 source files [echo] Classpath: C:\johanley\Projects\ant-test\lib\junit.jar test: [junit] Testsuite: hirondelle.ante.TESTLauncher [junit] Tests run: 1, Failures: 0, Errors: 0, Time elapsed: 0 sec jar: [jar] Building jar: C:\@build\dist\example-app-1.1.jar javadoc: [javadoc] Generating Javadoc [javadoc] Javadoc execution [javadoc] Loading source files for package hirondelle.ante... [javadoc] Loading source files for package hirondelle.ante.deluvian... [javadoc] Constructing Javadoc information... [javadoc] Standard Doclet version 1.5.0_07 [javadoc] Building tree for all the packages and classes... [javadoc] Generating C:\@build\javadoc\hirondelle/ante/\package-summary.html... [javadoc] Copying file C:\johanley\Projects\ant-test\src\hirondelle\ante\doc-files\VersionHistory.html to directory C:\@build\javadoc\hirondelle\ante\doc-files... [javadoc] Building index for all the packages and classes... [javadoc] Building index for all classes... text-templates: [copy] Copying 1 file to C:\@build\templates distro-binary: [zip] Building zip: C:\@build\dist\example-app-1.1-binary.zip distro-source: [zip] Building zip: C:\@build\dist\example-app-1.1-src.zip all: [echo] Finished creating all build artifacts. BUILD SUCCESSFUL Total time: 6 seconds
For those already familiar with Ant, here are some reminders regarding its use.
General
- tasks placed outside of a target will always be run. You can think of them as being 'global' in scope.
- many tasks are incremental (<javac>, for example), and inspect file timestamp information.
- more than a single target can be specified when calling Ant.
- file separators can go either way in a build file, \ or /. Both are accepted.
- for booleans, on/true/yes means true, and all other values are false.
- sometimes a feature of Ant will not be available in an IDE.
Datatypes
- usually Paths or Filesets
- can be declared within a task, or outside of a task
- the id's assigned to a datatype must be unique across all datatypes
Paths
- similar to PATH and CLASSPATH
- ordered list of directories and files
- can include Filesets
- many Path items include 'path' in their name, but some don't
Filesets
- set of files rooted in a single directory.
- Ant doesn't guarantee the order in which files are processed.
- membership in a Fileset is done with a Selector, based on file name, size, last-modified date, file content, and so on.
- Filesets are resolved when the declaration is first evaluated. After that, they stay the same, while the content of the file system can change.
Patternset
- just a list of file name patterns
- the Patternset doesn't contain files, just file name patterns
- usually embedded inside Filesets, but can be used as a datatype itself
- 'excludes' override 'includes'
- a number of file types are excluded by default (CVS files, etc.)
- the <defaultexcludes> task controls the list of default excludes
- * - 0..N characters
- ? - single character
- ** - used to signify all directories from that point down
- / and \ - path separators
Other datatypes
- Filelist - ordered list of files/directories, that may or may not exist.
- Dirset - directories only. Used only in javadoc task.
- Filterset - alter file content (version number, timestamp), during a copy or move. Uses '@' character to delimit text in the file that needs to be updated.
- FilterReader - again, alters file content. Can be chained together. Flavors: tabs-to-spaces, replace tokens, escape non-ASCII chars, etc.
Properties
Properties are immutable once set.Sources of Properties
- <property> tasks
- properties files referenced by a <property> task
- ANT_OPTS environment variable
- command line arguments, 'ant -Dx=y', or 'ant -propertfile blah.properties'. This style can never be overidden by other settings.
Built-in Properties
- all JRE system properties (user.name, user.home, etc.)
- ant.project.name
- ant.file
- ant.home
- ant.java.version
- ant.version
- basedir
Properties Files
- are typically used to avoid editing the build file itself.
- resolve the ${xyz} syntax when used with <property>, but NOT when used with '-propertyfile' on the Ant command line.
- equate to using <property name='x' value='y'> since the 'location' attribute isn't used, this isn't recommended for files and directories, since this will not resolve relative references. If you do specify a location in a properties file, then it should be absolute, not relative. In addition, you'll need to escape backslashes.
# example properties file app.version = 1.0 # This style also works, and it's not sensitive to order # of appearance in this file # Thus, the contents of this file is manipulated somewhat # after being loaded. # This does NOT work when using 'ant -propertyfile' app.name = My Wonderful App ${app.version} # you need to escape backslashes build.dir = C:\\@build
Reminders and Practices
- properties are immutable once set.
- command line arguments can never be overridden: 'ant -Dx=y', or 'ant -propertfile blah.properties'.
- 'ant -propertyfile blah.properties' doesn't resolve ${xyz} style references within properties files.
- 'ant -projecthelp' lists all targets having a description. It also tests the general syntax of your build file.
- if you use a copy task with the '@' placeholder, ensure overwrite='yes'.
- when running programs with Ant, using fork='true' can often fix problems.
- a common convention for target names is to use dashes, as in 'like-this'
- performing a clean before a build is often desirable. For important or official builds, it's essential.
- instead of setting properties in an init target, you can simply place them near the start of the build file, outside of any target. That will cause them to be executed for all targets anyway.
- consider adding a description to your project and its targets.
- for properties that represent a file or directory, use the 'location' attribute, not 'value'
- don't misuse antcall; to specify order, use the order of dependencies instead
- to avoid editing the build file itself, use properties files. Place <property file='build.properties'> in your build file, to let users easily override values. The build file must work without the build.properties file being present.
- for tasks with many attributes, consider putting each attribute on a separate line
- PatternSets have a set of default file types to exclude; you can control it using the <defaultexcludes> task; you should usually only extend the default file types.
- if desired, use <property environment='env'/> to force ant properties and system properties into different namespaces.
- you usually want your source code management tool to ignore generated class files. CVS users can set a $CVSIGNORE environment variable, or place a .cvsignore file in their home directory.
- jar-ing files with duplicate='preserve' is usually desirable.
See Also :
Would you use this technique?