In a previous post, making jar files with Ant, I briefly covered the creation of a Ant script that would compile the Java source files and then create jar file from them.
It was a really neat example of how with less than 100 lines of script ant can compile the source, create a zip file from the source, create a jar and even include the source files in the jar file (optionally).
This is possible due to the powerful list of (over 150) tasks that are available to your scripts. This list ranges from the more common tasks like
- copy files or directories
- delete a file or directory
- make directory
- zip / unzip
- tar / untar
- fix operating system line endings
- auto incrementing build number
- secure copy
- clearcase access tool
- cvs access tool
This is just a small list of what is available. If the list of tasks doesn’t contain what you need you can either execute any system command. It is also possible to extend Ant with new tasks but I won’t be getting into that right now.
Creating a package
It is beyond the scope of this blog entry to cover the creation of something really interesting like creating a Debian or Red Hat Package but Ant could probably do it.
One of the IT groups that I have worked with in the past had their own custom package installation system. It was really just a zip file with one of the files containing the inventory of files in the package as well as which types of operations should be done as part of the install.
The package installer then read the inventory file and proceeded to copy, delete and in general install files to their machine. The actual format of their package is irrelevant for this example, however, below is a similar style of how we used Ant to build our packages for that client.
<target name="package" depends="jarfile,srcfile" description="prepare for release">
<echo>"preparing ..."</echo>
step 1 <!-- create a temp directory with name of our package -->
<mkdir dir="${packagename}"/>
<!-- fill it with all sorts of program goodness -->
step2 <!-- contents of our "static" files-->
<copy todir="${packagename}/${packagename}" verbose="No" preservelastmodified="Yes">
<fileset dir="${package.dir}" includes="**" />
</copy>
step 3 <!-- contents of other libraries -->
<copy todir="${packagename}/${packagename}/lib" verbose="No" preservelastmodified="Yes">
<fileset dir="${lib.dir}" includes="**" />
</copy>
step 4 <!-- copy our program -->
<copy file="${build.dir}/${jarname}.jar" todir="${packagename}/${packagename}/lib" verbose="No" preservelastmodified="Yes"/>
step 5 <!-- turn it into a zip file -->
<zip zipfile="${build.dir}/${packagename}.zip" basedir="${packagename}"
includes="**">
</zip>
step 6 <!-- turn it into a tar file -->
<tar compression="gzip" destfile="${build.dir}/${zipfile}-${version.num}.tar.gz">
<tarfileset dir="${packagename}" >
<include name="**" />
</tarfileset>
</tar>
step 7 <!-- clean up after ourselves -->
<delete dir="${packagename}"/>
</target>
The actual ant tasks and their comment should actually be enough, but just in case I have summarized each step in the following table.
Step |
Description |
1. |
Create a temporary directory using the name of the package. |
2. |
Copy all files from my directory that contained some of the more static package files into the temporary directory. This directory contains any other configuration or data files. |
3. |
Copy all libraries from the lib directory into the temporary directory. |
4. |
Copy our newly created java library into the temporary directory.
Note: we could have created the java library in that directory directly but that would have not been so obvious compared to the build file we have. |
5. |
Create a zip file using the temporary directory as our source. This will create the zip file with the same name as our package which is quite convenient.
Note: the permissions get lost on the runInterface.sh file |
6. |
Create a gzipped tar file using the temporary directory as our source. This will create the tar file with the same name as our package which is quite convenient.
Note: the permissions get lost on the runInterface.sh file
You could get around this by running an operating system command (tar). |
7. |
Remove the temporary directory as no longer necessary. |
Below is the complete source code for a log4j2 based Hello world application. It isn’t the program that is so interesting but how the build script, using the package generation seen above can create our zip with our program.
If you look closely at the build script you will see it includes the two log4j2 jar files in the classpath allowing us to have a runnable jar not just a normal jar.
Simply unzip the package into a directory and the scripts can be run immediately.
HelloWorld.java
package de.companyname.helloworld;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class HelloWorld {
public HelloWorld()
{
communicate();
}
public void communicate()
{
Logger logfile = LogManager.getLogger(HelloWorld.class.getName());
logfile.debug("HelloWorld starting ");
logfile.info("Hello World!");
logfile.debug("HelloWorld finishing ");
}
public static void main(String args[])
{
new HelloWorld();
}
}
log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration xmlns="http://logging.apache.org/log4j/2.0/config">
<Appenders>
<File name="FILE" fileName="${sys:LOGDIR}/logfile.log" append="true">
<PatternLayout pattern="%-5p | %d{yyyy-MM-dd HH:mm:ss} | [%t] %C (%F:%L) - %m%n"/>
</File>
<Console name="STDOUT" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} | %-5p | %C{2} (%F:%L) - %m%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="debug">
<AppenderRef ref="STDOUT" />
<AppenderRef ref="FILE" />
</Root>
</Loggers>
</Configuration>
build.xml
<project default="world" basedir=".">
<property file="project.properties"/>
<property name="lib.dir" value="${basedir}/lib"/>
<property name="src.dir" value="${basedir}/src"/>
<property name="class.dir" value="${basedir}/bin"/>
<property name="build.dir" value="${basedir}/final"/>
<property name="package.dir" value="${basedir}/package"/>
<property name="packagename" value="${jarname}-${version.num}"/>
<path id="classpath">
<fileset dir="${lib.dir}" includes="**/*.jar"/>
</path>
<target name="clean" description="remove anything we created">
<echo>"cleaning ..."</echo>
<delete dir="${class.dir}"/>
<delete dir="${build.dir}"/>
</target>
<target name="copysource" description="copy source code for jar" if="copysource">
<echo>"copying ..."</echo>
<mkdir dir="${class.dir}"/>
<copy todir="${class.dir}" verbose="No" preservelastmodified="Yes">
<fileset dir="${src.dir}" includes="**/*.java" />
</copy>
</target>
<target name="compile" depends="copysource" description="compile source code">
<echo>"compiling ..."</echo>
<mkdir dir="${class.dir}"/>
<javac srcdir="${src.dir}" destdir="${class.dir}" classpathref="classpath" debug="on"/>
</target>
<target name="jarfile" depends="compile" description="generate jar file">
<echo>"build jar ..."</echo>
<mkdir dir="${build.dir}"/>
<tstamp>
<format property="today.timestamp" pattern="yyyy-MM-dd HH:mm:ss" />
</tstamp>
<jar destfile="${build.dir}/${jarname}.jar" basedir="${class.dir}">
<manifest>
<attribute name="Built-By" value="${user.name}" />
<attribute name="Main-Class" value="${mainclass}" />
<attribute name="Implementation-Version" value="${version.num} build #${build.num}"/>
<attribute name="Built-Date" value="${today.timestamp}"/>
<attribute name="Class-Path" value="../lib/log4j-core-2.6.2.jar ../lib/log4j-api-2.6.2.jar ../resources/log4j2.xml"/>
</manifest>
</jar>
</target>
<target name="srcfile" description="prepare source tar file">
<echo>"gather source ..."</echo>
<zip zipfile="${build.dir}/${zipfile}-${version.num}.zip" basedir="${src.dir}" includes="**">
</zip>
</target>
<target name="package" depends="jarfile,srcfile" description="prepare for release">
<echo>"preparing ..."</echo>
<!-- create a temp directory with name of our package -->
<mkdir dir="${packagename}"/>
<!-- fill it with all sorts of program goodness -->
<!-- contents of our "static" files-->
<copy todir="${packagename}/${packagename}" verbose="No" preservelastmodified="Yes">
<fileset dir="${package.dir}" includes="**" />
</copy>
<!-- contents of other libraries -->
<copy todir="${packagename}/${packagename}/lib" verbose="No" preservelastmodified="Yes">
<fileset dir="${lib.dir}" includes="**" />
</copy>
<!-- copy our program -->
<copy file="${build.dir}/${jarname}.jar" todir="${packagename}/${packagename}/lib" verbose="No" preservelastmodified="Yes"/>
<!-- turn it into a zip file as well -->
<zip zipfile="${build.dir}/${packagename}.zip" basedir="${packagename}" includes="**">
</zip>
<!-- turn it into a tar file -->
<tar compression="gzip" destfile="${build.dir}/${zipfile}-${version.num}.tar.gz">
<tarfileset dir="${packagename}" >
<include name="**" />
</tarfileset>
</tar>
<!-- clean up after ourselves -->
<delete dir="${packagename}"/>
</target>
<target name="world" depends="package">
<echo>"build script version 1.00"</echo>
</target>
</project>
project.properties
version.num = 3.13.047
build.num = 9
mainclass = de.companyname.helloworld.HelloWorld
jarname = communication
zipfile = communication-src
copysource = true
runInterface.sh
#!/bin/bash
LOGDIR=`pwd`/log
CP="`pwd`/lib/log4j-api-2.6.2.jar:`pwd`/lib/log4j-core-2.6.2.jar:`pwd`/lib/communication.jar:resources"
CFGFILE=`pwd`/resources/log4j2.xml
PGM=de.companyname.helloworld.HelloWorld
if [ ! -d $LOGDIR ]
then
mkdir $LOGDIR
fi
echo first run
CMD="java -DLOGDIR=$LOGDIR -cp $CP $PGM "
echo $CMD
$CMD
echo .
echo second run
echo .
cd lib
CMD="java -DLOGDIR=$LOGDIR -Dlog4j.configurationFile=$CFGFILE -jar communication.jar"
echo $CMD
$CMD
echo .
other setup notes
create directory structure
mkdir \development\hello\src\de\companyname\helloword
mkdir \development\hello\package
mkdir \development\hello\package\resources
mkdir \development\hello\lib
copy the log4j2.xml to resources directory
copy log4j2 jar files to lib directory
copy build.xml hello directory
copy project.properties hello directory
copy runInterface.sh hello directory
copy runInterface.bat to hello directory
copy HelloWorld.java to helloworld directory
Note: make sure your path delimiters are correct in build script if you are automatically creating manifest file.
Note: The example was compiled with java 1.8 and uses log4j-api-2.6.2.jar and log4j-core-2.6.2.jar.