Hi folks! After a few months of inactivity I come back with a new post concerning the anatomy of an Ant buildfile. Ant buildfiles are written in XML and are mainly used to build Java applications. In this post I’ll explain step-by-step how to write a buildfile for a simple Java application (a simple Web server based on Jetty).
Requirements: in order that all things work correctly, you need to install the Java Developement Kit (Oracle JDK in my specific case) and Apache Ant.
Now, the first thing to do is to open a terminal and create a directory for your project:
mkdir /home/$USER/JettyApp
$USER is an environment variable containing the name of the current logged user. You can write your user name directly if you want (i.e. mkdir /home/peter/JettyApp).
Then you have to create one directory for the Java sources and one for the dependencies:
mkdir /home/$USER/JettyApp/src mkdir /home/$USER/JettyApp/deps
Now create an empty file called JettyExample.java in your ~/JettyApp/src directory (the tilde means the /home/$USER dir):
touch /home/$USER/JettyApp/src/JettyExample.java
Open the file with your preferred text editor, copy the source code of the file listed here below, paste it on the file you’ve just created, then save it.
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.ServletException; import java.io.IOException; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.AbstractHandler; public class JettyExample extends AbstractHandler { public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { response.setContentType("application/json;charset=utf-8"); response.setStatus(HttpServletResponse.SC_OK); baseRequest.setHandled(true); response.getWriter().println("{\"app_name\":\"JettyApp\"}"); } public static void main(String[] args) throws Exception { Server server = new Server(8080); server.setHandler(new JettyExample()); server.start(); System.out.println("Jetty Web server started successfully! Go to localhost:8080 or http://YOUR_IP_ADDRESS:8080"); server.join(); } }
This code instantiates a simple Web server which returns a JSON object containing the name of the application, when you perform an HTTP GET request.
Now you need to download the dependencies:
cd /home/$USER/JettyApp/deps curl -o servlet-api-3.1.jar http://central.maven.org/maven2/javax/servlet/javax.servlet-api/3.1.0/javax.servlet-api-3.1.0.jar curl -o jetty-all-9.2.10.v20150310.jar http://central.maven.org/maven2/org/eclipse/jetty/aggregate/jetty-all/9.2.10.v20150310/jetty-all-9.2.10.v20150310.jar
The -o option tells curl to write the output to a file instead of the standard output.
Now it’s the time to create our buildfile. So, let’s create a file called build.xml in the ~/JettyApp directory:
cd .. touch build.xml
Open the file with your preferred text editor, copy the XML code of the file listed here below, paste it on the file you’ve just created and then save it.
<project name="JettyApp" default="dist" basedir="."> <description> Ant build file for a simple Jetty example </description> <!-- Set global properties for this build --> <property name="src" location="src"/> <property name="deps" location="deps"/> <property name="build" location="build"/> <property name="dist" location="dist"/> <target name="init"> <!-- Create the time stamp --> <tstamp/> <!-- Create the build directory structure used by compile --> <mkdir dir="${build}"/> </target> <target name="compile" depends="init" description="compile the source"> <!-- Compile the java code from ${src} into ${build} --> <javac includeantruntime="false" debug="on" srcdir="${src}" destdir="${build}"> <!-- Add the dependencies to the Java classpath --> <classpath> <pathelement location="${deps}/servlet-api-3.1.jar"/> <pathelement location="${deps}/jetty-all-9.2.10.v20150310.jar"/> </classpath> </javac> </target> <target name="dist" depends="compile" description="generate the distribution"> <!-- Create the distribution directory --> <mkdir dir="${dist}/lib"/> <!-- Put everything in ${build} into the JettyApp-${DSTAMP}.jar file --> <jar jarfile="${dist}/lib/JettyApp-${DSTAMP}.jar" basedir="${build}"> <!-- Merge all the dependencies in the final .jar, exclude the manifest signatures --> <zipgroupfileset dir="${deps}" includes="**/*.jar" excludes="META-INF/**/*"/> <!-- Create a manifest file --> <manifest> <attribute name="Main-Class" value="JettyExample"/> <attribute name="Class-Path" value="${deps}/servlet-api-3.1.jar ${deps}/jetty-all-9.2.10.v20150310.jar"/> </manifest> </jar> </target> <target name="clean" description="clean up" > <!-- Delete the ${build} and ${dist} directory trees --> <delete dir="${build}"/> <delete dir="${dist}"/> </target> <target name="run" description="execute the application"> <tstamp/> <!-- Execute the .jar located in the ${dist}/lib directory --> <java jar="${dist}/lib/${ant.project.name}-${DSTAMP}.jar" fork="true"/> </target> </project>
Once you’ve created the buildfile you can build your application with:
ant -buildfile build.xml
or simply with (since the dist target is also the default target):
ant dist
This command will use the buildfile to compile the sources, put the .class files into the ~/JettyApp/build directory and finally create a .jar file into the ~/JettyApp/dist/lib directory.
On your terminal you should see something like this:
Buildfile: /home/cristiano/JettyApp/build.xml init: [mkdir] Created dir: /home/cristiano/JettyApp/build compile: [javac] Compiling 1 source file to /home/cristiano/JettyApp/build dist: [mkdir] Created dir: /home/cristiano/JettyApp/dist/lib [jar] Building jar: /home/cristiano/JettyApp/dist/lib/JettyApp-20150403.jar BUILD SUCCESSFUL Total time: 1 second
Ok, let’s see something about the buildfile structure. The first thing you should notice is that all the XML code is enclosed between the <project></project> tags, so the first thing to do when you want to create a new project is to write these two tags.
The project tag has three attributes (none of them is mandatory):
- name: the name of the project
- default: the default target to use when you don’t specify any target
- basedir: the directory from which all the paths are calculated (the dot means the current directory).
Furthermore, you can optionally add a brief description of the project using the <description></description> tags.
<project name="JettyApp" default="dist" basedir="."> <description> Ant build file for a simple Jetty example </description> ... </project>
In a buildfile you can also define some properties. In this example I’ve defined four properties, one for each directory. This is convenient beacuse the name of these directories appears frequently into the buildfile.
In my example, properties have two attributes:
- name: the name of the property (it’s case-sensitive)
- location: a relative path (calculated from the basedir)
... <property name="src" location="src"/> <property name="deps" location="deps"/> <property name="build" location="build"/> <property name="dist" location="dist"/> ...
To build your project you need to create one or more targets. Each target is responsible of a well defined set of actions, called tasks. In this example (see the complete buildfile) you can see there are five targets.
In general, targets have three attributes:
- name: the name of the target
- depends: one or more targets from which the target depends on (Ant resolves the dependencies between targets, defining their order of execution)
- description: a brief description of the actions performed by the target.
... <target name="compile" depends="init" description="compile the source"> <!-- Compile the java code from ${src} into ${build} --> <javac includeantruntime="false" debug="on" srcdir="${src}" destdir="${build}"> <!-- Add the dependencies to the Java classpath --> <classpath> <pathelement location="${deps}/servlet-api-3.1.jar"/> <pathelement location="${deps}/jetty-all-9.2.10.v20150310.jar"/> </classpath> </javac> </target> <target name="dist" depends="compile" description="generate the distribution" > <!-- Create the distribution directory --> <mkdir dir="${dist}/lib"/> <!-- Put everything in ${build} into the JettyApp-${DSTAMP}.jar file --> <jar jarfile="${dist}/lib/JettyApp-${DSTAMP}.jar" basedir="${build}"> <!-- Merge all the dependencies in the final .jar, exclude the manifest signatures --> <zipgroupfileset dir="${deps}" includes="**/*.jar" excludes="META-INF/**/*"/> <!-- Create a manifest file --> <manifest> <attribute name="Main-Class" value="JettyExample"/> <attribute name="Class-Path" value="${deps}/servlet-api-3.1.jar ${deps}/jetty-all-9.2.10.v20150310.jar"/> </manifest> </jar> </target> ...
If you want to use the clean target you can call it manually with:
ant clean
Instead, if you want to clean the ~/JettyApp/build and the ~/JettyApp/dist/lib directory trees automatically, you can modify the buildfile by changing the line 12 from <target name=”init”> to <target name=”init” depends=”clean”>. In this way the targets will be executed in this order: clean, init, compile, dist.
Now, if you want to run your application, you can do it in two ways:
# Traditional way java -jar dist/lib/FILE_NAME.jar
# The Ant way ant run
Point your Web browser to localhost:8080 or http://YOUR_IP_ADDRESS:8080, you should see the Web server response ;-)
For more details on buildfiles you can look at the Ant manual.
Hi! Great post, as usual! =p
Thank you very much, dear friend! ;-)