Skip to content

Add support for a static native library search path #92

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions ant/build.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<entry key="maven.nativelibdir.path" value="${maven.nativelibdir.path}"/>
<entry key="maven.assembly.id" value="${maven.assembly.id}"/>
<entry key="maven.test.skip" value="${maven.test.skip}"/>
<entry key="maven.exclude.tests" value="${maven.exclude.tests}"/>
</propertyfile>
</target>

Expand Down Expand Up @@ -193,6 +194,11 @@
</and>
</condition>

<!-- Skip ManualBootLibraryPathTest test if we're not building a native lib -->
<condition property="maven.exclude.tests" value="**/ManualBootLibraryPathTest.java" else="">
<equals arg1="${cmake.build.skip}" arg2="true"/>
</condition>

<!-- Summarize host/target -->
<echo level="info">Tests will run only if the TARGET and HOST match:${line.separator}${line.separator}</echo>
<echo level="info">TARGET: ${os.target.classifier}</echo>
Expand Down
8 changes: 7 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
<plugin.osmaven.version>1.7.0</plugin.osmaven.version>
<plugin.signature.version>1.1</plugin.signature.version>
<plugin.source.version>3.0.1</plugin.source.version>
<plugin.surfire.version>3.0.0-M3</plugin.surfire.version>
<plugin.surfire.version>3.0.0-M4</plugin.surfire.version>
</properties>

<dependencies>
Expand Down Expand Up @@ -253,6 +253,12 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${plugin.surfire.version}</version>
<configuration>
<reuseForks>false</reuseForks>
<excludes>
<exclude>${maven.exclude.tests}</exclude>
</excludes>
</configuration>
</plugin>

<plugin>
Expand Down
74 changes: 74 additions & 0 deletions src/main/java/jssc/DefaultJniExtractorStub.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* License: https://opensource.org/licenses/BSD-3-Clause
*/
package jssc;

import org.scijava.nativelib.DefaultJniExtractor;
import org.scijava.nativelib.NativeLibraryUtil;

import java.io.File;
import java.io.IOException;

