plugins { id 'application' id 'org.openjfx.javafxplugin' version '0.1.0' id 'org.beryx.jlink' version '4.0.2' id 'org.gradlex.extra-java-module-info' version '1.13.1' id 'io.matthewnelson.kmp.tor.resource-filterjar' version '408.21.0' } def os = org.gradle.internal.os.OperatingSystem.current() def osName = os.getFamilyName() if(os.macOsX) { osName = "osx" } def osArch = "x64" def releaseArch = "x86_64" if(System.getProperty("os.arch") == "aarch64") { osArch = "aarch64" releaseArch = "aarch64" } def headless = "true".equals(System.getProperty("java.awt.headless")) group = 'com.sparrowwallet' version = '2.5.4' repositories { mavenCentral() maven { url = uri('https://code.sparrowwallet.com/api/packages/sparrowwallet/maven') } } tasks.withType(AbstractArchiveTask).configureEach { useFileSystemPermissions() } javafx { version = "26" modules = [ 'javafx.controls', 'javafx.fxml', 'javafx.swing', 'javafx.graphics' ] } java { disableAutoTargetJvm() } dependencies { //Any changes to the dependencies must be reflected in the module definitions below! implementation(project(':drongo')) implementation(project(':lark')) implementation('com.google.guava:guava:33.5.0-jre') implementation('com.google.code.gson:gson:2.13.2') implementation('com.h2database:h2:2.1.214') implementation('com.zaxxer:HikariCP:7.0.2') { exclude group: 'org.slf4j' } implementation('org.jdbi:jdbi3-core:3.51.0') { exclude group: 'org.slf4j' } implementation('org.jdbi:jdbi3-sqlobject:3.51.0') { exclude group: 'org.slf4j' } implementation('org.flywaydb:flyway-core:9.22.3') implementation('org.fxmisc.richtext:richtextfx:0.11.7') implementation('no.tornado:tornadofx-controls:1.0.4') implementation('com.google.zxing:javase:3.5.4') { exclude group: 'com.beust', module: 'jcommander' } implementation('org.jcommander:jcommander:3.0') implementation('com.github.arteam:simple-json-rpc-core:1.3') implementation('com.github.arteam:simple-json-rpc-client:1.3') { exclude group: 'com.github.arteam', module: 'simple-json-rpc-core' } implementation('com.github.arteam:simple-json-rpc-server:1.3') { exclude group: 'org.slf4j' } implementation('com.fasterxml.jackson.core:jackson-databind:2.21.1') implementation('com.sparrowwallet:hummingbird:1.7.4') implementation('co.nstant.in:cbor:0.9') implementation('io.github.doblon8:openpnp-capture-java:0.0.3') implementation("io.matthewnelson.kmp-tor:runtime:2.5.0") implementation("io.matthewnelson.kmp-tor:resource-exec-tor-gpl:408.21.0") implementation('org.jetbrains.kotlinx:kotlinx-coroutines-javafx:1.10.2') { exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-common' } implementation('de.jangassen:nsmenufx:3.1.0') { exclude group: 'net.java.dev.jna', module: 'jna' } implementation('org.controlsfx:controlsfx:11.2.3' ) { exclude group: 'org.openjfx', module: 'javafx-base' exclude group: 'org.openjfx', module: 'javafx-graphics' exclude group: 'org.openjfx', module: 'javafx-controls' exclude group: 'org.openjfx', module: 'javafx-swing' exclude group: 'org.openjfx', module: 'javafx-fxml' exclude group: 'org.openjfx', module: 'javafx-web' exclude group: 'org.openjfx', module: 'javafx-media' } implementation('dev.bwt:bwt-jni:0.1.8') implementation('net.sourceforge.javacsv:javacsv:2.0') implementation ('org.slf4j:slf4j-api:2.0.17') implementation('org.slf4j:jul-to-slf4j:2.0.17') { exclude group: 'org.slf4j' } implementation('com.sparrowwallet.bokmakierie:bokmakierie:1.0') implementation('com.sparrowwallet:tern:1.0.6') implementation('io.reactivex.rxjava2:rxjava:2.2.21') implementation('io.reactivex.rxjava2:rxjavafx:2.2.2') implementation('org.apache.commons:commons-lang3:3.20.0') implementation('org.apache.commons:commons-compress:1.28.0') implementation('com.github.librepdf:openpdf:1.3.43') implementation('com.googlecode.lanterna:lanterna:3.1.5') implementation('net.coobird:thumbnailator:0.4.21') implementation('com.github.hervegirod:fxsvgimage:1.1') implementation('com.sparrowwallet:toucan:0.9.0') implementation('com.jcraft:jzlib:1.1.3') implementation('io.github.doblon8:jzbar:0.4.0') testImplementation('org.junit.jupiter:junit-jupiter-api:5.14.1') testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.14.1') testRuntimeOnly('org.junit.platform:junit-platform-launcher') } compileJava { options.with { fork = true compilerArgs.addAll(["--add-exports", "org.controlsfx.controls/impl.org.controlsfx.skin=com.sparrowwallet.sparrow", "--add-exports", "javafx.graphics/com.sun.glass.ui=com.sparrowwallet.sparrow", "--add-exports", "javafx.graphics/com.sun.javafx.application=com.sparrowwallet.sparrow"]) } } test { useJUnitPlatform() jvmArgs = ["--enable-native-access=ALL-UNNAMED"] } application { mainModule = 'com.sparrowwallet.sparrow' mainClass = 'com.sparrowwallet.sparrow.SparrowWallet' applicationDefaultJvmArgs = ["-XX:+HeapDumpOnOutOfMemoryError", "--enable-native-access=com.sparrowwallet.drongo", "--enable-native-access=com.sun.jna", "--enable-native-access=javafx.graphics", "--enable-native-access=com.fazecast.jSerialComm", "--enable-native-access=org.usb4java", "--enable-native-access=io.github.doblon8.jzbar", "--enable-native-access=io.github.doblon8.openpnp.capture", "--add-opens=javafx.graphics/com.sun.javafx.css=org.controlsfx.controls", "--add-opens=javafx.graphics/javafx.scene=org.controlsfx.controls", "--add-opens=javafx.controls/com.sun.javafx.scene.control.behavior=org.controlsfx.controls", "--add-opens=javafx.controls/com.sun.javafx.scene.control.inputmap=org.controlsfx.controls", "--add-opens=javafx.graphics/com.sun.javafx.scene.traversal=org.controlsfx.controls", "--add-opens=javafx.base/com.sun.javafx.event=org.controlsfx.controls", "--add-opens=javafx.controls/javafx.scene.control.cell=com.sparrowwallet.sparrow", "--add-opens=org.controlsfx.controls/impl.org.controlsfx.skin=com.sparrowwallet.sparrow", "--add-opens=org.controlsfx.controls/impl.org.controlsfx.skin=javafx.fxml", "--add-opens=javafx.graphics/com.sun.glass.ui=com.sparrowwallet.sparrow", "--add-opens=javafx.graphics/com.sun.javafx.application=com.sparrowwallet.sparrow", "--add-opens=javafx.graphics/javafx.scene.input=com.sparrowwallet.sparrow", "--add-opens=java.base/java.net=com.sparrowwallet.sparrow", "--add-opens=java.smartcardio/sun.security.smartcardio=com.sparrowwallet.sparrow", "--add-reads=kotlin.stdlib=kotlinx.coroutines.core", "--add-reads=org.flywaydb.core=java.desktop"] if(os.macOsX) { applicationDefaultJvmArgs += ["-Dprism.lcdtext=false", "-Xdock:name=Sparrow"] } if(headless) { applicationDefaultJvmArgs += ["-Dglass.platform=Headless"] } } run { //Add jdk.unsupported for IDE agents requiring sun.misc jvmArgs += ["--add-modules=jdk.unsupported"] } jlink { mergedModule { requires 'javafx.graphics' requires 'javafx.controls' requires 'java.xml' requires 'java.logging' requires 'javafx.base' requires 'jdk.crypto.cryptoki' requires 'java.management' requires 'io.leangen.geantyref' requires 'static jdk.jfr' 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/*,' + '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:/io.github.doblon8.jzbar/native/**,' + 'glob:/io.github.doblon8.openpnp.capture/native/**'] launcher { name = 'sparrow' jvmArgs = ["--enable-native-access=com.sparrowwallet.drongo", "--enable-native-access=com.sun.jna", "--enable-native-access=javafx.graphics", "--enable-native-access=com.sparrowwallet.merged.module", "--enable-native-access=com.fazecast.jSerialComm", "--enable-native-access=org.usb4java", "--enable-native-access=io.github.doblon8.jzbar", "--enable-native-access=io.github.doblon8.openpnp.capture", "--enable-native-access=com.sparrowwallet.sparrow", "--add-opens=javafx.graphics/com.sun.javafx.css=org.controlsfx.controls", "--add-opens=javafx.graphics/javafx.scene=org.controlsfx.controls", "--add-opens=javafx.controls/com.sun.javafx.scene.control.behavior=org.controlsfx.controls", "--add-opens=javafx.controls/com.sun.javafx.scene.control.inputmap=org.controlsfx.controls", "--add-opens=javafx.graphics/com.sun.javafx.scene.traversal=org.controlsfx.controls", "--add-opens=javafx.base/com.sun.javafx.event=org.controlsfx.controls", "--add-opens=javafx.controls/javafx.scene.control.cell=com.sparrowwallet.sparrow", "--add-opens=org.controlsfx.controls/impl.org.controlsfx.skin=com.sparrowwallet.sparrow", "--add-opens=org.controlsfx.controls/impl.org.controlsfx.skin=javafx.fxml", "--add-opens=javafx.graphics/com.sun.glass.ui=com.sparrowwallet.sparrow", "--add-opens=javafx.graphics/javafx.scene.input=com.sparrowwallet.sparrow", "--add-opens=javafx.graphics/com.sun.javafx.application=com.sparrowwallet.sparrow", "--add-opens=java.base/java.net=com.sparrowwallet.sparrow", "--add-opens=java.smartcardio/sun.security.smartcardio=com.sparrowwallet.sparrow", "--add-reads=com.sparrowwallet.merged.module=java.desktop", "--add-reads=com.sparrowwallet.merged.module=java.sql", "--add-reads=com.sparrowwallet.merged.module=com.sparrowwallet.sparrow", "--add-reads=com.sparrowwallet.merged.module=ch.qos.logback.classic", "--add-reads=com.sparrowwallet.merged.module=org.slf4j", "--add-reads=com.sparrowwallet.merged.module=com.fasterxml.jackson.databind", "--add-reads=com.sparrowwallet.merged.module=com.fasterxml.jackson.annotation", "--add-reads=com.sparrowwallet.merged.module=com.fasterxml.jackson.core", "--add-reads=com.sparrowwallet.merged.module=co.nstant.in.cbor", "--add-reads=com.sparrowwallet.merged.module=org.bouncycastle.pg", "--add-reads=com.sparrowwallet.merged.module=org.bouncycastle.provider", "--add-reads=com.sparrowwallet.merged.module=kotlin.stdlib", "--add-reads=com.sparrowwallet.merged.module=org.reactfx.reactfx", "--add-reads=kotlin.stdlib=kotlinx.coroutines.core", "--add-reads=org.flywaydb.core=java.desktop"] if(os.windows) { jvmArgs.addAll(["-Djavax.accessibility.assistive_technologies", "-Djavax.accessibility.screen_magnifier_present=false"]) } if(os.macOsX) { jvmArgs.addAll(["-Dprism.lcdtext=false", "--add-opens=javafx.graphics/com.sun.glass.ui.mac=com.sparrowwallet.merged.module"]) } if(headless) { jvmArgs.addAll(["-Dglass.platform=Headless"]) } } addExtraDependencies("javafx") jpackage { imageName = "Sparrow" installerName = "Sparrow" appVersion = "${version}" skipInstaller = os.macOsX || properties.skipInstallers imageOptions = [] installerOptions = ['--file-associations', 'src/main/deploy/psbt.properties', '--file-associations', 'src/main/deploy/txn.properties', '--file-associations', 'src/main/deploy/asc.properties', '--license-file', 'LICENSE'] if(os.windows) { installerOptions.addAll(['--win-per-user-install', '--win-menu', '--win-menu-group', 'Sparrow', '--win-shortcut', '--resource-dir', 'src/main/deploy/package/windows/']) imageOptions.addAll(['--icon', 'src/main/deploy/package/windows/sparrow.ico']) installerType = "msi" } if(os.linux) { if(headless) { installerName = "sparrowserver" installerOptions = ['--license-file', 'LICENSE'] } else { installerName = "sparrowwallet" installerOptions.addAll(['--linux-shortcut', '--linux-menu-group', 'Sparrow']) } installerOptions.addAll(['--resource-dir', layout.buildDirectory.dir('deploy/package').get().asFile.toString(), '--linux-app-category', 'utils', '--linux-app-release', '1', '--linux-rpm-license-type', 'ASL 2.0', '--linux-deb-maintainer', 'mail@sparrowwallet.com']) imageOptions.addAll(['--icon', 'src/main/deploy/package/linux/Sparrow.png', '--resource-dir', 'src/main/deploy/package/linux/']) } if(os.macOsX) { installerOptions.addAll(['--mac-sign', '--mac-signing-key-user-name', 'Craig Raw (UPLVMSK9D7)']) imageOptions.addAll(['--icon', 'src/main/deploy/package/macos/sparrow.icns', '--resource-dir', 'src/main/deploy/package/macos/']) installerType = "dmg" } } if(os.linux) { jpackageImage { dependsOn('prepareModulesDir', 'copyUdevRules') } } } if(os.linux) { tasks.jlink.finalizedBy('addUserWritePermission', 'copyUdevRules', 'extractNativeLibraries') tasks.jpackageImage.finalizedBy('prepareResourceDir') if(!headless) { tasks.jpackage.dependsOn('copyMimeInfo') } } else { tasks.jlink.finalizedBy('addUserWritePermission', 'extractNativeLibraries') } tasks.register('addUserWritePermission', Exec) { if(os.windows) { def usersGroup = '*S-1-5-32-545' // Windows "Users" group SID (language-independent) commandLine 'icacls', "$buildDir\\image\\legal", '/grant', "${usersGroup}:(OI)(CI)F", '/T' } else { commandLine 'chmod', '-R', 'u+w', "$buildDir/image/legal" } } tasks.register('copyUdevRules', Copy) { from('lark/src/main/resources/udev') into(layout.buildDirectory.dir('image/conf/udev')) include('*') } tasks.register('prepareResourceDir', Copy) { from("src/main/deploy/package/linux${headless ? '-headless' : ''}") into(layout.buildDirectory.dir('deploy/package')) include('*') eachFile { file -> if(file.name.equals('control') || file.name.endsWith('.spec')) { filter { line -> if(line.contains('${size}')) { line = line.replace('${size}', getDirectorySize(layout.buildDirectory.dir('jpackage/Sparrow').get().asFile)) } return line.replace('${version}', "${version}").replace('${arch}', osArch == 'aarch64' ? 'arm64' : 'amd64') } } } } tasks.register('copyMimeInfo', Copy) { mustRunAfter tasks.jpackageImage from('src/main/deploy/package/linux') into(layout.buildDirectory.dir('jpackage/Sparrow/lib')) include('sparrowwallet-Sparrow-MimeInfo.xml') } static def getDirectorySize(File directory) { long size = 0 if(directory.isFile()) { size = directory.length() } else if(directory.isDirectory()) { directory.eachFileRecurse { file -> if(file.isFile()) { size += file.length() } } } return Long.toString(size/1024 as long) } tasks.register('removeGroupWritePermission', Exec) { commandLine 'chmod', '-R', 'g-w', "$buildDir/jpackage/Sparrow" } tasks.register('packageZipDistribution', Zip) { archiveFileName = "Sparrow-${version}.zip" destinationDirectory = file("$buildDir/jpackage") preserveFileTimestamps = os.macOsX from("$buildDir/jpackage/") { include "Sparrow/**" include "Sparrow.app/**" } } tasks.register('packageTarDistribution', Tar) { dependsOn removeGroupWritePermission archiveFileName = "sparrow${headless ? 'server': 'wallet'}-${version}-${releaseArch}.tar.gz" destinationDirectory = file("$buildDir/jpackage") compression = Compression.GZIP from("$buildDir/jpackage/") { include "Sparrow/**" } } 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}/*", 'jSerialComm-' : "${serialOs}/${serialArch}/*", 'usb4java-' : "org/usb4java/${jnaPlatform}/*", 'jzbar-' : "native/${osName}/${osArch}/*", 'openpnp-capture-java-': "native/${osName}/${osArch}/*", ] tasks.register('extractNativeLibraries') { dependsOn 'jlink' def drongoNativeDir = "${project(':drongo').projectDir}/src/main/resources/native/${osName}/${osArch}" doLast { def imageLib = file("$buildDir/image/lib") // Project-owned natives copy { from drongoNativeDir, "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') requires('javafx.controls') } module('com.github.arteam:simple-json-rpc-core', 'simple.json.rpc.core') { exports('com.github.arteam.simplejsonrpc.core.annotation') exports('com.github.arteam.simplejsonrpc.core.domain') requires('com.fasterxml.jackson.core') requires('com.fasterxml.jackson.annotation') requires('com.fasterxml.jackson.databind') requires('org.jetbrains.annotations') } module('com.github.arteam:simple-json-rpc-client', 'simple.json.rpc.client') { exports('com.github.arteam.simplejsonrpc.client') exports('com.github.arteam.simplejsonrpc.client.builder') exports('com.github.arteam.simplejsonrpc.client.exception') requires('com.fasterxml.jackson.core') requires('com.fasterxml.jackson.databind') requires('simple.json.rpc.core') } module('com.github.arteam:simple-json-rpc-server', 'simple.json.rpc.server') { exports('com.github.arteam.simplejsonrpc.server') requires('simple.json.rpc.core') requires('com.google.common') requires('org.slf4j') requires('com.fasterxml.jackson.databind') } module('net.sourceforge.javacsv:javacsv', 'net.sourceforge.javacsv') { exports('com.csvreader') } module('com.google.guava:listenablefuture|empty-to-avoid-conflict-with-guava', 'com.google.guava.listenablefuture') module('com.google.code.findbugs:jsr305', 'com.google.code.findbugs.jsr305') module('j2objc-annotations-2.8.jar', 'com.google.j2objc.j2objc.annotations', '2.8') module('org.fxmisc.richtext:richtextfx', 'org.fxmisc.richtext') { exports('org.fxmisc.richtext') exports('org.fxmisc.richtext.event') exports('org.fxmisc.richtext.model') requires('javafx.base') requires('javafx.controls') requires('javafx.graphics') requires('org.fxmisc.flowless') requires('org.reactfx.reactfx') requires('org.fxmisc.undo') requires('org.fxmisc.wellbehaved') } module('org.fxmisc.undo:undofx', 'org.fxmisc.undo') { requires('javafx.base') requires('javafx.controls') requires('javafx.graphics') requires('org.reactfx.reactfx') } module('org.fxmisc.flowless:flowless', 'org.fxmisc.flowless') { exports('org.fxmisc.flowless') requires('javafx.base') requires('javafx.controls') requires('javafx.graphics') requires('org.reactfx.reactfx') } module('org.reactfx:reactfx', 'org.reactfx.reactfx') { exports('org.reactfx') exports('org.reactfx.value') exports('org.reactfx.collection') exports('org.reactfx.util') requires('javafx.base') requires('javafx.graphics') requires('javafx.controls') } module('io.reactivex.rxjava2:rxjavafx', 'io.reactivex.rxjava2fx') { exports('io.reactivex.rxjavafx.schedulers') requires('io.reactivex.rxjava2') requires('javafx.graphics') } module('org.flywaydb:flyway-core', 'org.flywaydb.core') { exports('org.flywaydb.core') exports('org.flywaydb.core.api') exports('org.flywaydb.core.api.exception') exports('org.flywaydb.core.api.configuration') uses('org.flywaydb.core.extensibility.Plugin') requires('java.sql') } module('org.fxmisc.wellbehaved:wellbehavedfx', 'org.fxmisc.wellbehaved') { requires('javafx.base') requires('javafx.graphics') } module('com.github.jai-imageio:jai-imageio-core', 'com.github.jai.imageio.jai.imageio.core') { requires('java.desktop') } module('co.nstant.in:cbor', 'co.nstant.in.cbor') { exports('co.nstant.in.cbor') exports('co.nstant.in.cbor.model') exports('co.nstant.in.cbor.builder') } module('org.jcommander:jcommander', 'org.jcommander') { exports('com.beust.jcommander') } module('com.sparrowwallet:hid4java', 'org.hid4java') { requires('com.sun.jna') exports('org.hid4java') exports('org.hid4java.jna') } module('com.sparrowwallet:usb4java', 'org.usb4java') { exports('org.usb4java') } module('com.jcraft:jzlib', 'com.jcraft.jzlib') { exports('com.jcraft.jzlib') } } kmpTorResourceFilterJar { keepTorCompilation("current","current") }