├── .mailmap ├── .github ├── build.sh ├── setup.sh └── workflows │ └── build.yml ├── .gitignore ├── NOTICE.txt ├── LICENSE.txt ├── src ├── test │ └── java │ │ └── org │ │ └── scijava │ │ └── nativelib │ │ ├── NativeLibraryUtilTest.java │ │ └── NativeLoaderTest.java └── main │ └── java │ └── org │ └── scijava │ └── nativelib │ ├── JniExtractor.java │ ├── DefaultJniExtractor.java │ ├── MxSysInfo.java │ ├── WebappJniExtractor.java │ ├── NativeLoader.java │ ├── BaseJniExtractor.java │ └── NativeLibraryUtil.java ├── README.md └── pom.xml /.mailmap: -------------------------------------------------------------------------------- 1 | Johannes Schindelin 2 | -------------------------------------------------------------------------------- /.github/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | curl -fsLO https://raw.githubusercontent.com/scijava/scijava-scripts/master/ci-build.sh 3 | sh ci-build.sh 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Maven # 2 | /target/ 3 | 4 | # Eclipse # 5 | /.classpath 6 | /.project 7 | /.settings/ 8 | 9 | # IDEA # 10 | /.idea/ 11 | /*.iml 12 | -------------------------------------------------------------------------------- /.github/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | curl -fsLO https://raw.githubusercontent.com/scijava/scijava-scripts/master/ci-setup-github-actions.sh 3 | sh ci-setup-github-actions.sh 4 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - "*-[0-9]+.*" 9 | pull_request: 10 | branches: 11 | - master 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up Java 20 | uses: actions/setup-java@v3 21 | with: 22 | java-version: '8' 23 | distribution: 'zulu' 24 | cache: 'maven' 25 | - name: Set up CI environment 26 | run: .github/setup.sh 27 | - name: Execute the build 28 | run: .github/build.sh 29 | env: 30 | GPG_KEY_NAME: ${{ secrets.GPG_KEY_NAME }} 31 | GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} 32 | MAVEN_USER: ${{ secrets.MAVEN_USER }} 33 | MAVEN_PASS: ${{ secrets.MAVEN_PASS }} 34 | OSSRH_USER: ${{ secrets.OSSRH_USER }} 35 | OSSRH_PASS: ${{ secrets.OSSRH_PASS }} 36 | SIGNING_ASC: ${{ secrets.SIGNING_ASC }} 37 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | Parts of this code are derived from Richard van der Hoff's mx-native-loader project. 2 | 3 | http://opensource.mxtelecom.com/maven/repo/com/wapmx/native/mx-native-loader/1.7/ 4 | 5 | Copyright (c) 2008 MX Telecom 6 | 7 | Permission is hereby granted, free of charge, to any person 8 | obtaining a copy of this software and associated documentation 9 | files (the "Software"), to deal in the Software without 10 | restriction, including without limitation the rights to use, 11 | copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following 14 | conditions: 15 | 16 | The above copyright notice and this permission notice shall be 17 | included in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 21 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 23 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 24 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 26 | OTHER DEALINGS IN THE SOFTWARE. 27 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 - 2023, Board of Regents of the University of 2 | Wisconsin-Madison and Glencoe Software, Inc. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 19 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /src/test/java/org/scijava/nativelib/NativeLibraryUtilTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * Native library loader for extracting and loading native libraries from Java. 4 | * %% 5 | * Copyright (C) 2010 - 2023 Board of Regents of the University of 6 | * Wisconsin-Madison and Glencoe Software, Inc. 7 | * %% 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright notice, 12 | * this list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE 21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | * POSSIBILITY OF SUCH DAMAGE. 28 | * #L% 29 | */ 30 | 31 | package org.scijava.nativelib; 32 | 33 | import static org.junit.Assert.assertEquals; 34 | 35 | import org.junit.Test; 36 | 37 | public class NativeLibraryUtilTest { 38 | 39 | @Test 40 | public void ifNoVersionWasFoundLibraryNameIsReturned() throws Exception { 41 | final String versionedLibraryName = 42 | NativeLibraryUtil.getVersionedLibraryName(NativeLibraryUtil.class, 43 | "native-lib-loader"); 44 | assertEquals("native-lib-loader", versionedLibraryName); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![](https://img.shields.io/maven-central/v/org.scijava/native-lib-loader.svg)](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.scijava%22%20AND%20a%3A%22native-lib-loader%22) 2 | [![](https://github.com/scijava/native-lib-loader/actions/workflows/build-main.yml/badge.svg)](https://github.com/scijava/native-lib-loader/actions/workflows/build-main.yml) 3 | 4 | # About native library loader 5 | 6 | The native library loader is a utility that assists with loading native 7 | libraries from Java. It provides the ability to painlessly identify, extract 8 | and load the correct platform-specific native library from a JAR file. 9 | 10 | 11 | ##### License 12 | 13 | Simplified BSD License 14 | 15 | 16 | # Usage 17 | 18 | ### Add dependency 19 | 20 | Search Maven Central for [latest version](http://search.maven.org/#search|ga|1|a:native-lib-loader) 21 | and add a dependency to your pom.xml. 22 | 23 | ```xml 24 | 25 | org.scijava 26 | native-lib-loader 27 | x.y.z 28 | 29 | ``` 30 | 31 | ### Package native libraries 32 | 33 | Native libraries should be packaged into a single jar file, with the 34 | following directory & file structure: 35 | 36 | ``` 37 | /natives 38 | /linux_32 39 | libxxx[-vvv].so 40 | /linux_64 41 | libxxx[-vvv].so 42 | /osx_32 43 | libxxx[-vvv].dylib 44 | /osx_64 45 | libxxx[-vvv].dylib 46 | /osx_arm64 47 | libxxx[-vvv].dylib 48 | /windows_32 49 | xxx[-vvv].dll 50 | /windows_64 51 | xxx[-vvv].dll 52 | /windows_arm64 53 | xxx[-vvv].dll 54 | /aix_32 55 | libxxx[-vvv].so 56 | libxxx[-vvv].a 57 | /aix_64 58 | libxxx[-vvv].so 59 | libxxx[-vvv].a 60 | ``` 61 | 62 | Here "xxx" is the name of the native library and "-vvv" is an optional version number. 63 | Depending on the platform at runtime, a native library will be unpacked into a temporary file 64 | and will be loaded from there. 65 | 66 | The version information will be grabbed from the MANIFEST.mf file 67 | from "Implementation-Version" entry. So it's recommended to follow Java's 68 | [package version information](https://docs.oracle.com/javase/tutorial/deployment/jar/packageman.html) 69 | convention. 70 | 71 | ### Load library 72 | 73 | If you want to load 'awesome.dll' (on Windows) or 'libawesome.so' (on Linux or AIX), 74 | simply do like this ... 75 | 76 | ```Java 77 | NativeLoader.loadLibrary("awesome"); 78 | ``` 79 | -------------------------------------------------------------------------------- /src/main/java/org/scijava/nativelib/JniExtractor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * Native library loader for extracting and loading native libraries from Java. 4 | * %% 5 | * Copyright (C) 2010 - 2023 Board of Regents of the University of 6 | * Wisconsin-Madison and Glencoe Software, Inc. 7 | * %% 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright notice, 12 | * this list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE 21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | * POSSIBILITY OF SUCH DAMAGE. 28 | * #L% 29 | */ 30 | 31 | // This code is derived from Richard van der Hoff's mx-native-loader project: 32 | // http://opensource.mxtelecom.com/maven/repo/com/wapmx/native/mx-native-loader/1.7/ 33 | // See NOTICE.txt for details. 34 | 35 | // Copyright 2006 MX Telecom Ltd 36 | 37 | package org.scijava.nativelib; 38 | 39 | import java.io.File; 40 | import java.io.IOException; 41 | 42 | /** 43 | * @author Richard van der Hoff (richardv@mxtelecom.com) 44 | */ 45 | public interface JniExtractor { 46 | 47 | /** 48 | * Extract a JNI library from the classpath to a temporary file. 49 | * 50 | * @param libPath library path 51 | * @param libname System.loadLibrary() compatible library name 52 | * @return the extracted file 53 | * @throws IOException when extracting the desired file failed 54 | */ 55 | public File extractJni(String libPath, String libname) throws IOException; 56 | 57 | /** 58 | * Extract all libraries which are registered for auto-extraction to files in 59 | * the temporary directory. 60 | * 61 | * @throws IOException when extracting the desired file failed 62 | */ 63 | public void extractRegistered() throws IOException; 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/org/scijava/nativelib/DefaultJniExtractor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * Native library loader for extracting and loading native libraries from Java. 4 | * %% 5 | * Copyright (C) 2010 - 2023 Board of Regents of the University of 6 | * Wisconsin-Madison and Glencoe Software, Inc. 7 | * %% 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright notice, 12 | * this list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE 21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | * POSSIBILITY OF SUCH DAMAGE. 28 | * #L% 29 | */ 30 | 31 | // This code is derived from Richard van der Hoff's mx-native-loader project: 32 | // http://opensource.mxtelecom.com/maven/repo/com/wapmx/native/mx-native-loader/1.7/ 33 | // See NOTICE.txt for details. 34 | 35 | // Copyright 2009 MX Telecom Ltd 36 | 37 | package org.scijava.nativelib; 38 | 39 | import java.io.File; 40 | import java.io.IOException; 41 | 42 | /** 43 | * JniExtractor suitable for single application deployments per virtual machine 44 | *

