Skip to content

Commit 135350e

Browse files
authored
Add support for a static native library search path (#92)
- Add optional jssc.boot.library.path for native libs, helps with sandboxed environments - Add unit tests for jssc.boot.library.path - Bump maven-surefire-plugin to 3.0.0-M4
1 parent 2c09d72 commit 135350e

File tree

6 files changed

+160
-1
lines changed

6 files changed

+160
-1
lines changed

ant/build.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<entry key="maven.nativelibdir.path" value="${maven.nativelibdir.path}"/>
99
<entry key="maven.assembly.id" value="${maven.assembly.id}"/>
1010
<entry key="maven.test.skip" value="${maven.test.skip}"/>
11+
<entry key="maven.exclude.tests" value="${maven.exclude.tests}"/>
1112
</propertyfile>
1213
</target>
1314

@@ -193,6 +194,11 @@
193194
</and>
194195
</condition>
195196

197+
<!-- Skip ManualBootLibraryPathTest test if we're not building a native lib -->
198+
<condition property="maven.exclude.tests" value="**/ManualBootLibraryPathTest.java" else="">
199+
<equals arg1="${cmake.build.skip}" arg2="true"/>
200+
</condition>
201+
196202
<!-- Summarize host/target -->
197203
<echo level="info">Tests will run only if the TARGET and HOST match:${line.separator}${line.separator}</echo>
198204
<echo level="info">TARGET: ${os.target.classifier}</echo>

pom.xml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
<plugin.osmaven.version>1.7.0</plugin.osmaven.version>
6969
<plugin.signature.version>1.1</plugin.signature.version>
7070
<plugin.source.version>3.0.1</plugin.source.version>
71-
<plugin.surfire.version>3.0.0-M3</plugin.surfire.version>
71+
<plugin.surfire.version>3.0.0-M4</plugin.surfire.version>
7272
</properties>
7373

7474
<dependencies>
@@ -253,6 +253,12 @@
253253
<groupId>org.apache.maven.plugins</groupId>
254254
<artifactId>maven-surefire-plugin</artifactId>
255255
<version>${plugin.surfire.version}</version>
256+
<configuration>
257+
<reuseForks>false</reuseForks>
258+
<excludes>
259+
<exclude>${maven.exclude.tests}</exclude>
260+
</excludes>
261+
</configuration>
256262
</plugin>
257263

258264
<plugin>
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**
2+
* License: https://opensource.org/licenses/BSD-3-Clause
3+
*/
4+
package jssc;
5+
6+
import org.scijava.nativelib.DefaultJniExtractor;
7+
import org.scijava.nativelib.NativeLibraryUtil;
8+
9+
import java.io.File;
10+
import java.io.IOException;
11+
12+
/**
13+
* @author A. Tres Finocchiaro
14+
*
15+
* Stub <code>DefaultJniExtractor</code> class to allow native-lib-loader to conditionally
16+
* use a statically defined native search path <code>bootPath</code> when provided.
17+
*/
18+
public class DefaultJniExtractorStub extends DefaultJniExtractor {
19+
private File bootPath;
20+
21+
/**
22+
* Default constructor
23+
*/
24+
public DefaultJniExtractorStub(Class libraryJarClass) throws IOException {
25+
super(libraryJarClass);
26+
}
27+
28+
/**
29+
* Force native-lib-loader to first look in the location defined as <code>bootPath</code>
30+
* prior to extracting a native library, useful for sandboxed environments.
31+
* <code>
32+
* NativeLoader.setJniExtractor(new DefaultJniExtractorStub(null, "/opt/nativelibs")));
33+
* NativeLoader.loadLibrary("mylibrary");
34+
* </code>
35+
*/
36+
public DefaultJniExtractorStub(Class libraryJarClass, String bootPath) throws IOException {
37+
this(libraryJarClass);
38+
39+
if(bootPath != null) {
40+
File bootTest = new File(bootPath);
41+
if(bootTest.exists()) {
42+
// assume a static, existing directory will contain the native libs
43+
this.bootPath = bootTest;
44+
} else {
45+
System.err.println("WARNING " + DefaultJniExtractorStub.class.getCanonicalName() + ": Boot path " + bootPath + " not found, falling back to default extraction behavior.");
46+
}
47+
}
48+
}
49+
50+
/**
51+
* If a <code>bootPath</code> was provided to the constructor and exists,
52+
* calculate the <code>File</code> path without any extraction logic.
53+
*
54+
* If a <code>bootPath</code> was NOT provided or does NOT exist, fallback on
55+
* the default extraction behavior.
56+
*/
57+
@Override
58+
public File extractJni(String libPath, String libName) throws IOException {
59+
// Lie and pretend it's already extracted at the bootPath location
60+
if(bootPath != null) {
61+
return new File(bootPath, NativeLibraryUtil.getPlatformLibraryName(libName));
62+
}
63+
// Fallback on default behavior
64+
return super.extractJni(libPath, libName);
65+
}
66+
67+
@Override
68+
public void extractRegistered() throws IOException {
69+
if(bootPath != null) {
70+
return; // no-op
71+
}
72+
super.extractRegistered();
73+
}
74+
}

src/main/java/jssc/SerialNativeInterface.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@ else if(osName.equals("SunOS"))
8585
else if(osName.equals("Mac OS X") || osName.equals("Darwin"))
8686
osType = OS_MAC_OS_X;
8787
try {
88+
/**
89+
* JSSC includes a small, platform-specific shared library and uses native-lib-loader for extraction.
90+
* - First, native-lib-loader will attempt to load this library from the system library path.
91+
* - Next, it will fallback to <code>jssc.boot.library.path</code>
92+
* - Finally it will attempt to extract the library from from the jssc.jar file, and load it.
93+
*/
94+
NativeLoader.setJniExtractor(new DefaultJniExtractorStub(null, System.getProperty("jssc.boot.library.path")));
8895
NativeLoader.loadLibrary("jssc");
8996
} catch (IOException ioException) {
9097
throw new UnsatisfiedLinkError("Could not load the jssc library: " + ioException.getMessage());
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package jssc.bootpath;
2+
3+
import jssc.SerialNativeInterface;
4+
import org.junit.Test;
5+
6+
import static org.junit.Assert.assertTrue;
7+
import static org.junit.Assert.fail;
8+
9+
/**
10+
* Tests if a valid <code>jssc.boot.library.path</code> which does NOT contain a native library
11+
* will predictably fail. This test can be run regardless of whether or not a native binary was
12+
* created during the build process.
13+
*
14+
* TODO: This MUST be in its own class to run in a separate JVM (https://stackoverflow.com/questions/68657855)
15+
* - JUnit does NOT currently offer JVM unloading between methods.
16+
* - maven-surefire-plugin DOES offer JVM unloading between classes using <code>reuseForks=false</code>
17+
* - Unloading is needed due to NativeLoader.loadLibrary(...) calls System.loadLibrary(...) which is static
18+
*/
19+
public class ManualBootLibraryPathFailedTest {
20+
@Test
21+
public void testBootPathOverride() {
22+
String nativeLibDir = "/"; // This should be valid on all platforms
23+
System.setProperty("jssc.boot.library.path", nativeLibDir);
24+
try {
25+
SerialNativeInterface.getNativeLibraryVersion();
26+
fail("Library loading should fail if path provided exists but does not contain a native library");
27+
} catch (UnsatisfiedLinkError ignore) {
28+
assertTrue("Library loading failed as expected with an invalid jssc.boot.library.path", true);
29+
}
30+
}
31+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package jssc.bootpath;
2+
3+
import jssc.SerialNativeInterface;
4+
import org.junit.Test;
5+
import org.scijava.nativelib.NativeLibraryUtil;
6+
7+
import static org.hamcrest.CoreMatchers.*;
8+
import static org.junit.Assert.assertThat;
9+
import static org.junit.Assert.fail;
10+
11+
/**
12+
* Tests if a valid <code>jssc.boot.library.path</code> which DOES contain a native library
13+
* will predictably pass. This test can ONLY be run regardless if a native binary was created
14+
* during the build process. See also <code>maven.exclude.tests</code>.
15+
*
16+
* TODO: This MUST be in its own class to run in a separate JVM (https://stackoverflow.com/questions/68657855)
17+
* - JUnit does NOT currently offer JVM unloading between methods.
18+
* - maven-surefire-plugin DOES offer JVM unloading between classes using <code>reuseForks=false</code>
19+
* - Unloading is needed due to NativeLoader.loadLibrary(...) calls System.loadLibrary(...) which is static
20+
*/
21+
public class ManualBootLibraryPathTest {
22+
@Test
23+
public void testBootPathOverride() {
24+
String nativeLibDir = NativeLibraryUtil.getPlatformLibraryPath(System.getProperty("user.dir") + "/target/cmake/natives/");
25+
System.setProperty("jssc.boot.library.path", nativeLibDir);
26+
try {
27+
final String nativeLibraryVersion = SerialNativeInterface.getNativeLibraryVersion();
28+
assertThat(nativeLibraryVersion, is(not(nullValue())));
29+
assertThat(nativeLibraryVersion, is(not("")));
30+
} catch (UnsatisfiedLinkError linkError) {
31+
linkError.printStackTrace();
32+
fail("Should be able to call method!");
33+
}
34+
}
35+
}

0 commit comments

Comments
 (0)