/**
* @author A. Tres Finocchiaro
*
* Stub <code>DefaultJniExtractor</code> class to allow native-lib-loader to conditionally
* use a statically defined native search path <code>bootPath</code> when provided.
*/
public class DefaultJniExtractorStub extends DefaultJniExtractor {
private File bootPath;

/**
* Default constructor
*/
public DefaultJniExtractorStub(Class libraryJarClass) throws IOException {
super(libraryJarClass);
}

/**
* Force native-lib-loader to first look in the location defined as <code>bootPath</code>
* prior to extracting a native library, useful for sandboxed environments.
* <code>
* NativeLoader.setJniExtractor(new DefaultJniExtractorStub(null, "/opt/nativelibs")));
* NativeLoader.loadLibrary("mylibrary");
* </code>
*/
public DefaultJniExtractorStub(Class libraryJarClass, String bootPath) throws IOException {
this(libraryJarClass);

if(bootPath != null) {
File bootTest = new File(bootPath);
if(bootTest.exists()) {
// assume a static, existing directory will contain the native libs
this.bootPath = bootTest;
} else {
System.err.println("WARNING " + DefaultJniExtractorStub.class.getCanonicalName() + ": Boot path " + bootPath + " not found, falling back to default extraction behavior.");
}
}
}

/**
* If a <code>bootPath</code> was provided to the constructor and exists,
* calculate the <code>File</code> path without any extraction logic.
*
* If a <code>bootPath</code> was NOT provided or does NOT exist, fallback on
* the default extraction behavior.
*/
@Override
public File extractJni(String libPath, String libName) throws IOException {
// Lie and pretend it's already extracted at the bootPath location
if(bootPath != null) {
return new File(bootPath, NativeLibraryUtil.getPlatformLibraryName(libName));
}
// Fallback on default behavior
return super.extractJni(libPath, libName);
}

@Override
public void extractRegistered() throws IOException {
if(bootPath != null) {
return; // no-op
}
super.extractRegistered();
}
}
7 changes: 7 additions & 0 deletions src/main/java/jssc/SerialNativeInterface.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ else if(osName.equals("SunOS"))
else if(osName.equals("Mac OS X") || osName.equals("Darwin"))
osType = OS_MAC_OS_X;
try {
/**
* JSSC includes a small, platform-specific shared library and uses native-lib-loader for extraction.
* - First, native-lib-loader will attempt to load this library from the system library path.
* - Next, it will fallback to <code>jssc.boot.library.path</code>
* - Finally it will attempt to extract the library from from the jssc.jar file, and load it.
*/
NativeLoader.setJniExtractor(new DefaultJniExtractorStub(null, System.getProperty("jssc.boot.library.path")));
NativeLoader.loadLibrary("jssc");
} catch (IOException ioException) {
throw new UnsatisfiedLinkError("Could not load the jssc library: " + ioException.getMessage());
Expand Down
31 changes: 31 additions & 0 deletions src/test/java/jssc/bootpath/ManualBootLibraryPathFailedTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package jssc.bootpath;

import jssc.SerialNativeInterface;
import org.junit.Test;

import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

/**
* Tests if a valid <code>jssc.boot.library.path</code> which does NOT contain a native library
* will predictably fail. This test can be run regardless of whether or not a native binary was
* created during the build process.
*
* TODO: This MUST be in its own class to run in a separate JVM (https://stackoverflow.com/questions/68657855)
* - JUnit does NOT currently offer JVM unloading between methods.
* - maven-surefire-plugin DOES offer JVM unloading between classes using <code>reuseForks=false</code>
* - Unloading is needed due to NativeLoader.loadLibrary(...) calls System.loadLibrary(...) which is static
*/
public class ManualBootLibraryPathFailedTest {
@Test
public void testBootPathOverride() {
String nativeLibDir = "/"; // This should be valid on all platforms
System.setProperty("jssc.boot.library.path", nativeLibDir);
try {
SerialNativeInterface.getNativeLibraryVersion();
fail("Library loading should fail if path provided exists but does not contain a native library");
} catch (UnsatisfiedLinkError ignore) {
assertTrue("Library loading failed as expected with an invalid jssc.boot.library.path", true);
}
}
}
35 changes: 35 additions & 0 deletions src/test/java/jssc/bootpath/ManualBootLibraryPathTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package jssc.bootpath;

import jssc.SerialNativeInterface;
import org.junit.Test;
import org.scijava.nativelib.NativeLibraryUtil;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;

/**
* Tests if a valid <code>jssc.boot.library.path</code> which DOES contain a native library
* will predictably pass. This test can ONLY be run regardless if a native binary was created
* during the build process. See also <code>maven.exclude.tests</code>.
*
* TODO: This MUST be in its own class to run in a separate JVM (https://stackoverflow.com/questions/68657855)
* - JUnit does NOT currently offer JVM unloading between methods.
* - maven-surefire-plugin DOES offer JVM unloading between classes using <code>reuseForks=false</code>
* - Unloading is needed due to NativeLoader.loadLibrary(...) calls System.loadLibrary(...) which is static
*/
public class ManualBootLibraryPathTest {
@Test
public void testBootPathOverride() {
String nativeLibDir = NativeLibraryUtil.getPlatformLibraryPath(System.getProperty("user.dir") + "/target/cmake/natives/");
System.setProperty("jssc.boot.library.path", nativeLibDir);
try {
final String nativeLibraryVersion = SerialNativeInterface.getNativeLibraryVersion();
assertThat(nativeLibraryVersion, is(not(nullValue())));
assertThat(nativeLibraryVersion, is(not("")));
} catch (UnsatisfiedLinkError linkError) {
linkError.printStackTrace();
fail("Should be able to call method!");
}
}
}