Build and deployment scripts using “real” programming languages

Like many teams, I’m sure, we are trying to squeeze every drop of effectiveness out of our time. Manual build and deployment not only takes up valuable time, but also acts as a drag on the development process. Anything which pulls developers out of “the zone” is a bad thing for productivity.

We usually use the familiar “ant” build tool for building deployable artefacts, but have found it increasingly fiddly as our expectations have grown. And ant is hardly useful at all for real-world deployment tasks.

It may be the time to re-consider other build and deployment options.

Thread.current.to_s: Let’s use real languages for builds

building an executable jar from other jars

I started the day cursing the stupid jar format, but ended the day smiling. I needed to build a stand-alone executable jar which accessed a MySQL database but kept getting all sorts of build-time and run-time errors.

Normally to make an executable jar I use the excellent pack ant task which build a minimal jar file by trawling all the class dependencies in yout code and including just the required classes from the classpath. For example:

<target name="jar">
   <taskdef name="pack" 
      classname="org.sadun.util.ant.Pack" classpath="lib/pack.jar" />
   <pack 
       classes = "${root.classes}"
       targetJar = "dist/${project.name}.jar"
       manifestMainClass = "${main.class}"
       excludePkg = "java,sun"
       includePkg = "com,org,javax"
   >
      <classpath refid="classpath"/>
   </pack>
</target>

Just set up the main.class property to the main class of the application, and set the root.classes property to a class which refers to everything you need (in many cases this will be the same main class) and you get a lovely small executable jar.

However, trying this approach with MySQL cause a bunch of problems. First I needed to add a load of (apparently dynamically loaded) MySQL class names such as com.mysql.jdbc.Driver.

I thought I was doing well when I had resolved that issue, but then I hit an even harder problem:

java.lang.RuntimeException: Can't load resource bundle due to underlying exception
java.util.MissingResourceException: Can't find bundle for base name com.mysql.jdbc.LocalizedErrorMessages, locale en_GB

Despite spending an hour or so searching and trying, I could not convince pack to find it. So I had to look elsewhere.

The solution I eventually chose was “one-jar” – a trick which subverts the normal jar execution process and creates a custom classloader to resolve jars within a jar. I still use pack to minimise the amount of other classes included in my project, but explicitly include the whole MySQL driver jar.

My ant target now looks like:

<target name="jar">
   <taskdef name="pack" 
      classname="org.sadun.util.ant.Pack"
      classpath="lib/pack.jar" />
   <pack 
       classes = "${root.classes}"
       targetJar = "tmp/main.jar"
       manifestMainClass = "${main.class}"
       excludePkg = "java,sun"
       includePkg = "com,org,javax"
   >
      <classpath refid="classpath"/>
   </pack>
   <taskdef name="one-jar" 
      classname="com.simontuffs.onejar.ant.OneJarTask"
      classpath="lib/one-jar-ant-task-0.96.jar" onerror="report"/>
   <one-jar destfile="dist/${project.name}.jar" manifest="src/main/files/${project.name}.mf">
      <main jar="tmp/main.jar"/>
      <lib>
         <fileset file="lib/mysql-connector-java-5.0.4-bin.jar"/>
      </lib>
   </one-jar> 
</target>

This builds all of my application except the MySQL bits using pack into a temporary “main.jar”, then uses one-jar to build another jar which refers to both main.jar and the MySQL driver jar.

I did need to create an explicit manifest file this way (usually, pack is smart enough to make it for me)

Main-Class: com.simontuffs.onejar.Boot
One-Jar-Main-Class: org.example.project.Main

I now have a working stand-alone executable jar which can access a MySQL database. Cool.

Read more at Deliver Your Java Application in One-JARâ„¢ ! and Java: Using ONE-JAR