A Maven POM for building JSR335 code

Posted on Mon 19 December 2011 in Coding

While playing around with lambdas and defender methods developed within the scope of Project Lambda, I thought I’d create a Maven POM file to facilitate compiling, testing and weaving (the process of making defender methods work in the JVM) of the code I write. The entire project can be fetched from GitHub.

Since a POM file can be quite intimidating, I’ll go through it step by step. First some basic stuff, defining the identity of the project:

<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>

  <groupId>com.programmaticallyspeaking.jsr335</groupId>
  <artifactId>jsr335-maven</artifactId>
  <version>1.0</version>
  <packaging>jar</packaging>

My tests use TestNG, so I’ll need that as a test dependency:

  <dependencies>
    <dependency>
      <groupId>org.testng</groupId>
      <artifactId>testng</artifactId>
      <version>6.3.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

Now for the interesting parts. First, we need to configure the compiler plugin to use theappropriate JDK. For that, it relies on the JDK8_HOME environment variable being set (the error we get if the variable is unset is quite ugly, but it’s not important right now; check out the enforcer plugin if you want to handle it nicer). Since we have a custom JDK, we must fork the compilation; otherwise we’d just use the regular JDK (the one Maven uses by default). Finally, we must specify source and target versions, since they both default to 1.5:

  <build>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.3.2</version>
        <configuration>
          <!-- Must use the 1.8/lambda compiler. -->
          <fork>true</fork>
          <executable>${env.JDK8_HOME}/bin/javac</executable>
          <!-- Testing 1.8 features, 1.8 source requires 1.8 target. -->
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>

The surefire plugin runs unit tests, and for that we need to fork using the special JDK as well. The configuration is easier than for the compiler plugin, and forking is on by default:

      <plugin>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.11</version>
        <configuration>
          <!-- Use the 1.8/lambda JVM when forking. -->
          <jvm>${env.JDK8_HOME}/bin/java</jvm>
        </configuration>
      </plugin>

Finally we must weave in defender method support in the class files since the JVM doesn’t yet support defender methods. This is impossible to do declaratively, so we must resort to the antrun plugin. For this part, the DPROTO_HOME environment variable must be set as well. In the Ant task, we weave class files into a temporary directory, from which we then copy the files back to the proper classes directory before deleting the temporary one:

      <plugin>
        <artifactId>maven-antrun-plugin</artifactId>
        <version>1.7</version>
        <executions>
          <execution>
            <!-- Phase suitable for doing bytecode magic. -->
            <phase>process-classes</phase>
            <goals>
              <goal>run</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <target>
            <property name="woven.dir" value="${project.build.directory}/woven" />
            <!-- Run static weaver on classes, can use standard JVM. -->
            <java
              classpath="${env.DPROTO_HOME}/distrib/jsr335-agent.jar:${env.DPROTO_HOME}/lib/asm-4.0.jar"
              classname="jsr335.agent.Main"
              >
              <arg value="-d" />
              <arg value="${woven.dir}" />
              <arg value="--jdk" />
              <arg value="${env.JDK8_HOME}" />
              <arg value="${project.build.outputDirectory}" />
            </java>

            <!-- Copy woven classes back to class dir. -->
            <copy todir="${project.build.outputDirectory}">
              <fileset dir="${woven.dir}" />
            </copy>

            <delete dir="${woven.dir}" />
          </target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

Note: We could add an execution that binds to the process-test-classes phase, but it seems as if weaving must be done all in one go as opposed to in two separate steps. This means that if you have a class within the tests that implements an interface with defender methods, things won’t work out very well for you.

And that’s all there is to it!