• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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