Building a JNI (Java Native Interface) project with the Maven NAR Plugin

The Maven NAR Plugin provides the easiest way to interface native code to Java projects. This example shows the configuration needed in order to build a JNI library, and package it into a launcher structure using the cross-platform-launcher-plugin.

The cross-platform-launcher-plugin has been developed and tested with version 2.1-SNAPSHOT of the NAR plugin.

This cross-platform example does not show the actual code, just the Maven plugin configuration. This example is taken from the early development of another DevZendo.org project, the Cross Platform Filesystem Access library. At this stage, the library was nothing more than a "Hello World", taken from the example code of the Maven NAR Plugin.

This example comprises three Maven Projects, and illustrates the typical partitioning and packaging of JNI projects:

  • CrossPlatformFileSystemAccess: The native code is a Maven Java/C++ project using the Maven NAR Plugin's 'nar' packaging. It contains both Java and C++ for Windows (2000/XP), Mac OS X (10.6 Intel Snow Leopard) and Linux (Intel Ubuntu Linux 10.04.1 LTS). The project contains both library code and integration tests.
  • CrossPlatformFileSystemAccessTest: Also a 'nar'-packaged project, since it makes use of the previous JNI library, and therefore also needs nar packaging. Despite the name, this project is not test code, but a 'main' that makes use of the JNI library.
  • CrossPlatformFileSystemAccessProduct: The packaging here is 'pom', with no code. This project provides the configuration of the CrossPlatformLauncherPlugin amongst others.

    There are multiple profiles activated on specific operating systems in order to create a launcher structure appropriate to the system its 'package' goal is run on. Each OS-activated profile declares the relevant NAR plugin's AOL (Architecture-OperatingSystem-Linker) classifiers and artifact types.

    The nar-download, nar-unpack and nar-assembly goals of the Maven NAR Plugin must be executed, followed by the CrossPlatformLauncherPlugin. The cross-platform-launcher-plugin understands AOL:type classifiers and will copy these, and their .nar libraries over into the launcher structure.

    The 'main' class referred to in the cross-platform-launcher-plugin configuration is that of the CrossPlatformFileSystemAccessTest project.

CrossPlatformFileSystemAccess: The JNI Project

First, CrossPlatformFileSystemAccess's pom.xml builds a JNI library and autogenerated NarSystem class in our projects' package:

<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>org.devzendo</groupId>
    <artifactId>CrossPlatformFileSystemAccess</artifactId>
    <name>CrossPlatformFileSystemAccess</name>
    <version>0.0.1-SNAPSHOT</version>
    <description>Java Native code for advanced file attribute and file system operations</description>
    <packaging>nar</packaging>

    <parent>
        <artifactId>GroupParent</artifactId>
        <groupId>org.devzendo</groupId>
        <version>1.0.0</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.4</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.easymock</groupId>
            <artifactId>easymock</artifactId>
            <version>2.3</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.8</version>
        </dependency>
    </dependencies>
    <build>
        <defaultGoal>install</defaultGoal>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-nar-plugin</artifactId>
                <version>2.1-SNAPSHOT</version>
                <extensions>true</extensions>
                <configuration>
                    <java>
                        <include>true</include>
                    </java>
                    <javah />
                    <libraries>
                        <library>
                            <type>jni</type>
                            <narSystemPackage>org.devzendo.xpfsa</narSystemPackage>
                        </library>
                    </libraries>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

CrossPlatformFileSystemAccessTest: The JNI Client Project

Second, CrossPlatformFileSystemAccessTest's pom.xml:

<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>org.devzendo</groupId>
    <artifactId>CrossPlatformFileSystemAccessTest</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>nar</packaging>

    <parent>
        <artifactId>GroupParent</artifactId>
        <groupId>org.devzendo</groupId>
        <version>1.0.0</version>
    </parent>

    <properties>
        <skipTests>true</skipTests>
    </properties>

    <!--  for Eclipse -->
    <build>
        <defaultGoal>integration-test</defaultGoal>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-nar-plugin</artifactId>
                <version>2.1-SNAPSHOT</version>
                <extensions>true</extensions>
                <executions>
                    <execution>
                        <id>unpack-nar-dependencies</id>
                        <phase>package</phase>
                        <goals>
                            <goal>nar-download</goal>
                            <goal>nar-unpack</goal>
                            <goal>nar-assembly</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>org.devzendo</groupId>
            <artifactId>CrossPlatformFileSystemAccess</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <type>nar</type>
        </dependency>
        <dependency>
            <groupId>org.devzendo</groupId>
            <artifactId>CommonCode</artifactId>
            <version>1.0.0</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.4</version>
            <scope>test</scope>
        </dependency>

    </dependencies>
</project>

CrossPlatformFileSystemAccessProduct: The Launcher Generation Project

Finally, CrossPlatformFileSystemAccessProduct's pom.xml. Note the activation of configuration specific to the operating system upon which the package goal is invoked, and the specification of the NAR Plugin's AOL-classifier:type in each invocation of the cross-platform-launcher-plugin.

The Mac OS X profile also copies the Quaqua JNI library over to the launcher structure, and declares the dependency of the java half of Quaqua.

