• 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 
23 /**
24  * Helper class for loading the TensorFlow Java native library.
25  *
26  * <p>The Java TensorFlow bindings require a native (JNI) library. This library
27  * (libtensorflow_jni.so on Linux, libtensorflow_jni.dylib on OS X, tensorflow_jni.dll on Windows)
28  * can be made available to the JVM using the java.library.path System property (e.g., using
29  * -Djava.library.path command-line argument). However, doing so requires an additional step of
30  * configuration.
31  *
32  * <p>Alternatively, the native libraries can be packaed in a .jar, making them easily usable from
33  * build systems like Maven. However, in such cases, the native library has to be extracted from the
34  * .jar archive.
35  *
36  * <p>NativeLibrary.load() takes care of this. First looking for the library in java.library.path
37  * and failing that, it tries to find the OS and architecture specific version of the library in the
38  * set of ClassLoader resources (under org/tensorflow/native/OS-ARCH). The resources paths used for
39  * lookup must be consistent with any packaging (such as on Maven Central) of the TensorFlow Java
40  * native libraries.
41  */
42 final class NativeLibrary {
43   private static final boolean DEBUG =
44       System.getProperty("org.tensorflow.NativeLibrary.DEBUG") != null;
45   private static final String JNI_LIBNAME = "tensorflow_jni";
46 
load()47   public static void load() {
48     if (isLoaded() || tryLoadLibrary()) {
49       // Either:
50       // (1) The native library has already been statically loaded, OR
51       // (2) The required native code has been statically linked (through a custom launcher), OR
52       // (3) The native code is part of another library (such as an application-level library)
53       // that has already been loaded. For example, tensorflow/examples/android and
54       // tensorflow/contrib/android include the required native code in differently named libraries.
55       //
56       // Doesn't matter how, but it seems the native code is loaded, so nothing else to do.
57       return;
58     }
59     // Native code is not present, perhaps it has been packaged into the .jar file containing this.
60     // Extract the JNI library itself
61     final String jniLibName = System.mapLibraryName(JNI_LIBNAME);
62     final String jniResourceName = makeResourceName(jniLibName);
63     log("jniResourceName: " + jniResourceName);
64     final InputStream jniResource =
65         NativeLibrary.class.getClassLoader().getResourceAsStream(jniResourceName);
66     // Extract the JNI's dependency
67     final String frameworkLibName =
68         maybeAdjustForMacOS(System.mapLibraryName("tensorflow_framework"));
69     final String frameworkResourceName = makeResourceName(frameworkLibName);
70     log("frameworkResourceName: " + frameworkResourceName);
71     final InputStream frameworkResource =
72         NativeLibrary.class.getClassLoader().getResourceAsStream(frameworkResourceName);
73     // Do not complain if the framework resource wasn't found. This may just mean that we're
74     // building with --config=monolithic (in which case it's not needed and not included).
75     if (jniResource == null) {
76       throw new UnsatisfiedLinkError(
77           String.format(
78               "Cannot find TensorFlow native library for OS: %s, architecture: %s. See "
79                   + "https://github.com/tensorflow/tensorflow/tree/master/tensorflow/java/README.md"
80                   + " for possible solutions (such as building the library from source). Additional"
81                   + " information on attempts to find the native library can be obtained by adding"
82                   + " org.tensorflow.NativeLibrary.DEBUG=1 to the system properties of the JVM.",
83               os(), architecture()));
84     }
85     try {
86       // Create a temporary directory for the extracted resource and its dependencies.
87       final File tempPath = createTemporaryDirectory();
88       // Deletions are in the reverse order of requests, so we need to request that the directory be
89       // deleted first, so that it is empty when the request is fulfilled.
90       tempPath.deleteOnExit();
91       final String tempDirectory = tempPath.getCanonicalPath();
92       if (frameworkResource != null) {
93         extractResource(frameworkResource, frameworkLibName, tempDirectory);
94       } else {
95         log(
96             frameworkResourceName
97                 + " not found. This is fine assuming "
98                 + jniResourceName
99                 + " is not built to depend on it.");
100       }
101       System.load(extractResource(jniResource, jniLibName, tempDirectory));
102     } catch (IOException e) {
103       throw new UnsatisfiedLinkError(
104           String.format(
105               "Unable to extract native library into a temporary file (%s)", e.toString()));
106     }
107   }
108 
tryLoadLibrary()109   private static boolean tryLoadLibrary() {
110     try {
111       System.loadLibrary(JNI_LIBNAME);
112       return true;
113     } catch (UnsatisfiedLinkError e) {
114       log("tryLoadLibraryFailed: " + e.getMessage());
115       return false;
116     }
117   }
118 
isLoaded()119   private static boolean isLoaded() {
120     try {
121       TensorFlow.version();
122       log("isLoaded: true");
123       return true;
124     } catch (UnsatisfiedLinkError e) {
125       return false;
126     }
127   }
128 
maybeAdjustForMacOS(String libFilename)129   private static String maybeAdjustForMacOS(String libFilename) {
130     if (!System.getProperty("os.name").contains("OS X")) {
131       return libFilename;
132     }
133     // This is macOS, and the TensorFlow release process might have setup dependencies on
134     // libtensorflow_framework.so instead of libtensorflow_framework.dylib. Adjust for that.
135     final ClassLoader cl = NativeLibrary.class.getClassLoader();
136     if (cl.getResource(makeResourceName(libFilename)) != null) {
137       return libFilename;
138     }
139     // liftensorflow_framework.dylib not found, try libtensorflow_framework.so
140     final String suffix = ".dylib";
141     if (!libFilename.endsWith(suffix)) {
142       return libFilename;
143     }
144     return libFilename.substring(0, libFilename.length() - suffix.length()) + ".so";
145   }
146 
extractResource( InputStream resource, String resourceName, String extractToDirectory)147   private static String extractResource(
148       InputStream resource, String resourceName, String extractToDirectory) throws IOException {
149     final File dst = new File(extractToDirectory, resourceName);
150     dst.deleteOnExit();
151     final String dstPath = dst.toString();
152     log("extracting native library to: " + dstPath);
153     final long nbytes = copy(resource, dst);
154     log(String.format("copied %d bytes to %s", nbytes, dstPath));
155     return dstPath;
156   }
157 
os()158   private static String os() {
159     final String p = System.getProperty("os.name").toLowerCase();
160     if (p.contains("linux")) {
161       return "linux";
162     } else if (p.contains("os x") || p.contains("darwin")) {
163       return "darwin";
164     } else if (p.contains("windows")) {
165       return "windows";
166     } else {
167       return p.replaceAll("\\s", "");
168     }
169   }
170 
architecture()171   private static String architecture() {
172     final String arch = System.getProperty("os.arch").toLowerCase();
173     return (arch.equals("amd64")) ? "x86_64" : arch;
174   }
175 
log(String msg)176   private static void log(String msg) {
177     if (DEBUG) {
178       System.err.println("org.tensorflow.NativeLibrary: " + msg);
179     }
180   }
181 
makeResourceName(String baseName)182   private static String makeResourceName(String baseName) {
183     return "org/tensorflow/native/" + String.format("%s-%s/", os(), architecture()) + baseName;
184   }
185 
copy(InputStream src, File dstFile)186   private static long copy(InputStream src, File dstFile) throws IOException {
187     FileOutputStream dst = new FileOutputStream(dstFile);
188     try {
189       byte[] buffer = new byte[1 << 20]; // 1MB
190       long ret = 0;
191       int n = 0;
192       while ((n = src.read(buffer)) >= 0) {
193         dst.write(buffer, 0, n);
194         ret += n;
195       }
196       return ret;
197     } finally {
198       dst.close();
199       src.close();
200     }
201   }
202 
203   // Shamelessly adapted from Guava to avoid using java.nio, for Android API
204   // compatibility.
createTemporaryDirectory()205   private static File createTemporaryDirectory() {
206     File baseDirectory = new File(System.getProperty("java.io.tmpdir"));
207     String directoryName = "tensorflow_native_libraries-" + System.currentTimeMillis() + "-";
208     for (int attempt = 0; attempt < 1000; attempt++) {
209       File temporaryDirectory = new File(baseDirectory, directoryName + attempt);
210       if (temporaryDirectory.mkdir()) {
211         return temporaryDirectory;
212       }
213     }
214     throw new IllegalStateException(
215         "Could not create a temporary directory (tried to make "
216             + directoryName
217             + "*) to extract TensorFlow native libraries.");
218   }
219 
NativeLibrary()220   private NativeLibrary() {}
221 }
222