45 | * WARNING: This extractor can result in UnsatisifiedLinkError if it is used in 46 | * more than one classloader. 47 | * 48 | * @author Richard van der Hoff (richardv@mxtelecom.com) 49 | */ 50 | public class DefaultJniExtractor extends BaseJniExtractor { 51 | 52 | /** 53 | * this is where native dependencies are extracted to (e.g. tmplib/). 54 | */ 55 | private File nativeDir; 56 | 57 | public DefaultJniExtractor(final Class libraryJarClass) throws IOException { 58 | super(libraryJarClass); 59 | 60 | nativeDir = getTempDir(); 61 | // Order of operations is such that we do not error if we are racing with 62 | // another thread to create the directory. 63 | nativeDir.mkdirs(); 64 | if (!nativeDir.isDirectory()) { 65 | throw new IOException( 66 | "Unable to create native library working directory " + nativeDir); 67 | } 68 | nativeDir.deleteOnExit(); 69 | } 70 | 71 | @Override 72 | public File getJniDir() { 73 | return nativeDir; 74 | } 75 | 76 | @Override 77 | public File getNativeDir() { 78 | return nativeDir; 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/org/scijava/nativelib/MxSysInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * Native library loader for extracting and loading native libraries from Java. 4 | * %% 5 | * Copyright (C) 2010 - 2023 Board of Regents of the University of 6 | * Wisconsin-Madison and Glencoe Software, Inc. 7 | * %% 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright notice, 12 | * this list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE 21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | * POSSIBILITY OF SUCH DAMAGE. 28 | * #L% 29 | */ 30 | 31 | package org.scijava.nativelib; 32 | 33 | import java.io.File; 34 | import java.io.IOException; 35 | import java.util.regex.Matcher; 36 | import java.util.regex.Pattern; 37 | 38 | public class MxSysInfo { 39 | 40 | /** 41 | * Find the mx.sysinfo string for the current jvm 42 | *

43 | * Can be overridden by specifying a mx.sysinfo system property 44 | * 45 | * @return the specified mx.sysinfo or a guessed one 46 | */ 47 | public static String getMxSysInfo() { 48 | final String mxSysInfo = System.getProperty("mx.sysinfo"); 49 | return mxSysInfo != null ? mxSysInfo : guessMxSysInfo(); 50 | } 51 | 52 | /** 53 | * Make a spirited attempt at guessing what the mx.sysinfo for the current jvm 54 | * might be. 55 | * 56 | * @return the guessed mx.sysinfo 57 | */ 58 | public static String guessMxSysInfo() { 59 | final String arch = System.getProperty("os.arch"); 60 | final String os = System.getProperty("os.name"); 61 | String extra = "unknown"; 62 | 63 | if ("Linux".equals(os)) { 64 | try { 65 | final String libc_dest = new File("/lib/libc.so.6").getCanonicalPath(); 66 | final Matcher libc_m = 67 | Pattern.compile(".*/libc-(\\d+)\\.(\\d+)\\..*").matcher(libc_dest); 68 | if (!libc_m.matches()) throw new IOException( 69 | "libc symlink contains unexpected destination: " + libc_dest); 70 | 71 | File libstdcxx_file = new File("/usr/lib/libstdc++.so.6"); 72 | if (!libstdcxx_file.exists()) libstdcxx_file = 73 | new File("/usr/lib/libstdc++.so.5"); 74 | 75 | final String libstdcxx_dest = libstdcxx_file.getCanonicalPath(); 76 | final Matcher libstdcxx_m = 77 | Pattern.compile(".*/libstdc\\+\\+\\.so\\.(\\d+)\\.0\\.(\\d+)") 78 | .matcher(libstdcxx_dest); 79 | if (!libstdcxx_m.matches()) throw new IOException( 80 | "libstdc++ symlink contains unexpected destination: " + 81 | libstdcxx_dest); 82 | String cxxver; 83 | if ("5".equals(libstdcxx_m.group(1))) { 84 | cxxver = "5"; 85 | } 86 | else if ("6".equals(libstdcxx_m.group(1))) { 87 | final int minor_ver = Integer.parseInt(libstdcxx_m.group(2)); 88 | if (minor_ver < 9) { 89 | cxxver = "6"; 90 | } 91 | else { 92 | cxxver = "6" + libstdcxx_m.group(2); 93 | } 94 | } 95 | else { 96 | cxxver = libstdcxx_m.group(1) + libstdcxx_m.group(2); 97 | } 98 | 99 | extra = "c" + libc_m.group(1) + libc_m.group(2) + "cxx" + cxxver; 100 | } 101 | catch (final IOException e) { 102 | extra = "unknown"; 103 | } 104 | } 105 | 106 | return arch + "-" + os + "-" + extra; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/org/scijava/nativelib/WebappJniExtractor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * Native library loader for extracting and loading native libraries from Java. 4 | * %% 5 | * Copyright (C) 2010 - 2023 Board of Regents of the University of 6 | * Wisconsin-Madison and Glencoe Software, Inc. 7 | * %% 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright notice, 12 | * this list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE 21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | * POSSIBILITY OF SUCH DAMAGE. 28 | * #L% 29 | */ 30 | 31 | // This code is derived from Richard van der Hoff's mx-native-loader project: 32 | // http://opensource.mxtelecom.com/maven/repo/com/wapmx/native/mx-native-loader/1.7/ 33 | // See NOTICE.txt for details. 34 | 35 | // Copyright 2009 MX Telecom Ltd 36 | 37 | package org.scijava.nativelib; 38 | 39 | import java.io.File; 40 | import java.io.IOException; 41 | 42 | /** 43 | * JniExtractor suitable for multiple application deployments on the same 44 | * virtual machine (such as webapps) 45 | *

46 | * Designed to avoid the restriction that jni library can be loaded by at most 47 | * one classloader at a time. 48 | *

49 | * Works by extracting each library to a different location for each 50 | * classloader. 51 | *

52 | * WARNING: This can expose strange and wonderful bugs in jni code. These bugs 53 | * generally stem from transitive dependencies of the jni library and can be 54 | * solved by linking these dependencies statically to form a single library 55 | * 56 | * @author markjh 57 | */ 58 | public class WebappJniExtractor extends BaseJniExtractor { 59 | 60 | private final File nativeDir; 61 | private final File jniSubDir; 62 | 63 | /** 64 | * @param classloaderName is a friendly name for your classloader which will 65 | * be embedded in the directory name of the classloader-specific 66 | * subdirectory which will be created. 67 | */ 68 | public WebappJniExtractor(final String classloaderName) throws IOException { 69 | nativeDir = getTempDir(); 70 | // Order of operations is such thatwe do not error if we are racing with 71 | // another thread to create the directory. 72 | nativeDir.mkdirs(); 73 | if (!nativeDir.isDirectory()) { 74 | throw new IOException( 75 | "Unable to create native library working directory " + nativeDir); 76 | } 77 | 78 | final long now = System.currentTimeMillis(); 79 | File trialJniSubDir; 80 | int attempt = 0; 81 | while (true) { 82 | trialJniSubDir = 83 | new File(nativeDir, classloaderName + "." + now + "." + attempt); 84 | if (trialJniSubDir.mkdir()) break; 85 | if (trialJniSubDir.exists()) { 86 | attempt++; 87 | continue; 88 | } 89 | throw new IOException( 90 | "Unable to create native library working directory " + trialJniSubDir); 91 | } 92 | jniSubDir = trialJniSubDir; 93 | jniSubDir.deleteOnExit(); 94 | } 95 | 96 | @Override 97 | protected void finalize() throws Throwable { 98 | super.finalize(); 99 | final File[] files = jniSubDir.listFiles(); 100 | for (final File file : files) { 101 | file.delete(); 102 | } 103 | jniSubDir.delete(); 104 | } 105 | 106 | @Override 107 | public File getJniDir() { 108 | return jniSubDir; 109 | } 110 | 111 | @Override 112 | public File getNativeDir() { 113 | return nativeDir; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/test/java/org/scijava/nativelib/NativeLoaderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * Native library loader for extracting and loading native libraries from Java. 4 | * %% 5 | * Copyright (C) 2010 - 2023 Board of Regents of the University of 6 | * Wisconsin-Madison and Glencoe Software, Inc. 7 | * %% 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright notice, 12 | * this list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE 21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | * POSSIBILITY OF SUCH DAMAGE. 28 | * #L% 29 | */ 30 | 31 | package org.scijava.nativelib; 32 | 33 | import static org.junit.Assert.assertTrue; 34 | 35 | import java.io.File; 36 | import java.io.FileInputStream; 37 | import java.io.FileOutputStream; 38 | import java.io.IOException; 39 | import java.lang.reflect.Method; 40 | import java.net.URL; 41 | import java.net.URLClassLoader; 42 | import java.util.Locale; 43 | import java.util.jar.*; 44 | 45 | import org.junit.Rule; 46 | import org.junit.Test; 47 | import org.junit.rules.TemporaryFolder; 48 | 49 | public class NativeLoaderTest { 50 | 51 | @Rule 52 | public TemporaryFolder tmpTestDir = new TemporaryFolder(); 53 | 54 | // Creates a temporary jar with a dummy lib in it for testing extractiong 55 | private void createJar() throws Exception { 56 | // create a jar file... 57 | File dummyJar = tmpTestDir.newFile("dummy.jar"); 58 | Manifest manifest = new Manifest(); 59 | manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); 60 | JarOutputStream target = null; 61 | try { 62 | target = new JarOutputStream(new FileOutputStream(dummyJar), manifest); 63 | 64 | // with a dummy binary in it 65 | File source = new File(String.format("natives/%s/%s", 66 | NativeLibraryUtil.getArchitecture().name().toLowerCase(Locale.ENGLISH), 67 | NativeLibraryUtil.getPlatformLibraryName("dummy"))); 68 | JarEntry entry = new JarEntry(source.getPath().replace("\\", "/")); 69 | entry.setTime(System.currentTimeMillis()); 70 | target.putNextEntry(entry); 71 | 72 | // fill the file... 73 | byte[] buffer = "native-lib-loader".getBytes(); 74 | target.write(buffer, 0, buffer.length); 75 | target.closeEntry(); 76 | } finally { 77 | if (target != null) { target.close(); } 78 | } 79 | 80 | // and add to classpath as if it is a dependency of the project 81 | Method addURLMethod = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class}); 82 | addURLMethod.setAccessible(true); 83 | addURLMethod.invoke(ClassLoader.getSystemClassLoader(), new Object[]{ dummyJar.toURI().toURL() }); 84 | } 85 | 86 | @Test(expected = IOException.class) 87 | public void exampleHowToUse() throws Exception { 88 | NativeLoader.loadLibrary("mylib"); 89 | // expect IOException, because this lib does not exist 90 | } 91 | 92 | @Test 93 | public void testExtracting() throws Exception { 94 | // NB: one may want to find a way to remove the used (deleted) jars from 95 | // classpath. Otherwise, ClassLoader.getResource will not discover the new 96 | // jar if there is another test. 97 | createJar(); 98 | // see if dummy is correctly extracted 99 | Locale originalLocale = Locale.getDefault(); 100 | Locale.setDefault(new Locale("tr", "TR")); 101 | JniExtractor jniExtractor = new DefaultJniExtractor(null); 102 | String libPath = String.format("natives/%s", 103 | NativeLibraryUtil.getArchitecture().name().toLowerCase(Locale.ENGLISH)); 104 | File extracted = jniExtractor.extractJni(libPath + "", "dummy"); 105 | 106 | FileInputStream in = null; 107 | try { 108 | in = new FileInputStream(extracted); 109 | byte[] buffer = new byte[32]; 110 | in.read(buffer, 0, buffer.length); 111 | assertTrue(new String(buffer).trim().equals("native-lib-loader")); 112 | } finally { 113 | if (in != null) { in.close(); } 114 | Locale.setDefault(originalLocale); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | org.scijava 7 | pom-scijava 8 | 37.0.0 9 | 10 | 11 | 12 | native-lib-loader 13 | 2.5.1-SNAPSHOT 14 | 15 | Native Library Loader 16 | A library for loading native libraries. 17 | https://github.com/scijava/native-lib-loader 18 | 2010 19 | 20 | SciJava 21 | https://scijava.org/ 22 | 23 | 24 | 25 | Simplified BSD License 26 | repo 27 | 28 | 29 | 30 | 31 | 32 | ctrueden 33 | Curtis Rueden 34 | https://imagej.net/User:Rueden 35 | 36 | lead 37 | developer 38 | debugger 39 | reviewer 40 | support 41 | maintainer 42 | 43 | 44 | 45 | 46 | 47 | Aivar Grislis 48 | https://imagej.net/User:Grislis 49 | founder 50 | grislis 51 | 52 | 53 | Johannes Schindelin 54 | https://imagej.net/User:Schindelin 55 | dscho 56 | 57 | 58 | Mark Hiner 59 | https://imagej.net/User:Hinerm 60 | hinerm 61 | 62 | 63 | Melissa Linkert 64 | https://imagej.net/User:Linkert 65 | melissalinkert 66 | 67 | 68 | Sebastien Besson 69 | https://imagej.net/User:Sbesson 70 | sbesson 71 | 72 | 73 | Mark J H 74 | MX Telecom 75 | 76 | 77 | Richard van der Hoff 78 | MX Telecom 79 | founder 80 | 81 | Scott Cyphers 82 | Yuriy Cherniavsky 83 | 84 | Martin W. Kirst 85 | nitram509 86 | 87 | 88 | 89 | 90 | 91 | SciJava 92 | https://groups.google.com/group/scijava 93 | https://groups.google.com/group/scijava 94 | scijava@googlegroups.com 95 | https://groups.google.com/group/scijava 96 | 97 | 98 | 99 | 100 | scm:git:https://github.com/scijava/native-lib-loader 101 | scm:git:git@github.com:scijava/native-lib-loader 102 | HEAD 103 | https://github.com/scijava/native-lib-loader 104 | 105 | 106 | GitHub Issues 107 | https://github.com/scijava/native-lib-loader/issues 108 | 109 | 110 | GitHub Actions 111 | https://github.com/scijava/native-lib-loader/actions 112 | 113 | 114 | 115 | org.scijava.nativelib 116 | bsd_2 117 | Board of Regents of the University of 118 | Wisconsin-Madison and Glencoe Software, Inc. 119 | Native library loader for extracting and loading native libraries from Java. 120 | 1.6 121 | 1.17 122 | 123 | 124 | 125 | 126 | org.slf4j 127 | slf4j-api 128 | 129 | 130 | junit 131 | junit 132 | test 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | org.codehaus.mojo 142 | animal-sniffer-maven-plugin 143 | ${plugin.animalsniffer.version} 144 | 145 | 146 | org.codehaus.mojo.signature 147 | java16 148 | 1.1 149 | 150 | 151 | 152 | 153 | ensure-java-1.6-class-library 154 | verify 155 | 156 | check 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /src/main/java/org/scijava/nativelib/NativeLoader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * Native library loader for extracting and loading native libraries from Java. 4 | * %% 5 | * Copyright (C) 2010 - 2023 Board of Regents of the University of 6 | * Wisconsin-Madison and Glencoe Software, Inc. 7 | * %% 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright notice, 12 | * this list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE 21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | * POSSIBILITY OF SUCH DAMAGE. 28 | * #L% 29 | */ 30 | 31 | // This code is derived from Richard van der Hoff's mx-native-loader project: 32 | // http://opensource.mxtelecom.com/maven/repo/com/wapmx/native/mx-native-loader/1.7/ 33 | // See NOTICE.txt for details. 34 | 35 | // Copyright 2006 MX Telecom Ltd 36 | 37 | package org.scijava.nativelib; 38 | 39 | import java.io.IOException; 40 | 41 | /** 42 | * Provides a means of loading JNI libraries which are stored within a jar. 43 | *

44 | * The library is first extracted to a temporary file, and then loaded with 45 | * System.load(). 46 | *

47 | * The extractor implementation can be replaced, but the default implementation 48 | * expects to find the library in natives/, with its OS-dependent name. It 49 | * extracts the library underneath a temporary directory, whose name is given by 50 | * the System property "java.library.tmpdir", defaulting to "tmplib". 51 | *

52 | * This is complicated by Java's library and version management - specifically 55 | * "The same JNI native library cannot be loaded into more than one class loader" 56 | * . In practice this appears to mean 57 | * "A JNI library on a given absolute path cannot be loaded by more than one classloader" 58 | * . Native libraries that are loaded by the OS dynamic linker as dependencies 59 | * of JNI libraries are not subject to this restriction. 60 | *

61 | * Native libraries that are loaded as dependencies must be extracted using the 62 | * library identifier a.k.a. soname (which usually includes a major version 63 | * number) instead of what was linked against (this can be found using ldd on 64 | * linux or using otool on OS X). Because they are loaded by the OS dynamic 65 | * linker and not by explicit invocation within Java, this extractor needs to be 66 | * aware of them to extract them by alternate means. This is accomplished by 67 | * listing the base filename in a META-INF/lib/AUTOEXTRACT.LIST classpath 68 | * resource. This is useful for shipping libraries which are used by code which 69 | * is not itself aware of the NativeLoader system. The application must call 70 | * {@link #extractRegistered()} at some suitably early point in its 71 | * initialization (before loading any JNI libraries which might require these 72 | * dependencies), and ensure that JVM is launched with the LD_LIBRARY_PATH 73 | * environment variable (or other OS-dependent equivalent) set to include the 74 | * "tmplib" directory (or other directory as overridden by "java.library.tmpdir" 75 | * as above). 76 | * 77 | * @author Richard van der Hoff (richardv@mxtelecom.com) 78 | */ 79 | public class NativeLoader { 80 | 81 | private static JniExtractor jniExtractor = null; 82 | 83 | static { 84 | try { 85 | /* 86 | * We provide two implementations of JniExtractor 87 | * 88 | * The first will work with transitively, dynamically linked libraries with shared global variables 89 | * (e.g. dynamically linked c++) but can only be used by one ClassLoader in the JVM. 90 | * 91 | * The second can be used by multiple ClassLoaders in the JVM but will only work if global variables 92 | * are not shared between transitively, dynamically linked libraries. 93 | * 94 | * For convenience we assume that if the NativeLoader is loaded by the system ClassLoader then it should be 95 | * use the first form, and that if it is loaded by a different ClassLoader then it should use the second. 96 | */ 97 | if (NativeLoader.class.getClassLoader() == ClassLoader 98 | .getSystemClassLoader()) 99 | { 100 | jniExtractor = new DefaultJniExtractor(null); 101 | } 102 | else { 103 | jniExtractor = new WebappJniExtractor("Classloader"); 104 | } 105 | } 106 | catch (final IOException e) { 107 | throw new ExceptionInInitializerError(e); 108 | } 109 | } 110 | 111 | /** 112 | * Extract the given library from a jar, and load it. 113 | *

114 | * The default jni extractor expects libraries to be in natives/<platform>/ 115 | * with their platform-dependent name (e.g. natives/osx_64/libnative.dylib). 116 | *

117 | * If natives/ does not exists or does not contain the directory structure, 118 | * <platform>/<lib_binary> will be searched in the root, 119 | * META-INF/lib/ and searchPaths. 120 | * 121 | * @param libName platform-independent library name (as would be passed to 122 | * System.loadLibrary) 123 | * @param searchPaths a list of additional paths relative to the jar's root 124 | * to search for the specified native library in case it does not 125 | * exist in natives/, root or META-INF/lib/ 126 | * @throws IOException if there is a problem extracting the jni library 127 | * @throws SecurityException if a security manager exists and its 128 | * checkLink method doesn't allow loading of the 129 | * specified dynamic library 130 | */ 131 | public static void loadLibrary(final String libName, 132 | final String... searchPaths) throws IOException 133 | { 134 | try { 135 | // try to load library from classpath 136 | System.loadLibrary(libName); 137 | } 138 | catch (final UnsatisfiedLinkError e) { 139 | if (NativeLibraryUtil.loadNativeLibrary(jniExtractor, libName, 140 | searchPaths)) return; 141 | throw new IOException("Couldn't load library library " + libName, e); 142 | } 143 | } 144 | 145 | /** 146 | * Extract all libraries registered for auto-extraction by way of 147 | * META-INF/lib/AUTOEXTRACT.LIST resources. The application must call 148 | * {@link #extractRegistered()} at some suitably early point in its 149 | * initialization if it is using libraries packaged in this way. 150 | * 151 | * @throws IOException if there is a problem extracting the libraries 152 | */ 153 | public static void extractRegistered() throws IOException { 154 | jniExtractor.extractRegistered(); 155 | } 156 | 157 | /** 158 | * @return the JniExtractor implementation object. 159 | */ 160 | public static JniExtractor getJniExtractor() { 161 | return jniExtractor; 162 | } 163 | 164 | /** 165 | * @param jniExtractor JniExtractor implementation to use instead of the 166 | * default. 167 | */ 168 | public static void setJniExtractor(final JniExtractor jniExtractor) { 169 | NativeLoader.jniExtractor = jniExtractor; 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/main/java/org/scijava/nativelib/BaseJniExtractor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * Native library loader for extracting and loading native libraries from Java. 4 | * %% 5 | * Copyright (C) 2010 - 2023 Board of Regents of the University of 6 | * Wisconsin-Madison and Glencoe Software, Inc. 7 | * %% 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright notice, 12 | * this list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE 21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | * POSSIBILITY OF SUCH DAMAGE. 28 | * #L% 29 | */ 30 | 31 | // This code is derived from Richard van der Hoff's mx-native-loader project: 32 | // http://opensource.mxtelecom.com/maven/repo/com/wapmx/native/mx-native-loader/1.7/ 33 | // See NOTICE.txt for details. 34 | 35 | // Copyright 2006 MX Telecom Ltd 36 | 37 | package org.scijava.nativelib; 38 | 39 | import java.io.BufferedReader; 40 | import java.io.File; 41 | import java.io.FileOutputStream; 42 | import java.io.FilenameFilter; 43 | import java.io.IOException; 44 | import java.io.InputStream; 45 | import java.io.InputStreamReader; 46 | import java.io.OutputStream; 47 | import java.net.URL; 48 | import java.net.URLConnection; 49 | import java.util.Enumeration; 50 | 51 | import org.slf4j.Logger; 52 | import org.slf4j.LoggerFactory; 53 | 54 | /** 55 | * @author Richard van der Hoff (richardv@mxtelecom.com) 56 | */ 57 | public abstract class BaseJniExtractor implements JniExtractor { 58 | 59 | private static final Logger LOGGER = LoggerFactory.getLogger( 60 | "org.scijava.nativelib.BaseJniExtractor"); 61 | protected static final String JAVA_TMPDIR = "java.io.tmpdir"; 62 | protected static final String ALTR_TMPDIR = "."+ NativeLibraryUtil.DELIM + "tmplib"; 63 | protected static final String TMP_PREFIX = "nativelib-loader_"; 64 | private static final String LEFTOVER_MIN_AGE = "org.scijava.nativelib.leftoverMinAgeMs"; 65 | private static final long LEFTOVER_MIN_AGE_DEFAULT = 5 * 60 * 1000; // 5 minutes 66 | 67 | private Class libraryJarClass; 68 | 69 | /** 70 | * We use a resource path of the form META-INF/lib/${mx.sysinfo}/ This way 71 | * native builds for multiple architectures can be packaged together without 72 | * interfering with each other And by setting mx.sysinfo the jvm can pick the 73 | * native libraries appropriate for itself. 74 | */ 75 | private String[] nativeResourcePaths; 76 | 77 | public BaseJniExtractor() throws IOException { 78 | init(null); 79 | } 80 | 81 | public BaseJniExtractor(final Class libraryJarClass) throws IOException { 82 | init(libraryJarClass); 83 | } 84 | 85 | private void init(final Class libraryJarClass) { 86 | this.libraryJarClass = libraryJarClass; 87 | 88 | final String mxSysInfo = MxSysInfo.getMxSysInfo(); 89 | 90 | if (mxSysInfo != null) { 91 | nativeResourcePaths = 92 | new String[] { "natives/", "META-INF/lib/" + mxSysInfo + "/", "META-INF/lib/" }; 93 | } 94 | else { 95 | nativeResourcePaths = new String[] { "natives/", "META-INF/lib/" }; 96 | } 97 | // clean up leftover libraries from previous runs 98 | deleteLeftoverFiles(); 99 | } 100 | 101 | private static boolean deleteRecursively(final File directory) { 102 | if (directory == null) return true; 103 | final File[] list = directory.listFiles(); 104 | if (list == null) return true; 105 | for (final File file : list) { 106 | if (file.isFile()) { 107 | if (!file.delete()) return false; 108 | } 109 | else if (file.isDirectory()) { 110 | if (!deleteRecursively(file)) return false; 111 | } 112 | } 113 | return directory.delete(); 114 | } 115 | 116 | protected static File getTempDir() throws IOException { 117 | // creates a temporary directory for hosting extracted files 118 | // If system tempdir is not available, use tmplib 119 | File tmpDir = new File(System.getProperty(JAVA_TMPDIR, ALTR_TMPDIR)); 120 | if (!tmpDir.isDirectory()) { 121 | tmpDir.mkdirs(); 122 | if (!tmpDir.isDirectory()) 123 | throw new IOException("Unable to create temporary directory " + tmpDir); 124 | } 125 | 126 | File tempFile = File.createTempFile(TMP_PREFIX, ""); 127 | tempFile.delete(); 128 | return tempFile; 129 | } 130 | 131 | /** 132 | * this is where native dependencies are extracted to (e.g. tmplib/). 133 | * 134 | * @return native working dir 135 | */ 136 | public abstract File getNativeDir(); 137 | 138 | /** 139 | * this is where JNI libraries are extracted to (e.g. 140 | * tmplib/classloaderName.1234567890000.0/). 141 | * 142 | * @return jni working dir 143 | */ 144 | public abstract File getJniDir(); 145 | 146 | @Override 147 | public File extractJni(final String libPath, final String libname) 148 | throws IOException 149 | { 150 | String mappedlibName = System.mapLibraryName(libname); 151 | debug("mappedLib is " + mappedlibName); 152 | /* 153 | * On Darwin, the default mapping is to .jnilib; but we use .dylibs so that library interdependencies are 154 | * handled correctly. if we don't find a .jnilib, try .dylib instead. 155 | */ 156 | URL lib = null; 157 | 158 | // if no class specified look for resources in the jar of this class 159 | if (null == libraryJarClass) { 160 | libraryJarClass = this.getClass(); 161 | } 162 | 163 | // foolproof 164 | String combinedPath = (libPath.equals("") || libPath.endsWith(NativeLibraryUtil.DELIM) ? 165 | libPath : libPath + NativeLibraryUtil.DELIM) + mappedlibName; 166 | lib = libraryJarClass.getClassLoader().getResource(combinedPath); 167 | if (null == lib) { 168 | /* 169 | * On OS X, the default mapping changed from .jnilib to .dylib as of JDK 7, so 170 | * we need to be prepared for the actual library and mapLibraryName disagreeing 171 | * in either direction. 172 | */ 173 | final String altLibName; 174 | if (mappedlibName.endsWith(".jnilib")) { 175 | altLibName = 176 | mappedlibName.substring(0, mappedlibName.length() - 7) + ".dylib"; 177 | } 178 | else if (mappedlibName.endsWith(".dylib")) { 179 | altLibName = 180 | mappedlibName.substring(0, mappedlibName.length() - 6) + ".jnilib"; 181 | } 182 | else { 183 | altLibName = null; 184 | } 185 | if (altLibName != null) { 186 | lib = getClass().getClassLoader().getResource(libPath + altLibName); 187 | if (lib != null) { 188 | mappedlibName = altLibName; 189 | } 190 | } 191 | } 192 | 193 | if (null != lib) { 194 | debug("URL is " + lib.toString()); 195 | debug("URL path is " + lib.getPath()); 196 | return extractResource(getJniDir(), lib, mappedlibName); 197 | } 198 | debug("Couldn't find resource " + combinedPath); 199 | return null; 200 | } 201 | 202 | @Override 203 | public void extractRegistered() throws IOException { 204 | debug("Extracting libraries registered in classloader " + 205 | this.getClass().getClassLoader()); 206 | for (final String nativeResourcePath : nativeResourcePaths) { 207 | final Enumeration resources = 208 | this.getClass().getClassLoader().getResources( 209 | nativeResourcePath + "AUTOEXTRACT.LIST"); 210 | while (resources.hasMoreElements()) { 211 | final URL res = resources.nextElement(); 212 | extractLibrariesFromResource(res); 213 | } 214 | } 215 | } 216 | 217 | private void extractLibrariesFromResource(final URL resource) 218 | throws IOException 219 | { 220 | debug("Extracting libraries listed in " + resource); 221 | BufferedReader reader = null; 222 | try { 223 | URLConnection connection = resource.openConnection(); 224 | connection.setUseCaches(false); 225 | reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8")); 226 | for (String line; (line = reader.readLine()) != null;) { 227 | URL lib = null; 228 | for (final String nativeResourcePath : nativeResourcePaths) { 229 | lib = 230 | this.getClass().getClassLoader().getResource( 231 | nativeResourcePath + line); 232 | if (lib != null) break; 233 | } 234 | if (lib != null) { 235 | extractResource(getNativeDir(), lib, line); 236 | } 237 | else { 238 | throw new IOException("Couldn't find native library " + line + 239 | "on the classpath"); 240 | } 241 | } 242 | } 243 | finally { 244 | if (reader != null) { 245 | reader.close(); 246 | } 247 | } 248 | } 249 | 250 | /** 251 | * Extract a resource to the tmp dir (this entry point is used for unit 252 | * testing) 253 | * 254 | * @param dir the directory to extract the resource to 255 | * @param resource the resource on the classpath 256 | * @param outputName the filename to copy to (within the tmp dir) 257 | * @return the extracted file 258 | * @throws IOException 259 | */ 260 | File extractResource(final File dir, final URL resource, 261 | final String outputName) throws IOException 262 | { 263 | InputStream in = null; 264 | try { 265 | URLConnection connection = resource.openConnection(); 266 | connection.setUseCaches(false); 267 | in = connection.getInputStream(); 268 | // TODO there's also a getResourceAsStream 269 | 270 | // make a lib file with exactly the same lib name 271 | final File outfile = new File(getJniDir(), outputName); 272 | debug("Extracting '" + resource + "' to '" + 273 | outfile.getAbsolutePath() + "'"); 274 | 275 | // copy resource stream to temporary file 276 | FileOutputStream out = null; 277 | try { 278 | out = new FileOutputStream(outfile); 279 | copy(in, out); 280 | } finally { 281 | if (out != null) { out.close(); } 282 | } 283 | 284 | // note that this doesn't always work: 285 | outfile.deleteOnExit(); 286 | 287 | return outfile; 288 | } finally { 289 | if (in != null) { in.close(); } 290 | } 291 | } 292 | 293 | /** 294 | * Looks in the temporary directory for leftover versions of temporary shared 295 | * libraries. 296 | *

297 | * If a temporary shared library is in use by another instance it won't 298 | * delete. 299 | *

300 | * An old library will be deleted only if its last modified date is at least 301 | * LEFTOVER_MIN_AGE milliseconds old (default to 5 minutes) 302 | * This was introduced to avoid a possible race condition when two instances (JVMs) run the same unpacking code 303 | * and one of which manage to delete the extracted file of the other before the other gets a chance to load it 304 | *

305 | * Another issue is that createTempFile only guarantees to use the first three 306 | * characters of the prefix, so I could delete a similarly-named temporary 307 | * shared library if I haven't loaded it yet. 308 | */ 309 | void deleteLeftoverFiles() { 310 | final File tmpDirectory = new File(System.getProperty(JAVA_TMPDIR, ALTR_TMPDIR)); 311 | final File[] folders = tmpDirectory.listFiles(new FilenameFilter() { 312 | 313 | @Override 314 | public boolean accept(final File dir, final String name) { 315 | return name.startsWith(TMP_PREFIX); 316 | } 317 | }); 318 | if (folders == null) return; 319 | long leftoverMinAge = getLeftoverMinAge(); 320 | for (final File folder : folders) { 321 | // attempt to delete 322 | long age = System.currentTimeMillis() - folder.lastModified(); 323 | if (age < leftoverMinAge) { 324 | debug("Not deleting leftover folder " + folder + ": is " + age + "ms old"); 325 | continue; 326 | } 327 | debug("Deleting leftover folder: " + folder); 328 | deleteRecursively(folder); 329 | } 330 | } 331 | 332 | long getLeftoverMinAge() { 333 | try { 334 | return Long.parseLong(System.getProperty(LEFTOVER_MIN_AGE, String.valueOf(LEFTOVER_MIN_AGE_DEFAULT))); 335 | } catch (NumberFormatException e) { 336 | error("Cannot load leftover minimal age system property", e); 337 | return LEFTOVER_MIN_AGE_DEFAULT; 338 | } 339 | } 340 | /** 341 | * copy an InputStream to an OutputStream. 342 | * 343 | * @param in InputStream to copy from 344 | * @param out OutputStream to copy to 345 | * @throws IOException if there's an error 346 | */ 347 | static void copy(final InputStream in, final OutputStream out) 348 | throws IOException 349 | { 350 | final byte[] tmp = new byte[8192]; 351 | int len = 0; 352 | while (true) { 353 | len = in.read(tmp); 354 | if (len <= 0) { 355 | break; 356 | } 357 | out.write(tmp, 0, len); 358 | } 359 | } 360 | 361 | private static void debug(final String message) { 362 | LOGGER.debug(message); 363 | } 364 | 365 | private static void error(final String message, final Throwable t) { 366 | LOGGER.error(message, t); 367 | } 368 | } 369 | -------------------------------------------------------------------------------- /src/main/java/org/scijava/nativelib/NativeLibraryUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * Native library loader for extracting and loading native libraries from Java. 4 | * %% 5 | * Copyright (C) 2010 - 2023 Board of Regents of the University of 6 | * Wisconsin-Madison and Glencoe Software, Inc. 7 | * %% 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright notice, 12 | * this list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE 21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | * POSSIBILITY OF SUCH DAMAGE. 28 | * #L% 29 | */ 30 | 31 | package org.scijava.nativelib; 32 | 33 | import java.io.File; 34 | import java.io.IOException; 35 | import java.util.Arrays; 36 | import java.util.LinkedList; 37 | import java.util.List; 38 | import java.util.Locale; 39 | 40 | import org.slf4j.Logger; 41 | import org.slf4j.LoggerFactory; 42 | 43 | /** 44 | * This class is a utility for loading native libraries. 45 | *

46 | * Native libraries should be packaged into a single jar file, with the 47 | * following directory and file structure: 48 | * 49 | *

 50 |  * natives
 51 |  *   linux_32
 52 |  *     libxxx[-vvv].so
 53 |  *   linux_64
 54 |  *     libxxx[-vvv].so
 55 |  *   linux_arm
 56 |  *     libxxx[-vvv].so
 57 |  *   linux_arm64
 58 |  *     libxxx[-vvv].so
 59 |  *   linux_riscv32
 60 |  *     libxxx[-vvv].so
 61 |  *   linux_riscv64
 62 |  *     libxxx[-vvv].so
 63 |  *   osx_32
 64 |  *     libxxx[-vvv].dylib
 65 |  *   osx_64
 66 |  *     libxxx[-vvv].dylib
 67 |  *   windows_32
 68 |  *     xxx[-vvv].dll
 69 |  *   windows_64
 70 |  *     xxx[-vvv].dll
 71 |  *   aix_32
 72 |  *     libxxx[-vvv].so
 73 |  *     libxxx[-vvv].a
 74 |  *   aix_64
 75 |  *     libxxx[-vvv].so
 76 |  *     libxxx[-vvv].a
 77 |  * 
78 | *

79 | * Here "xxx" is the name of the native library and "-vvv" is an optional 80 | * version number. 81 | *

82 | * Current approach is to unpack the native library into a temporary file and 83 | * load from there. 84 | * 85 | * @author Aivar Grislis 86 | */ 87 | public class NativeLibraryUtil { 88 | 89 | public static enum Architecture { 90 | UNKNOWN, LINUX_32, LINUX_64, LINUX_ARM, LINUX_ARM64, LINUX_RISCV32, LINUX_RISCV64, 91 | WINDOWS_32, WINDOWS_64, WINDOWS_ARM64, 92 | OSX_32, OSX_64, OSX_PPC, OSX_ARM64, AIX_32, AIX_64 93 | } 94 | 95 | private static enum Processor { 96 | UNKNOWN, INTEL_32, INTEL_64, PPC, PPC_64, ARM, AARCH_64, RISCV_32, RISCV_64 97 | } 98 | 99 | public static final String DELIM = "/"; 100 | public static final String DEFAULT_SEARCH_PATH = "natives" + DELIM; 101 | 102 | private static Architecture architecture = Architecture.UNKNOWN; 103 | private static String archStr = null; 104 | private static final Logger LOGGER = LoggerFactory.getLogger( 105 | "org.scijava.nativelib.NativeLibraryUtil"); 106 | 107 | /** 108 | * Determines the underlying hardware platform and architecture. 109 | * 110 | * @return enumerated architecture value 111 | */ 112 | public static Architecture getArchitecture() { 113 | if (Architecture.UNKNOWN == architecture) { 114 | final Processor processor = getProcessor(); 115 | if (Processor.UNKNOWN != processor) { 116 | final String name = System.getProperty("os.name").toLowerCase(Locale.ENGLISH); 117 | if (name.contains("nix") || name.contains("nux")) { 118 | if (Processor.INTEL_32 == processor) { 119 | architecture = Architecture.LINUX_32; 120 | } 121 | else if (Processor.INTEL_64 == processor) { 122 | architecture = Architecture.LINUX_64; 123 | } 124 | else if (Processor.ARM == processor) { 125 | architecture = Architecture.LINUX_ARM; 126 | } 127 | else if (Processor.AARCH_64 == processor) { 128 | architecture = Architecture.LINUX_ARM64; 129 | } 130 | else if (Processor.RISCV_32 == processor) { 131 | architecture = Architecture.LINUX_RISCV32; 132 | } 133 | else if (Processor.RISCV_64 == processor) { 134 | architecture = Architecture.LINUX_RISCV64; 135 | } 136 | } 137 | else if (name.contains("aix")) { 138 | if (Processor.PPC == processor) { 139 | architecture = Architecture.AIX_32; 140 | } 141 | else if (Processor.PPC_64 == processor) { 142 | architecture = Architecture.AIX_64; 143 | } 144 | } 145 | else if (name.contains("win")) { 146 | if (Processor.INTEL_32 == processor) { 147 | architecture = Architecture.WINDOWS_32; 148 | } 149 | else if (Processor.INTEL_64 == processor) { 150 | architecture = Architecture.WINDOWS_64; 151 | } 152 | else if (Processor.AARCH_64 == processor) { 153 | architecture = Architecture.WINDOWS_ARM64; 154 | } 155 | } 156 | else if (name.contains("mac")) { 157 | if (Processor.INTEL_32 == processor) { 158 | architecture = Architecture.OSX_32; 159 | } 160 | else if (Processor.INTEL_64 == processor) { 161 | architecture = Architecture.OSX_64; 162 | } 163 | else if (Processor.AARCH_64 == processor) { 164 | architecture = Architecture.OSX_ARM64; 165 | } 166 | else if (Processor.PPC == processor) { 167 | architecture = Architecture.OSX_PPC; 168 | } 169 | } 170 | } 171 | } 172 | LOGGER.debug("architecture is " + architecture + " os.name is " + 173 | System.getProperty("os.name").toLowerCase(Locale.ENGLISH)); 174 | return architecture; 175 | } 176 | 177 | /** 178 | * Determines what processor is in use. 179 | * 180 | * @return The processor in use. 181 | */ 182 | private static Processor getProcessor() { 183 | Processor processor = Processor.UNKNOWN; 184 | int bits; 185 | 186 | // Note that this is actually the architecture of the installed JVM. 187 | final String arch = System.getProperty("os.arch").toLowerCase(Locale.ENGLISH); 188 | 189 | if (arch.contains("arm")) { 190 | processor = Processor.ARM; 191 | } 192 | else if (arch.contains("aarch64")) { 193 | processor = Processor.AARCH_64; 194 | } 195 | else if (arch.contains("ppc")) { 196 | bits = 32; 197 | if (arch.contains("64")) { 198 | bits = 64; 199 | } 200 | processor = (32 == bits) ? Processor.PPC : Processor.PPC_64; 201 | } 202 | else if (arch.contains("86") || arch.contains("amd")) { 203 | bits = 32; 204 | if (arch.contains("64")) { 205 | bits = 64; 206 | } 207 | processor = (32 == bits) ? Processor.INTEL_32 : Processor.INTEL_64; 208 | } 209 | else if (arch.contains("riscv")) { 210 | bits = 32; 211 | if (arch.contains("64")) { 212 | bits = 64; 213 | } 214 | processor = (32 == bits) ? Processor.RISCV_32 : Processor.RISCV_64; 215 | } 216 | LOGGER.debug("processor is " + processor + " os.arch is " + 217 | System.getProperty("os.arch").toLowerCase(Locale.ENGLISH)); 218 | return processor; 219 | } 220 | 221 | /** 222 | * Returns the path to the native library. 223 | * 224 | * @param searchPath the path to search for <platform> directory. 225 | * Pass in null to get default path 226 | * (natives/<platform>). 227 | * 228 | * @return path 229 | */ 230 | public static String getPlatformLibraryPath(String searchPath) { 231 | if (archStr == null) 232 | archStr = NativeLibraryUtil.getArchitecture().name().toLowerCase(Locale.ENGLISH); 233 | 234 | // foolproof 235 | String fullSearchPath = (searchPath.equals("") || searchPath.endsWith(DELIM) ? 236 | searchPath : searchPath + DELIM) + archStr + DELIM; 237 | LOGGER.debug("platform specific path is " + fullSearchPath); 238 | return fullSearchPath; 239 | } 240 | 241 | /** 242 | * Returns the full file name (without path) of the native library. 243 | * 244 | * @param libName name of library 245 | * @return file name 246 | */ 247 | public static String getPlatformLibraryName(final String libName) { 248 | String name = null; 249 | switch (getArchitecture()) { 250 | case AIX_32: 251 | case AIX_64: 252 | case LINUX_32: 253 | case LINUX_64: 254 | case LINUX_ARM: 255 | case LINUX_ARM64: 256 | case LINUX_RISCV32: 257 | case LINUX_RISCV64: 258 | name = "lib" + libName + ".so"; 259 | break; 260 | case WINDOWS_32: 261 | case WINDOWS_64: 262 | case WINDOWS_ARM64: 263 | name = libName + ".dll"; 264 | break; 265 | case OSX_32: 266 | case OSX_64: 267 | case OSX_ARM64: 268 | name = "lib" + libName + ".dylib"; 269 | break; 270 | default: 271 | break; 272 | } 273 | LOGGER.debug("native library name " + name); 274 | return name; 275 | } 276 | 277 | /** 278 | * Returns the Maven-versioned file name of the native library. In order for 279 | * this to work Maven needs to save its version number in the jar manifest. 280 | * The version of the library-containing jar and the version encoded in the 281 | * native library names should agree. 282 | * 283 | *

284 | 	 * {@code
285 | 	 * 
286 | 	 *   
287 | 	 *     
288 | 	 *       maven-jar-plugin
289 | 	 *         true *
290 | 	 *         
291 | 	 *            
292 | 	 *              
293 | 	 *                com.example.package
294 | 	 *                true *
295 | 	 *              
296 | 	 *           
297 | 	 *         
298 | 	 *     
299 | 	 *   
300 | 	 * 
301 | 	 *
302 | 	 * * = necessary to save version information in manifest
303 | 	 * }
304 | 	 * 
305 | * 306 | * @param libraryJarClass any class within the library-containing jar 307 | * @param libName name of library 308 | * @return The Maven-versioned file name of the native library. 309 | */ 310 | public static String getVersionedLibraryName(final Class libraryJarClass, 311 | String libName) 312 | { 313 | final String version = 314 | libraryJarClass.getPackage().getImplementationVersion(); 315 | if (null != version && version.length() > 0) { 316 | libName += "-" + version; 317 | } 318 | return libName; 319 | } 320 | 321 | /** 322 | * Loads the native library. Picks up the version number to specify from the 323 | * library-containing jar. 324 | * 325 | * @param libraryJarClass any class within the library-containing jar 326 | * @param libName name of library 327 | * @return whether or not successful 328 | */ 329 | public static boolean loadVersionedNativeLibrary( 330 | final Class libraryJarClass, String libName) 331 | { 332 | // append version information to native library name 333 | libName = getVersionedLibraryName(libraryJarClass, libName); 334 | 335 | return loadNativeLibrary(libraryJarClass, libName); 336 | } 337 | 338 | /** 339 | * Loads the native library. 340 | * 341 | * @param jniExtractor the extractor to use 342 | * @param libName name of library 343 | * @param searchPaths a list of additional paths to search for the library 344 | * @return whether or not successful 345 | */ 346 | public static boolean loadNativeLibrary(final JniExtractor jniExtractor, 347 | final String libName, final String... searchPaths) 348 | { 349 | if (Architecture.UNKNOWN == getArchitecture()) { 350 | LOGGER.warn("No native library available for this platform."); 351 | } 352 | else { 353 | try { 354 | final List libPaths = searchPaths == null ? 355 | new LinkedList() : 356 | new LinkedList(Arrays.asList(searchPaths)); 357 | libPaths.add(0, NativeLibraryUtil.DEFAULT_SEARCH_PATH); 358 | // for backward compatibility 359 | libPaths.add(1, ""); 360 | libPaths.add(2, "META-INF" + NativeLibraryUtil.DELIM + "lib"); 361 | // NB: Although the documented behavior of this method is to load 362 | // native library from META-INF/lib/, what it actually does is 363 | // to load from the root dir. See: https://github.com/scijava/ 364 | // native-lib-loader/blob/6c303443cf81bf913b1732d42c74544f61aef5d1/ 365 | // src/main/java/org/scijava/nativelib/NativeLoader.java#L126 366 | 367 | // search in each path in {natives/, /, META-INF/lib/, ...} 368 | for (String libPath : libPaths) { 369 | File extracted = jniExtractor.extractJni( 370 | NativeLibraryUtil.getPlatformLibraryPath(libPath), 371 | libName); 372 | if (extracted != null) { 373 | System.load(extracted.getAbsolutePath()); 374 | return true; 375 | } 376 | } 377 | } 378 | catch (final UnsatisfiedLinkError e) { 379 | LOGGER.debug("Problem with library", e); 380 | } catch (IOException e) { 381 | LOGGER.debug("Problem with extracting the library", e); 382 | } 383 | } 384 | return false; 385 | } 386 | 387 | /** 388 | * Loads the native library. 389 | * 390 | * @param libraryJarClass any class within the library-containing jar 391 | * @param libName name of library 392 | * @return whether or not successful 393 | */ 394 | @Deprecated 395 | public static boolean loadNativeLibrary(final Class libraryJarClass, 396 | final String libName) 397 | { 398 | try { 399 | return NativeLibraryUtil.loadNativeLibrary(new DefaultJniExtractor(libraryJarClass), libName); 400 | } 401 | catch (final IOException e) { 402 | LOGGER.debug("IOException creating DefaultJniExtractor", e); 403 | } 404 | return false; 405 | } 406 | } 407 | --------------------------------------------------------------------------------