I have a JNI project configured with Maven using the nar-maven-plugin
. Both the Java and the C++ code reside in the project. The main code apparently compiles properly (both C++ and Java). The problem is with the test code (JUnit).
The test code defined one Java class that itself has a native method. The corresponding native code resides in the directory
<project root>
+- src
+- test
+- c++
There is no evidence from the build messages that this native test code is ever compiled and the corresponding native method does not appear at all when I run nm
from the command line on the DLL created by the build process. In addition, I intentionally put a syntax error into the test code and recompiled to see if I would get a compile-time error. There is no error, consistent with my belief that the code is never compiled.
Correspondingly, I get an UnsatisfiedLinkError
when the test runs during mvn install
. Note that I can tell from the point at which the test failed that the native methods for the main (non-test) code were properly loaded and linked. Hence I conclude that there is some problem related to the building and linking of native test code specifically.
I'm currently on Windows 10 using the Eclipse IDE and MinGW compilers for native code.
The relevant sections of my POM are below (slightly updated from my answer on Avoiding machine-dependent POM with MinGW compiler and nar-maven-plugin related to an early configuration problem):
<profiles>
<profile>
<id>Windows-MinGW</id>
<activation>
<os>
<family>Windows</family>
</os>
</activation>
<build>
<plugins>
<plugin>
<groupId>com.github.maven-nar</groupId>
<artifactId>nar-maven-plugin</artifactId>
<version>3.5.1</version>
<extensions>true</extensions>
<configuration>
<cpp>
<options>
<option>-std=c++1y</option>
</options>
</cpp>
<linker>
<name>g++</name>
<options>
<option>-Wl,--kill-at</option>
</options>
</linker>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<build>
<defaultGoal>integration-test</defaultGoal>
<plugins>
<plugin>
<groupId>com.github.maven-nar</groupId>
<artifactId>nar-maven-plugin</artifactId>
<version>3.5.1</version>
<extensions>true</extensions>
<configuration>
<cpp>
<defines>
<define>EXPORT_DLL</define>
</defines>
</cpp>
<libraries>
<library>
<type>jni</type>
<narSystemPackage>com.mycompany.sandbox</narSystemPackage>
</library>
</libraries>
</configuration>
</plugin>
</plugins>
</build>
Is there a known way to handle this problem? (Maybe additional configuration tags?)
I got this to work, but it wasn't pretty. I'll give the POM parts at the end, but, in outline, these are the steps:
<tests>
section on nar-maven-plugin
to compile the native test code, noting that this is really set-up to create executables for native tests not to create DLL/SO backing Java tests<testOptions>
to the linker to hack it into making a DLL/SO instead of an executable<testOptions>
to get your test code to link against the main project code since this is not automatically supported when you choose jni
(rather than shared
or static
) library typeExpanding on the last bullet in more detail: Even once you get the test code to compile, you won't be able to load it from Java because the test directories are not on the java.library.path
when the tests are run! The best solution that I could find was to move the test library temporarily into a directory that is on the path. This seemed easier than changing the path due to the configuration options easily available. Provided that you get the library on the path, then you can use System.loadLibrary
as usual during the JUnit test execution.
Now here are the expanded POM segments that accomplish the above. This is based on what I had in the question, but with the new pieces requires to accomplish the bullets at the beginning of the answer. Note that the JNI code backing the test is in a file TestWrapper.cpp
in /src/test/c++
. (I know that's not standard naming convention for a JNI source file.)
NOTE: At this point I have only worked out the linker flags for testing on my Windows 10 machine, which is representing in the profiles
section. Likewise, the copy / delete has the .dll
extension explicitly, which would need to be adjusted. (Further note, however, that even though you get a .dll
file, it will have a .exe
extension when the plugin makes it!) My POM is no doubt broken for other machines / architectures at this point, but there's a clear path forward to making them work from here, so it seems worth posting the answer as is.
<profiles>
<profile>
<id>Windows-MinGW</id>
<activation>
<os>
<family>Windows</family>
</os>
</activation>
<build>
<plugins>
<plugin>
<groupId>com.github.maven-nar</groupId>
<artifactId>nar-maven-plugin</artifactId>
<version>3.5.1</version>
<extensions>true</extensions>
<configuration>
<cpp>
<options>
<option>-std=c++1y</option>
</options>
</cpp>
<linker>
<name>g++</name>
<options>
<option>-Wl,--kill-at</option>
</options>
<testOptions>
<!-- Put the -shared flag onto the linker - That will force a DLL instead of an EXE -->
<testOption>-shared</testOption>
<!-- We cannot easily link to the *library* that was created for the main project but we can get the compiled object files with the following option -->
<testOption>${project.build.directory}/nar/obj/${nar.aol}/*.o</testOption>
</testOptions>
</linker>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<build>
<defaultGoal>integration-test</defaultGoal>
<plugins>
<plugin>
<groupId>com.github.maven-nar</groupId>
<artifactId>nar-maven-plugin</artifactId>
<version>3.5.1</version>
<extensions>true</extensions>
<configuration>
<cpp>
<defines>
<define>EXPORT_DLL</define>
</defines>
</cpp>
<libraries>
<library>
<type>jni</type>
<narSystemPackage>com.mycompany.mypackage</narSystemPackage>
</library>
</libraries>
<tests>
<test>
<name>TestWrapper</name>
<run>false</run>
</test>
</tests>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.7</version>
<executions>
<execution>
<id>copy-test-lib-to-path</id>
<phase>pre-integration-test</phase>
<configuration>
<target>
<copy file="${project.build.directory}/test-nar/bin/${nar.aol}/TestWrapper.exe" tofile="${project.build.directory}/nar/${project.artifactId}-${project.version}-${nar.aol}-jni/lib/${nar.aol}/jni/TestWrapper.dll"/>
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
<execution>
<id>delete-test-lib-from-deployment</id>
<phase>post-integration-test</phase>
<configuration>
<target>
<delete file="${project.build.directory}/nar/${project.artifactId}-${project.version}-${nar.aol}-jni/lib/${nar.aol}/jni/TestWrapper.dll"/>
</target>
</configuration>
<goals>
<goal>run</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>