Thursday, June 14, 2012

Packaging Artifacts with Maven: Proguard Example

Don't underestimate how much effort it needs to get your program properly packaged.

It is nice - but not more - if the program works in your IDE.

It is of no value whatsoever if the  code doesn't make it to the production system since there are flaws in the chain after you've committed it in your code repository, or there are some manual steps involved which render a continuous integration scenario impossible.

A great part of maven copes with assembling artifacts together, zipping files, copying, or even deploying them to the proper place. You can get very creative with maven, you can even extend it with your own plugins (in maven speak: mojos). Quite easily.

It is not so easy to always know what is "the right way" to do it.

In my opinion it is nice if you have many options, but always prefer to keep it simple. Therefore, it is better if you use three standard tools which do the work sequentially, than to write your own optimized version of your very special process. 

Since the latter isn't so special really, have a look at the existing plugins, it will save you lot of work.

However, reading documentation is often the enemy which stands between you and your deadline. Doing it the right way depends on your knowledge at a specific moment in time.

You can and should always refactor your build definitions.

You could start for example by implementing certain steps of your build using a shell script or a bat file, or implement a process step in your favorite programming language. You can implement it 'natively' by configuring a multitude of maven plugins with a dozen xml files. Doing it the one way or another always depends on your knowledge, the team situation, available resources ...

Of course, you can get religious about this, but you shouldn't. It should be 'good enough'.

Some maven plugins which can be very helpful:

Every mentioned plugin has nice features which can serve you in many occasions, I'll highlight some usecases here.

Maven Assembly Plugin

This plugin comes in very handy if you want to create zip files. You can basically collect arbitrary files from the harddisk and put them via an xml configuration file in the zip file. You can even create directory trees inside the zip file for that matter. +1

Maven Shade Plugin


This is a very interesting thing: by providing 10 lines of xml you'll get a jar file with all dependent class files which enables you to have a standalone jar file. Very useful for distributing your application.

Maven Dependency Plugin


If you ever wondered about an easy way to get an artifact out of the central or your lokal repository - maybe to include it in your assembly - this is the way to go.

Maven Proguard Plugin


Proguard! I bet every Android Developer loves its capability to shrink jar files. Even better, it can obfuscate your code in order to make a reverse engineering very painful.

Maven AntRun Plugin


Who doesn't know Ant? Maven and Ant are good friends. This plugin is the bridge to the Ant world - which is definitely worth knowing.

Maven Exec Plugin


By using this plugin you can fork arbitrary java programs - or even native executables of your choice.

Using all the goodness

You can combine the plugins for example to do the following:
  1. create some artifacts with by using the antrun and exec plugin (binding them to certain phases in your build)
  2. get some stuff via the dependency plugin
  3. compile some other part of your system from your sources
  4. obfuscate or shrink the code with the proguard plugin
  5. use the jar plugin to create an executable jar file

You can imagine by this invented example that those tools can be part of a complex build scenario. I wanted to create here an example in order to get a feel how one can use these plugins.

Here is an example pom where aforementioned scala-compiler plugin is in action and also proguard plugin is configured.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <parent>
  <groupId>net.ladstatt</groupId>
  <artifactId>ladstatt-pom</artifactId>
  <version>1.0.0-SNAPSHOT</version>
 </parent>
 <artifactId>ladstatt-util</artifactId>
 <packaging>jar</packaging>

 <build>
  <plugins>
   <plugin>
    <groupId>org.scala-tools</groupId>
    <artifactId>maven-scala-plugin</artifactId>
    <executions>
     <execution>
      <goals>
       <goal>compile</goal>
       <goal>testCompile</goal>
      </goals>
     </execution>
    </executions>
    <configuration>
     <scalaVersion>${scala.version}</scalaVersion>
     <jvmArgs>
      <jvmArg>-client</jvmArg>
      <jvmArg>-Xms64m</jvmArg>
      <jvmArg>-Xmx1024m</jvmArg>
     </jvmArgs>
     <args>
      <arg>-deprecation</arg>
      <arg>-dependencyfile</arg>
      <arg>${project.build.directory}/.scala_dependencies</arg>
     </args>
    </configuration>
   </plugin>
   <plugin>
    <groupId>com.pyx4me</groupId>
    <artifactId>proguard-maven-plugin</artifactId>
    <executions>
     <execution>
      <phase>package</phase>
      <goals>
       <goal>proguard</goal>
      </goals>
     </execution>
    </executions>
    <configuration>
     <maxMemory>2G</maxMemory><!-- we give em 2Gig of memory -->
     <assembly>
      <inclusions>
       <inclusion>
        <groupId>net.ladstatt</groupId>
        <artifactId>ladstatt-core</artifactId>
       </inclusion>
       <inclusion>
        <groupId>net.ladstatt</groupId>
        <artifactId>ladstatt-parser</artifactId>
       </inclusion>
       <inclusion>
        <groupId>net.ladstatt</groupId>
        <artifactId>ladstatt-contrib</artifactId>
       </inclusion>
       <inclusion>
        <groupId>org.scala-lang</groupId>
        <artifactId>scala-library</artifactId>
       </inclusion>
      </inclusions>
     </assembly>
     <options>
      <option>-dontobfuscate</option>
      <option>-allowaccessmodification</option>
      <option>-ignorewarnings</option>
      <option>-dontskipnonpubliclibraryclasses</option>
      <option>-dontskipnonpubliclibraryclassmembers</option>
      <option>-keep public class net.ladstatt.Util { *;}</option>
     </options>
     <outjar>${project.name}.jar</outjar>
     <libs>
      <lib>${java.home}/lib/rt.jar</lib>
     </libs>
    </configuration>
   </plugin>
   <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <configuration>
     <archive>
      <manifest>
       <mainClass>net.ladstatt.Util</mainClass>
       <addClasspath>true</addClasspath>
      </manifest>
     </archive>
    </configuration>
   </plugin>
  </plugins>
 </build>

 <dependencies>
  <dependency>
   <groupId>net.ladstatt</groupId>
   <artifactId>ladstatt-core</artifactId>
  </dependency>
  <dependency>
   <groupId>net.ladstatt</groupId>
   <artifactId>ladstatt-parser</artifactId>
  </dependency>
  <dependency>
   <groupId>net.ladstatt</groupId>
   <artifactId>ladstatt-contrib</artifactId>
  </dependency>
  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <scope>test</scope>
  </dependency>
 </dependencies>

</project>


This shows how to configure the proguard plugin to use certain dependencies as 'program jars', also the scala library is included. I did not mention yet the maven-jar-plugin, which is configured in the above example in order to create a manifest file and a main class in that file in order to be able to call the artifact with

mvn -jar ladstatt-util-1.0.0-SNAPSHOT.jar

which will then call the main method of the net.ladstatt.Util class.

In the next post I'll give some comments on deploying a webapplication with embedded jetty.

No comments:

Post a Comment