diff --git a/build.gradle b/build.gradle index b5e0c788..83b0ba4e 100644 --- a/build.gradle +++ b/build.gradle @@ -124,14 +124,6 @@ compileJava { } } -processResources { - doLast { - delete fileTree("$buildDir/resources/main/native").matching { - exclude "${osName}/${osArch}/**" - } - } -} - test { useJUnitPlatform() jvmArgs = ["--add-opens=java.base/java.io=ALL-UNNAMED", "--enable-native-access=ALL-UNNAMED"] @@ -188,7 +180,34 @@ jlink { uses 'org.eclipse.jetty.http.HttpFieldPreEncoder' } - options = ['--strip-native-commands', '--strip-java-debug-attributes', '--compress', 'zip-6', '--no-header-files', '--no-man-pages', '--ignore-signing-information', '--exclude-files', '**.png', '--exclude-resources', 'glob:/com.sparrowwallet.merged.module/META-INF/*'] + options = ['--strip-native-commands', '--strip-java-debug-attributes', '--compress', 'zip-6', + '--no-header-files', '--no-man-pages', '--ignore-signing-information', + '--exclude-files', '**.png', + '--exclude-resources', + 'glob:/com.sparrowwallet.merged.module/META-INF/*,' + + 'glob:/javafx.graphics/*.dylib,' + + 'glob:/javafx.graphics/*.so,' + + 'glob:/javafx.graphics/*.dll,' + + 'glob:/com.sparrowwallet.drongo/native/**,' + + 'glob:/com.sparrowwallet.sparrow/native/**,' + + 'glob:/com.sparrowwallet.merged.module/com/sun/jna/**/*.so,' + + 'glob:/com.sparrowwallet.merged.module/com/sun/jna/**/*.dylib,' + + 'glob:/com.sparrowwallet.merged.module/com/sun/jna/**/*.jnilib,' + + 'glob:/com.sparrowwallet.merged.module/com/sun/jna/**/*.dll,' + + 'glob:/com.sparrowwallet.merged.module/com/sun/jna/**/*.a,' + + 'glob:/com.sparrowwallet.merged.module/darwin-*/**,' + + 'glob:/com.sparrowwallet.merged.module/linux-*/**,' + + 'glob:/com.sparrowwallet.merged.module/win32-*/**,' + + 'glob:/org.usb4java/org/usb4java/darwin-*/**,' + + 'glob:/org.usb4java/org/usb4java/linux-*/**,' + + 'glob:/org.usb4java/org/usb4java/win32-*/**,' + + 'glob:/org.hid4java/darwin-*/**,' + + 'glob:/org.hid4java/linux-*/**,' + + 'glob:/org.hid4java/win32-*/**,' + + 'glob:/openpnp.capture.java/darwin-*/**,' + + 'glob:/openpnp.capture.java/linux-*/**,' + + 'glob:/openpnp.capture.java/win32-*/**,' + + 'glob:/io.github.doblon8.jzbar/native/**'] launcher { name = 'sparrow' jvmArgs = ["--enable-native-access=com.sparrowwallet.drongo", @@ -277,13 +296,13 @@ jlink { } if(os.linux) { - tasks.jlink.finalizedBy('addUserWritePermission', 'copyUdevRules') + tasks.jlink.finalizedBy('addUserWritePermission', 'copyUdevRules', 'extractNativeLibraries') tasks.jpackageImage.finalizedBy('prepareResourceDir') if(!headless) { tasks.jpackage.dependsOn('copyMimeInfo') } } else { - tasks.jlink.finalizedBy('addUserWritePermission') + tasks.jlink.finalizedBy('addUserWritePermission', 'extractNativeLibraries') } tasks.register('addUserWritePermission', Exec) { @@ -362,6 +381,74 @@ tasks.register('packageTarDistribution', Tar) { } } +def jnaPlatform +if(os.macOsX) { + jnaPlatform = "darwin-${osArch == 'aarch64' ? 'aarch64' : 'x86-64'}" +} else if(os.windows) { + jnaPlatform = "win32-x86-64" +} else { + jnaPlatform = "linux-${osArch == 'aarch64' ? 'aarch64' : 'x86-64'}" +} + +def serialOs = os.macOsX ? "OSX" : (os.windows ? "Windows" : "Linux") +def serialArch = osArch == "aarch64" ? "aarch64" : "x86_64" + +// Map of JAR name prefix to the include glob for platform-specific natives inside the JAR. +def nativeLibJars = [ + 'jna-' : "com/sun/jna/${jnaPlatform}/*", + 'argon2-jvm-2' : "${jnaPlatform}/*", + 'hid4java-' : "${jnaPlatform}/*", + 'openpnp-capture-java': "${jnaPlatform}/*", + 'jSerialComm-' : "${serialOs}/${serialArch}/*", + 'usb4java-' : "org/usb4java/${jnaPlatform}/*", + 'jzbar-' : "native/${osName}/${osArch}/*", +] + +tasks.register('extractNativeLibraries') { + dependsOn 'jlink' + doLast { + def imageLib = file("$buildDir/image/lib") + + // Project-owned natives + copy { + from "${project(':drongo').projectDir}/src/main/resources/native/${osName}/${osArch}", "src/main/resources/native/${osName}/${osArch}" + into imageLib + eachFile { it.permissions { unix('rw-r--r--') } } + } + + // JavaFX natives + def javafxClassifier = "" + if(os.macOsX) { + javafxClassifier = osArch == "aarch64" ? "mac-aarch64" : "mac" + } else if(os.windows) { + javafxClassifier = "win" + } else { + javafxClassifier = osArch == "aarch64" ? "linux-aarch64" : "linux" + } + def javafxJar = configurations.runtimeClasspath.find { it.name == "javafx-graphics-${javafx.version}-${javafxClassifier}.jar" } + if(javafxJar) { + copy { + from(zipTree(javafxJar)) { include "*.dylib", "*.so", "*.dll" } + into imageLib + eachFile { it.permissions { unix('rw-r--r--') } } + } + } + + // Third-party natives + nativeLibJars.each { prefix, includePattern -> + def jar = configurations.runtimeClasspath.find { it.name.startsWith(prefix) } + if(jar) { + copy { + from(zipTree(jar)) { include includePattern } + into imageLib + eachFile { it.path = it.name; it.permissions { unix('rw-r--r--') } } + includeEmptyDirs = false + } + } + } + } +} + extraJavaModuleInfo { module('no.tornado:tornadofx-controls', 'tornadofx.controls') { exports('tornadofx.control') diff --git a/drongo b/drongo index f5a52f9e..dad9fe2f 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit f5a52f9eae07853c05c554d34099466dfb41bd0e +Subproject commit dad9fe2fccda624566e44324d0da42a3423f8e6e diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index 191ce5b3..345467da 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -88,7 +88,6 @@ public class AppController implements Initializable { public static final String LOADING_TRANSACTIONS_MESSAGE = "Loading wallet, select Transactions tab to view..."; public static final String CONNECTION_FAILED_PREFIX = "Connection failed: "; public static final String TRYING_ANOTHER_SERVER_MESSAGE = "trying another server..."; - public static final String JPACKAGE_APP_PATH = "jpackage.app-path"; @FXML private VBox rootBox; @@ -420,7 +419,7 @@ public class AppController implements Initializable { networkItem.setOnAction(event -> restart(event, network)); restart.getItems().add(networkItem); } - restart.setVisible(System.getProperty(JPACKAGE_APP_PATH) != null); + restart.setVisible(System.getProperty(SparrowWallet.JPACKAGE_APP_PATH) != null); saveTransaction.setDisable(true); showTransaction.visibleProperty().bind(Bindings.and(saveTransaction.visibleProperty(), saveTransaction.disableProperty().not())); @@ -597,7 +596,7 @@ public class AppController implements Initializable { sudo groupadd -f -r plugdev sudo usermod -aG plugdev `whoami` """; - String home = System.getProperty(JPACKAGE_APP_PATH); + String home = System.getProperty(SparrowWallet.JPACKAGE_APP_PATH); if(home != null && !home.startsWith("/opt/sparrowwallet") && home.endsWith("bin/Sparrow")) { home = home.replace("bin/Sparrow", ""); commands = commands.replace("/opt/sparrowwallet/", home); @@ -1045,8 +1044,8 @@ public class AppController implements Initializable { } public void restart(ActionEvent event, Network network) { - if(System.getProperty(JPACKAGE_APP_PATH) == null) { - throw new IllegalStateException("Property " + JPACKAGE_APP_PATH + " is not present"); + if(System.getProperty(SparrowWallet.JPACKAGE_APP_PATH) == null) { + throw new IllegalStateException("Property " + SparrowWallet.JPACKAGE_APP_PATH + " is not present"); } Args args = getRestartArgs(); @@ -1067,7 +1066,7 @@ public class AppController implements Initializable { private void restart(ActionEvent event, Args args) { try { List cmd = new ArrayList<>(); - cmd.add(System.getProperty(JPACKAGE_APP_PATH)); + cmd.add(System.getProperty(SparrowWallet.JPACKAGE_APP_PATH)); cmd.addAll(args.toParams()); final ProcessBuilder builder = new ProcessBuilder(cmd); if(OsType.getCurrent() == OsType.UNIX) { diff --git a/src/main/java/com/sparrowwallet/sparrow/SparrowWallet.java b/src/main/java/com/sparrowwallet/sparrow/SparrowWallet.java index d3af37d5..94bebaaf 100644 --- a/src/main/java/com/sparrowwallet/sparrow/SparrowWallet.java +++ b/src/main/java/com/sparrowwallet/sparrow/SparrowWallet.java @@ -22,10 +22,20 @@ public class SparrowWallet { public static final String APP_VERSION_SUFFIX = ""; public static final String APP_HOME_PROPERTY = "sparrow.home"; public static final String NETWORK_ENV_PROPERTY = "SPARROW_NETWORK"; + public static final String JPACKAGE_APP_PATH = "jpackage.app-path"; private static Instance instance; public static void main(String[] argv) { + if(System.getProperty(JPACKAGE_APP_PATH) != null) { + String libDir = System.getProperty("java.home") + File.separator + "lib"; + System.setProperty("jna.boot.library.path", libDir); + System.setProperty("jna.library.path", libDir); + System.setProperty("jSerialComm.library.path", libDir); + System.setProperty("org.usb4java.LibraryName", "usb4java"); + System.setProperty("java.library.path", libDir); + } + Args args = new Args(); JCommander jCommander = JCommander.newBuilder().addObject(args).programName(APP_NAME.toLowerCase(Locale.ROOT)).acceptUnknownOptions(true).build(); jCommander.parse(argv); diff --git a/src/main/java/com/sparrowwallet/sparrow/net/Bwt.java b/src/main/java/com/sparrowwallet/sparrow/net/Bwt.java index 82b8e93e..62863c9c 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/Bwt.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/Bwt.java @@ -23,6 +23,7 @@ import javafx.concurrent.Task; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; import java.io.IOException; import java.net.InetAddress; import java.time.Duration; @@ -42,22 +43,47 @@ public class Bwt { public synchronized static void initialize() { if(!initialized) { + OsType osType = OsType.getCurrent(); + String osArch = System.getProperty("os.arch"); + String libName; + if(osType == OsType.MACOS) { + libName = "libbwt_jni.dylib"; + } else if(osType == OsType.WINDOWS) { + libName = "bwt_jni.dll"; + } else { + libName = "libbwt_jni.so"; + } + + // Try loading from the application image lib/ directory + String javaHome = System.getProperty("java.home"); + if(javaHome != null) { + File libFile = new File(javaHome, "lib" + java.io.File.separator + libName); + if(libFile.exists()) { + try { + System.load(libFile.getAbsolutePath()); + initialized = true; + return; + } catch(UnsatisfiedLinkError e) { + log.debug("Could not load bwt from java.home, falling back to JAR extraction", e); + } + } + } + + // Fallback: extract from JAR try { - OsType osType = OsType.getCurrent(); - String osArch = System.getProperty("os.arch"); if(osType == OsType.MACOS && osArch.equals("aarch64")) { - NativeUtils.loadLibraryFromJar("/native/osx/aarch64/libbwt_jni.dylib"); + NativeUtils.loadLibraryFromJar("/native/osx/aarch64/" + libName); } else if(osType == OsType.MACOS) { - NativeUtils.loadLibraryFromJar("/native/osx/x64/libbwt_jni.dylib"); + NativeUtils.loadLibraryFromJar("/native/osx/x64/" + libName); } else if(osType == OsType.WINDOWS) { - NativeUtils.loadLibraryFromJar("/native/windows/x64/bwt_jni.dll"); + NativeUtils.loadLibraryFromJar("/native/windows/x64/" + libName); } else if(osArch.equals("aarch64")) { - NativeUtils.loadLibraryFromJar("/native/linux/aarch64/libbwt_jni.so"); + NativeUtils.loadLibraryFromJar("/native/linux/aarch64/" + libName); } else { - NativeUtils.loadLibraryFromJar("/native/linux/x64/libbwt_jni.so"); + NativeUtils.loadLibraryFromJar("/native/linux/x64/" + libName); } initialized = true; - } catch(IOException e) { + } catch(UnsatisfiedLinkError | IOException e) { log.error("Error loading bwt library", e); } }