<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>org.devzendo</groupId>
    <artifactId>CrossPlatformFileSystemAccessProduct</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>pom</packaging>
    <description>Test Application</description>

    <parent>
        <artifactId>GroupParent</artifactId>
        <groupId>org.devzendo</groupId>
        <version>1.0.0</version>
    </parent>

    <properties>
        <appName>CrossPlatformFileSystemAccessProduct</appName>
        <skipTests>true</skipTests>
    </properties>

    <profiles>
        <profile>
            <id>mac os x</id>
            <activation>
                <os>
                    <name>mac os x</name>
                </os>
            </activation>
            <build>
                <plugins>
                    <!--
                        Create the Mac OS X BeanMinder.app launcher structure under
                        target/macosx.
                    -->
                    <plugin>
                        <groupId>org.devzendo</groupId>
                        <artifactId>cross-platform-launcher-plugin</artifactId>
                        <version>1.1.0</version>
                        <configuration>
                            <os>MacOSX</os>
                            <applicationName>${appName}</applicationName>
                            <mainClassName>org.devzendo.xpfsatest.XPFSATest</mainClassName>
                            <iconsFileName>BeanMinder.icns</iconsFileName>
                            <narClassifierTypes>
                                <param>x86_64-MacOSX-g++:jni</param>
                            </narClassifierTypes>
                            <!--
                                I don't have an assigned creator code
                                <bundleSignature>BM</bundleSignature>
                            -->
                        </configuration>
                        <executions>
                            <execution>
                                <id>createlauncher</id>
                                <phase>package</phase>
                                <goals>
                                    <goal>createlauncher</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                    <!--
                        Copy the Quaqua native libraries into the correct location in the
                        Mac OS X launcher structure created above.
                    -->
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-dependency-plugin</artifactId>
                        <executions>
                            <execution>
                                <id>unpack-quaqua-dependencies</id>
                                <phase>package</phase>
                                <goals>
                                    <goal>unpack</goal>
                                </goals>
                                <configuration>
                                    <artifactItems>
                                        <artifactItem>
                                            <groupId>org.devzendo</groupId>
                                            <artifactId>libquaqua</artifactId>
                                            <version>9.1</version>
                                            <type>zip</type>
                                            <overWrite>true</overWrite>
                                            <includes>*</includes>
                                            <outputDirectory>${project.build.directory}/macosx/${appName}.app/Contents/Resources/Java/lib
                                            </outputDirectory>
                                        </artifactItem>
                                    </artifactItems>
                                    <!-- other configurations here -->
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
            <dependencies>
                <dependency>
                    <groupId>org.devzendo</groupId>
                    <artifactId>quaqua</artifactId>
                    <version>9.1</version>
                </dependency>
            </dependencies>
        </profile> <!--  mac os x -->


        <profile>
            <id>windows</id>
            <activation>
                <os>
                    <family>Windows</family>
                </os>
            </activation>
            <build>
                <plugins>
                    <!--
                        Create the Windows launcher structure under target/windows.
                    -->
                    <plugin>
                        <groupId>org.devzendo</groupId>
                        <artifactId>cross-platform-launcher-plugin</artifactId>
                        <version>1.1.0</version>
                        <configuration>
                            <os>Windows</os>
                            <applicationName>${appName}</applicationName>
                            <mainClassName>org.devzendo.xpfsatest.XPFSATest</mainClassName>
                            <narClassifierTypes>
                                <param>x86-Windows-msvc:jni</param>
                            </narClassifierTypes>
                        </configuration>
                        <executions>
                            <execution>
                                <id>createlauncher</id>
                                <phase>package</phase>
                                <goals>
                                    <goal>createlauncher</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile> <!--  Windows -->


        <profile>
            <id>linux</id>
            <activation>
                <os>
                    <family>Linux</family>
                </os>
            </activation>
            <build>
                <plugins>
                    <!--
                        Create the Linux launcher structure under target/linux.
                    -->
                    <plugin>
                        <groupId>org.devzendo</groupId>
                        <artifactId>cross-platform-launcher-plugin</artifactId>
                        <version>1.1.0</version>
                        <configuration>
                            <os>Linux</os>
                            <applicationName>${appName}</applicationName>
                            <mainClassName>org.devzendo.xpfsatest.XPFSATest</mainClassName>
                            <narClassifierTypes>
                                <param>i386-Linux-g++:jni</param>
                            </narClassifierTypes>
                        </configuration>
                        <executions>
                            <execution>
                                <id>createlauncher</id>
                                <phase>package</phase>
                                <goals>
                                    <goal>createlauncher</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile> <!--  Linux -->
    </profiles>

    <!-- Common for all profiles, and needed for Eclipse -->
    <build>
        <defaultGoal>integration-test</defaultGoal>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-nar-plugin</artifactId>
                <version>2.1-SNAPSHOT</version>
                <extensions>true</extensions>
                <executions>
                    <execution>
                        <id>unpack-nar-dependencies</id>
                        <phase>package</phase>
                        <goals>
                            <goal>nar-download</goal>
                            <goal>nar-unpack</goal>
                            <goal>nar-assembly</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>org.devzendo</groupId>
            <artifactId>CrossPlatformFileSystemAccessTest</artifactId>
            <version>0.0.1-SNAPSHOT</version>
            <type>nar</type>
        </dependency>
    </dependencies>
</project>