1 /* Copyright 2017 The TensorFlow Authors. All Rights Reserved. 2 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 7 http://www.apache.org/licenses/LICENSE-2.0 8 9 Unless required by applicable law or agreed to in writing, software 10 distributed under the License is distributed on an "AS IS" BASIS, 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 See the License for the specific language governing permissions and 13 limitations under the License. 14 ==============================================================================*/ 15 16 package org.tensorflow; 17 18 import java.io.File; 19 import java.io.FileOutputStream; 20 import java.io.IOException; 21 import java.io.InputStream; 22 import java.util.Properties; 23 24 /** 25 * Helper class for loading the TensorFlow Java native library. 26 * 27 * <p>The Java TensorFlow bindings require a native (JNI) library. This library 28 * (libtensorflow_jni.so on Linux, libtensorflow_jni.dylib on OS X, tensorflow_jni.dll on Windows) 29 * can be made available to the JVM using the java.library.path System property (e.g., using 30 * -Djava.library.path command-line argument). However, doing so requires an additional step of 31 * configuration. 32 * 33 * <p>Alternatively, the native libraries can be packaed in a .jar, making them easily usable from 34 * build systems like Maven. However, in such cases, the native library has to be extracted from the 35 * .jar archive. 36 * 37 * <p>NativeLibrary.load() takes care of this. First looking for the library in java.library.path 38 * and failing that, it tries to find the OS and architecture specific version of the library in the 39 * set of ClassLoader resources (under org/tensorflow/native/OS-ARCH). The resources paths used for 40 * lookup must be consistent with any packaging (such as on Maven Central) of the TensorFlow Java 41 * native libraries. 42 */ 43 final class NativeLibrary { 44 private static final boolean DEBUG = 45 System.getProperty("org.tensorflow.NativeLibrary.DEBUG") != null; 46 private static final String JNI_LIBNAME = "tensorflow_jni"; 47 load()48 public static void load() { 49 if (isLoaded() || tryLoadLibrary()) { 50 // Either: 51 // (1) The native library has already been statically loaded, OR 52 // (2) The required native code has been statically linked (through a custom launcher), OR 53 // (3) The native code is part of another library (such as an application-level library) 54 // that has already been loaded. For example, tensorflow/tools/android/test and 55 // tensorflow/tools/android/inference_interface include the required native code in 56 // differently named libraries. 57 // 58 // Doesn't matter how, but it seems the native code is loaded, so nothing else to do. 59 return; 60 } 61 // Native code is not present, perhaps it has been packaged into the .jar file containing this. 62 // Extract the JNI library itself 63 final String jniLibName = System.mapLibraryName(JNI_LIBNAME); 64 final String jniResourceName = makeResourceName(jniLibName); 65 log("jniResourceName: " + jniResourceName); 66 final InputStream jniResource = 67 NativeLibrary.class.getClassLoader().getResourceAsStream(jniResourceName); 68 // Extract the JNI's dependency 69 final String frameworkLibName = 70 getVersionedLibraryName(System.mapLibraryName("tensorflow_framework")); 71 final String frameworkResourceName = makeResourceName(frameworkLibName); 72 log("frameworkResourceName: " + frameworkResourceName); 73 final InputStream frameworkResource = 74 NativeLibrary.class.getClassLoader().getResourceAsStream(frameworkResourceName); 75 // Do not complain if the framework resource wasn't found. This may just mean that we're 76 // building with --config=monolithic (in which case it's not needed and not included). 77 if (jniResource == null) { 78 throw new UnsatisfiedLinkError( 79 String.format( 80 "Cannot find TensorFlow native library for OS: %s, architecture: %s. See " 81 + "https://github.com/tensorflow/tensorflow/tree/master/tensorflow/java/README.md" 82 + " for possible solutions (such as building the library from source). Additional" 83 + " information on attempts to find the native library can be obtained by adding" 84 + " org.tensorflow.NativeLibrary.DEBUG=1 to the system properties of the JVM.", 85 os(), architecture())); 86 } 87 try { 88 // Create a temporary directory for the extracted resource and its dependencies. 89 final File tempPath = createTemporaryDirectory(); 90 // Deletions are in the reverse order of requests, so we need to request that the directory be 91 // deleted first, so that it is empty when the request is fulfilled. 92 tempPath.deleteOnExit(); 93 final String tempDirectory = tempPath.getCanonicalPath(); 94 if (frameworkResource != null) { 95 extractResource(frameworkResource, frameworkLibName, tempDirectory); 96 } else { 97 log( 98 frameworkResourceName 99 + " not found. This is fine assuming " 100 + jniResourceName 101 + " is not built to depend on it."); 102 } 103 System.load(extractResource(jniResource, jniLibName, tempDirectory)); 104 } catch (IOException e) { 105 throw new UnsatisfiedLinkError( 106 String.format( 107 "Unable to extract native library into a temporary file (%s)", e.toString())); 108 } 109 } 110 tryLoadLibrary()111 private static boolean tryLoadLibrary() { 112 try { 113 System.loadLibrary(JNI_LIBNAME); 114 return true; 115 } catch (UnsatisfiedLinkError e) { 116 log("tryLoadLibraryFailed: " + e.getMessage()); 117 return false; 118 } 119 } 120 isLoaded()121 private static boolean isLoaded() { 122 try { 123 TensorFlow.version(); 124 log("isLoaded: true"); 125 return true; 126 } catch (UnsatisfiedLinkError e) { 127 return false; 128 } 129 } 130 resourceExists(String baseName)131 private static boolean resourceExists(String baseName) { 132 return NativeLibrary.class.getClassLoader().getResource(makeResourceName(baseName)) != null; 133 } 134 getVersionedLibraryName(String libFilename)135 private static String getVersionedLibraryName(String libFilename) { 136 final String versionName = getMajorVersionNumber(); 137 138 // If we're on darwin, the versioned libraries look like blah.1.dylib. 139 final String darwinSuffix = ".dylib"; 140 if (libFilename.endsWith(darwinSuffix)) { 141 final String prefix = libFilename.substring(0, libFilename.length() - darwinSuffix.length()); 142 if (versionName != null) { 143 final String darwinVersionedLibrary = prefix + "." + versionName + darwinSuffix; 144 if (resourceExists(darwinVersionedLibrary)) { 145 return darwinVersionedLibrary; 146 } 147 } else { 148 // If we're here, we're on darwin, but we couldn't figure out the major version number. We 149 // already tried the library name without any changes, but let's do one final try for the 150 // library with a .so suffix. 151 final String darwinSoName = prefix + ".so"; 152 if (resourceExists(darwinSoName)) { 153 return darwinSoName; 154 } 155 } 156 } else if (libFilename.endsWith(".so")) { 157 // Libraries ending in ".so" are versioned like "libfoo.so.1", so try that. 158 final String versionedSoName = libFilename + "." + versionName; 159 if (versionName != null && resourceExists(versionedSoName)) { 160 return versionedSoName; 161 } 162 } 163 164 // Otherwise, we've got no idea. 165 return libFilename; 166 } 167 168 /** 169 * Returns the major version number of this TensorFlow Java API, or {@code null} if it cannot be 170 * determined. 171 */ getMajorVersionNumber()172 private static String getMajorVersionNumber() { 173 InputStream resourceStream = 174 NativeLibrary.class.getClassLoader().getResourceAsStream("tensorflow-version-info"); 175 if (resourceStream == null) { 176 return null; 177 } 178 179 try { 180 Properties props = new Properties(); 181 props.load(resourceStream); 182 String version = props.getProperty("version"); 183 // expecting a string like 1.14.0, we want to get the first '1'. 184 int dotIndex; 185 if (version == null || (dotIndex = version.indexOf('.')) == -1) { 186 return null; 187 } 188 String majorVersion = version.substring(0, dotIndex); 189 try { 190 Integer.parseInt(majorVersion); 191 return majorVersion; 192 } catch (NumberFormatException unused) { 193 return null; 194 } 195 } catch (IOException e) { 196 log("failed to load tensorflow version info."); 197 return null; 198 } 199 } 200 extractResource( InputStream resource, String resourceName, String extractToDirectory)201 private static String extractResource( 202 InputStream resource, String resourceName, String extractToDirectory) throws IOException { 203 final File dst = new File(extractToDirectory, resourceName); 204 dst.deleteOnExit(); 205 final String dstPath = dst.toString(); 206 log("extracting native library to: " + dstPath); 207 final long nbytes = copy(resource, dst); 208 log(String.format("copied %d bytes to %s", nbytes, dstPath)); 209 return dstPath; 210 } 211 os()212 private static String os() { 213 final String p = System.getProperty("os.name").toLowerCase(); 214 if (p.contains("linux")) { 215 return "linux"; 216 } else if (p.contains("os x") || p.contains("darwin")) { 217 return "darwin"; 218 } else if (p.contains("windows")) { 219 return "windows"; 220 } else { 221 return p.replaceAll("\\s", ""); 222 } 223 } 224 architecture()225 private static String architecture() { 226 final String arch = System.getProperty("os.arch").toLowerCase(); 227 return (arch.equals("amd64")) ? "x86_64" : arch; 228 } 229 log(String msg)230 private static void log(String msg) { 231 if (DEBUG) { 232 System.err.println("org.tensorflow.NativeLibrary: " + msg); 233 } 234 } 235 makeResourceName(String baseName)236 private static String makeResourceName(String baseName) { 237 return "org/tensorflow/native/" + String.format("%s-%s/", os(), architecture()) + baseName; 238 } 239 copy(InputStream src, File dstFile)240 private static long copy(InputStream src, File dstFile) throws IOException { 241 FileOutputStream dst = new FileOutputStream(dstFile); 242 try { 243 byte[] buffer = new byte[1 << 20]; // 1MB 244 long ret = 0; 245 int n = 0; 246 while ((n = src.read(buffer)) >= 0) { 247 dst.write(buffer, 0, n); 248 ret += n; 249 } 250 return ret; 251 } finally { 252 dst.close(); 253 src.close(); 254 } 255 } 256 257 // Shamelessly adapted from Guava to avoid using java.nio, for Android API 258 // compatibility. createTemporaryDirectory()259 private static File createTemporaryDirectory() { 260 File baseDirectory = new File(System.getProperty("java.io.tmpdir")); 261 String directoryName = "tensorflow_native_libraries-" + System.currentTimeMillis() + "-"; 262 for (int attempt = 0; attempt < 1000; attempt++) { 263 File temporaryDirectory = new File(baseDirectory, directoryName + attempt); 264 if (temporaryDirectory.mkdir()) { 265 return temporaryDirectory; 266 } 267 } 268 throw new IllegalStateException( 269 "Could not create a temporary directory (tried to make " 270 + directoryName 271 + "*) to extract TensorFlow native libraries."); 272 } 273 NativeLibrary()274 private NativeLibrary() {} 275 } 276