2020-12-26 14:12:05 -08:00
|
|
|
// Copyright (c) FIRST and other WPILib contributors.
|
|
|
|
|
// Open Source Software; you can modify and/or share it under the terms of
|
|
|
|
|
// the WPILib BSD license file in the root directory of this project.
|
2018-07-29 10:20:41 -07:00
|
|
|
|
2021-06-12 01:17:09 -07:00
|
|
|
package edu.wpi.first.util;
|
2018-07-29 10:20:41 -07:00
|
|
|
|
|
|
|
|
import java.io.File;
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.io.InputStream;
|
|
|
|
|
import java.io.OutputStream;
|
2018-07-29 16:47:22 -07:00
|
|
|
import java.nio.charset.StandardCharsets;
|
2018-07-29 10:20:41 -07:00
|
|
|
import java.nio.file.Files;
|
|
|
|
|
import java.nio.file.Paths;
|
|
|
|
|
import java.security.DigestInputStream;
|
|
|
|
|
import java.security.MessageDigest;
|
|
|
|
|
import java.security.NoSuchAlgorithmException;
|
|
|
|
|
import java.util.Locale;
|
|
|
|
|
import java.util.Scanner;
|
|
|
|
|
|
|
|
|
|
public final class RuntimeLoader<T> {
|
|
|
|
|
private static String defaultExtractionRoot;
|
|
|
|
|
|
2021-06-10 20:46:47 -07:00
|
|
|
/**
|
|
|
|
|
* Gets the default extration root location (~/.wpilib/nativecache).
|
|
|
|
|
*
|
|
|
|
|
* @return The default extraction root location.
|
|
|
|
|
*/
|
2018-07-29 10:20:41 -07:00
|
|
|
public static synchronized String getDefaultExtractionRoot() {
|
|
|
|
|
if (defaultExtractionRoot != null) {
|
|
|
|
|
return defaultExtractionRoot;
|
|
|
|
|
}
|
|
|
|
|
String home = System.getProperty("user.home");
|
|
|
|
|
defaultExtractionRoot = Paths.get(home, ".wpilib", "nativecache").toString();
|
|
|
|
|
return defaultExtractionRoot;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private final String m_libraryName;
|
|
|
|
|
private final Class<T> m_loadClass;
|
|
|
|
|
private final String m_extractionRoot;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates a new library loader.
|
|
|
|
|
*
|
2021-06-10 20:46:47 -07:00
|
|
|
* @param libraryName Name of library to load.
|
|
|
|
|
* @param extractionRoot Location from which to load the library.
|
|
|
|
|
* @param cls Class whose classpath the given library belongs.
|
2018-07-29 10:20:41 -07:00
|
|
|
*/
|
|
|
|
|
public RuntimeLoader(String libraryName, String extractionRoot, Class<T> cls) {
|
|
|
|
|
m_libraryName = libraryName;
|
|
|
|
|
m_loadClass = cls;
|
|
|
|
|
m_extractionRoot = extractionRoot;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-10 20:46:47 -07:00
|
|
|
/**
|
|
|
|
|
* Returns a load error message given the information in the provided UnsatisfiedLinkError.
|
|
|
|
|
*
|
|
|
|
|
* @param ule UnsatisfiedLinkError object.
|
|
|
|
|
* @return A load error message.
|
|
|
|
|
*/
|
2020-01-27 14:10:46 -08:00
|
|
|
private String getLoadErrorMessage(UnsatisfiedLinkError ule) {
|
|
|
|
|
StringBuilder msg = new StringBuilder(512);
|
2018-12-21 00:26:53 -08:00
|
|
|
msg.append(m_libraryName)
|
2020-12-29 22:45:16 -08:00
|
|
|
.append(
|
|
|
|
|
" could not be loaded from path or an embedded resource.\n"
|
2020-12-29 09:27:48 -08:00
|
|
|
+ "\tattempted to load for platform ")
|
|
|
|
|
.append(RuntimeDetector.getPlatformPath())
|
|
|
|
|
.append("\nLast Load Error: \n")
|
|
|
|
|
.append(ule.getMessage())
|
|
|
|
|
.append('\n');
|
2020-01-27 14:10:46 -08:00
|
|
|
if (RuntimeDetector.isWindows()) {
|
2020-12-29 22:45:16 -08:00
|
|
|
msg.append(
|
|
|
|
|
"A common cause of this error is missing the C++ runtime.\n"
|
|
|
|
|
+ "Download the latest at https://support.microsoft.com/en-us/help/2977003/the-latest-supported-visual-c-downloads\n");
|
2020-01-27 14:10:46 -08:00
|
|
|
}
|
2018-12-21 00:26:53 -08:00
|
|
|
return msg.toString();
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-10 20:46:47 -07:00
|
|
|
/**
|
|
|
|
|
* Loads a native library.
|
|
|
|
|
*
|
|
|
|
|
* @throws IOException if the library fails to load
|
|
|
|
|
*/
|
2018-07-29 10:20:41 -07:00
|
|
|
@SuppressWarnings("PMD.PreserveStackTrace")
|
|
|
|
|
public void loadLibrary() throws IOException {
|
|
|
|
|
try {
|
|
|
|
|
// First, try loading path
|
|
|
|
|
System.loadLibrary(m_libraryName);
|
|
|
|
|
} catch (UnsatisfiedLinkError ule) {
|
|
|
|
|
// Then load the hash from the resources
|
|
|
|
|
String hashName = RuntimeDetector.getHashLibraryResource(m_libraryName);
|
|
|
|
|
String resname = RuntimeDetector.getLibraryResource(m_libraryName);
|
|
|
|
|
try (InputStream hashIs = m_loadClass.getResourceAsStream(hashName)) {
|
|
|
|
|
if (hashIs == null) {
|
2020-01-27 14:10:46 -08:00
|
|
|
throw new IOException(getLoadErrorMessage(ule));
|
2018-07-29 10:20:41 -07:00
|
|
|
}
|
2018-07-29 16:47:22 -07:00
|
|
|
try (Scanner scanner = new Scanner(hashIs, StandardCharsets.UTF_8.name())) {
|
2018-07-29 10:20:41 -07:00
|
|
|
String hash = scanner.nextLine();
|
|
|
|
|
File jniLibrary = new File(m_extractionRoot, resname + "." + hash);
|
|
|
|
|
try {
|
|
|
|
|
// Try to load from an already extracted hash
|
|
|
|
|
System.load(jniLibrary.getAbsolutePath());
|
|
|
|
|
} catch (UnsatisfiedLinkError ule2) {
|
|
|
|
|
// If extraction failed, extract
|
|
|
|
|
try (InputStream resIs = m_loadClass.getResourceAsStream(resname)) {
|
|
|
|
|
if (resIs == null) {
|
2020-01-27 14:10:46 -08:00
|
|
|
throw new IOException(getLoadErrorMessage(ule));
|
2018-07-29 10:20:41 -07:00
|
|
|
}
|
|
|
|
|
jniLibrary.getParentFile().mkdirs();
|
|
|
|
|
try (OutputStream os = Files.newOutputStream(jniLibrary.toPath())) {
|
|
|
|
|
byte[] buffer = new byte[0xFFFF]; // 64K copy buffer
|
|
|
|
|
int readBytes;
|
|
|
|
|
while ((readBytes = resIs.read(buffer)) != -1) { // NOPMD
|
|
|
|
|
os.write(buffer, 0, readBytes);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
System.load(jniLibrary.getAbsolutePath());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-10 20:46:47 -07:00
|
|
|
/**
|
|
|
|
|
* Load a native library by directly hashing the file.
|
|
|
|
|
*
|
|
|
|
|
* @throws IOException if the library failed to load
|
|
|
|
|
*/
|
2021-06-09 07:01:00 -07:00
|
|
|
@SuppressWarnings({"PMD.PreserveStackTrace", "PMD.EmptyWhileStmt"})
|
2018-07-29 10:20:41 -07:00
|
|
|
public void loadLibraryHashed() throws IOException {
|
|
|
|
|
try {
|
|
|
|
|
// First, try loading path
|
|
|
|
|
System.loadLibrary(m_libraryName);
|
|
|
|
|
} catch (UnsatisfiedLinkError ule) {
|
|
|
|
|
// Then load the hash from the input file
|
|
|
|
|
String resname = RuntimeDetector.getLibraryResource(m_libraryName);
|
|
|
|
|
String hash = null;
|
|
|
|
|
try (InputStream is = m_loadClass.getResourceAsStream(resname)) {
|
|
|
|
|
if (is == null) {
|
2020-01-27 14:10:46 -08:00
|
|
|
throw new IOException(getLoadErrorMessage(ule));
|
2018-07-29 10:20:41 -07:00
|
|
|
}
|
|
|
|
|
MessageDigest md = null;
|
|
|
|
|
try {
|
|
|
|
|
md = MessageDigest.getInstance("MD5");
|
|
|
|
|
} catch (NoSuchAlgorithmException nsae) {
|
|
|
|
|
throw new RuntimeException("Weird Hash Algorithm?");
|
|
|
|
|
}
|
|
|
|
|
try (DigestInputStream dis = new DigestInputStream(is, md)) {
|
|
|
|
|
// Read the entire buffer once to hash
|
|
|
|
|
byte[] buffer = new byte[0xFFFF];
|
|
|
|
|
while (dis.read(buffer) > -1) {}
|
|
|
|
|
MessageDigest digest = dis.getMessageDigest();
|
|
|
|
|
byte[] digestOutput = digest.digest();
|
|
|
|
|
StringBuilder builder = new StringBuilder();
|
|
|
|
|
for (byte b : digestOutput) {
|
|
|
|
|
builder.append(String.format("%02X", b));
|
|
|
|
|
}
|
|
|
|
|
hash = builder.toString().toLowerCase(Locale.ENGLISH);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (hash == null) {
|
|
|
|
|
throw new IOException("Weird Hash?");
|
|
|
|
|
}
|
|
|
|
|
File jniLibrary = new File(m_extractionRoot, resname + "." + hash);
|
|
|
|
|
try {
|
|
|
|
|
// Try to load from an already extracted hash
|
|
|
|
|
System.load(jniLibrary.getAbsolutePath());
|
|
|
|
|
} catch (UnsatisfiedLinkError ule2) {
|
|
|
|
|
// If extraction failed, extract
|
|
|
|
|
try (InputStream resIs = m_loadClass.getResourceAsStream(resname)) {
|
|
|
|
|
if (resIs == null) {
|
2020-01-27 14:10:46 -08:00
|
|
|
throw new IOException(getLoadErrorMessage(ule));
|
2018-07-29 10:20:41 -07:00
|
|
|
}
|
|
|
|
|
jniLibrary.getParentFile().mkdirs();
|
|
|
|
|
try (OutputStream os = Files.newOutputStream(jniLibrary.toPath())) {
|
|
|
|
|
byte[] buffer = new byte[0xFFFF]; // 64K copy buffer
|
|
|
|
|
int readBytes;
|
|
|
|
|
while ((readBytes = resIs.read(buffer)) != -1) { // NOPMD
|
|
|
|
|
os.write(buffer, 0, readBytes);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
System.load(jniLibrary.getAbsolutePath());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|