diff --git a/build.gradle b/build.gradle index 2982b9bf..e5adbf9c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,8 +1,8 @@ plugins { id 'application' - id 'org.openjfx.javafxplugin' version '0.0.9' - id 'org.kordamp.gradle.jdeps' version '0.9.0' - id 'org.beryx.jlink' version '2.22.0' + id 'extra-java-module-info' + id 'com.dua3.javafxgradle7plugin' version '0.0.9' + id 'org.beryx.jlink' version '2.24.0' } def sparrowVersion = '1.4.2' @@ -29,7 +29,7 @@ tasks.withType(AbstractArchiveTask) { } javafx { - version = "15" + version = "16" modules = [ 'javafx.controls', 'javafx.fxml', 'javafx.swing', 'javafx.graphics' ] } @@ -53,19 +53,28 @@ dependencies { implementation('org.flywaydb:flyway-core:7.10.5-SNAPSHOT') implementation('org.fxmisc.richtext:richtextfx:0.10.4') implementation('no.tornado:tornadofx-controls:1.0.4') - implementation('com.google.zxing:javase:3.4.0') - implementation('com.github.arteam:simple-json-rpc-client:1.0') + implementation('com.google.zxing:javase:3.4.0') { + exclude group: 'com.beust', module: 'jcommander' + } + implementation('com.beust:jcommander:1.81') + implementation('com.github.arteam:simple-json-rpc-core:1.0') + implementation('com.github.arteam:simple-json-rpc-client:1.0') { + exclude group: 'com.github.arteam', module: 'simple-json-rpc-core' + } implementation('com.github.arteam:simple-json-rpc-server:1.0') { exclude group: 'org.slf4j' } - implementation('com.sparrowwallet:hummingbird:1.6.0') + implementation('com.sparrowwallet:hummingbird:1.6.1') implementation('com.nativelibs4java:bridj:0.7-20140918-3') { exclude group: 'com.google.android.tools', module: 'dx' } implementation('com.github.sarxos:webcam-capture:0.3.13-SNAPSHOT') { exclude group: 'com.nativelibs4java', module: 'bridj' } - implementation("com.sparrowwallet:netlayer-jpms-${osName}:0.6.8") + implementation("com.sparrowwallet:netlayer-jpms-${osName}:0.6.8") { + exclude group: 'org.jetbrains.kotlin' + } + implementation('org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.20') implementation('de.codecentric.centerdevice:centerdevice-nsmenufx:2.1.7') implementation('org.controlsfx:controlsfx:11.1.0' ) { exclude group: 'org.openjfx', module: 'javafx-base' @@ -76,7 +85,7 @@ dependencies { exclude group: 'org.openjfx', module: 'javafx-web' exclude group: 'org.openjfx', module: 'javafx-media' } - implementation('dev.bwt:bwt-jni:0.1.7') + implementation('dev.bwt:bwt-jni:0.1.8') implementation('net.sourceforge.javacsv:javacsv:2.0') implementation('org.slf4j:jul-to-slf4j:1.7.30') { exclude group: 'org.slf4j' @@ -84,7 +93,10 @@ dependencies { testImplementation('junit:junit:4.12') } -mainClassName = 'com.sparrowwallet.sparrow/com.sparrowwallet.sparrow.MainApp' +application { + mainModule = 'com.sparrowwallet.sparrow' + mainClass = 'com.sparrowwallet.sparrow.MainApp' +} compileJava { options.with { @@ -135,9 +147,9 @@ jlink { requires 'java.xml' requires 'java.logging' requires 'javafx.base' - requires 'com.fasterxml.jackson.databind' requires 'jdk.crypto.cryptoki' requires 'java.management' + requires 'io.leangen.geantyref' uses 'org.flywaydb.core.extensibility.FlywayExtension' uses 'org.flywaydb.core.internal.database.DatabaseType' } @@ -154,17 +166,18 @@ jlink { "--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.javafx.tk=com.sparrowwallet.merged.module", - "--add-opens=javafx.graphics/com.sun.javafx.tk.quantum=com.sparrowwallet.merged.module", - "--add-opens=javafx.graphics/com.sun.glass.ui=com.sparrowwallet.merged.module", - "--add-opens=javafx.controls/com.sun.javafx.scene.control=com.sparrowwallet.merged.module", - "--add-opens=javafx.graphics/com.sun.javafx.menu=com.sparrowwallet.merged.module", + "--add-opens=javafx.graphics/com.sun.javafx.tk=centerdevice.nsmenufx", + "--add-opens=javafx.graphics/com.sun.javafx.tk.quantum=centerdevice.nsmenufx", + "--add-opens=javafx.graphics/com.sun.glass.ui=centerdevice.nsmenufx", + "--add-opens=javafx.controls/com.sun.javafx.scene.control=centerdevice.nsmenufx", + "--add-opens=javafx.graphics/com.sun.javafx.menu=centerdevice.nsmenufx", "--add-opens=javafx.graphics/com.sun.glass.ui=com.sparrowwallet.sparrow", "--add-opens=javafx.graphics/com.sun.javafx.application=com.sparrowwallet.sparrow", "--add-opens=java.base/java.net=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=com.sparrowwallet.sparrow", + "--add-reads=com.sparrowwallet.merged.module=logback.classic"] if(os.macOsX) { jvmArgs += "--add-opens=javafx.graphics/com.sun.glass.ui.mac=com.sparrowwallet.merged.module" @@ -216,3 +229,196 @@ task packageTarDistribution(type: Tar) { include "Sparrow/**" } } + +extraJavaModuleInfo { + module('tornadofx-controls-1.0.4.jar', 'tornadofx.controls', '1.0.4') { + exports('tornadofx.control') + requires('javafx.controls') + } + module('simple-json-rpc-core-1.0.jar', 'simple.json.rpc.core', '1.0') { + 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('simple-json-rpc-client-1.0.jar', 'simple.json.rpc.client', '1.0') { + exports('com.github.arteam.simplejsonrpc.client') + exports('com.github.arteam.simplejsonrpc.client.builder') + exports('com.github.arteam.simplejsonrpc.client.exception') + requires('com.fasterxml.jackson.databind') + requires('simple.json.rpc.core') + } + module('simple-json-rpc-server-1.0.jar', 'simple.json.rpc.server', '1.0') { + exports('com.github.arteam.simplejsonrpc.server') + requires('simple.json.rpc.core') + requires('com.google.common') + requires('org.slf4j') + requires('com.fasterxml.jackson.databind') + } + module('bridj-0.7-20140918-3.jar', 'com.nativelibs4java.bridj', '0.7-20140918-3') { + exports('org.bridj') + exports('org.bridj.cpp') + requires('java.logging') + } + module('webcam-capture-0.3.13-SNAPSHOT.jar', 'com.github.sarxos.webcam.capture', '0.3.13-SNAPSHOT') { + exports('com.github.sarxos.webcam') + exports('com.github.sarxos.webcam.ds.buildin') + exports('com.github.sarxos.webcam.ds.buildin.natives') + requires('java.desktop') + requires('com.nativelibs4java.bridj') + requires('org.slf4j') + } + module('centerdevice-nsmenufx-2.1.7.jar', 'centerdevice.nsmenufx', '2.1.7') { + exports('de.codecentric.centerdevice') + requires('javafx.base') + requires('javafx.controls') + requires('javafx.graphics') + } + module('javacsv-2.0.jar', 'net.sourceforge.javacsv', '2.0') { + exports('com.csvreader') + } + module('jul-to-slf4j-1.7.30.jar', 'org.slf4j.jul.to.slf4j', '1.7.30') { + exports('org.slf4j.bridge') + requires('java.logging') + } + module('jeromq-0.5.0.jar', 'jeromq', '0.5.0') { + exports('org.zeromq') + } + module('json-simple-1.1.1.jar', 'json.simple', '1.1.1') { + exports('org.json.simple') + } + module('logback-classic-1.2.3.jar', 'logback.classic', '1.2.3') { + exports('ch.qos.logback.classic') + requires('org.slf4j') + requires('logback.core') + requires('java.xml') + requires('java.logging') + } + module('kotlin-logging-1.5.4.jar', 'io.github.microutils.kotlin.logging', '1.5.4') { + exports('mu') + requires('kotlin.stdlib') + requires('org.slf4j') + } + module('failureaccess-1.0.1.jar', 'failureaccess', '1.0.1') { + exports('com.google.common.util.concurrent.internal') + } + module('listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar', 'com.google.guava.listenablefuture', '9999.0-empty-to-avoid-conflict-with-guava') + module('guava-28.2-jre.jar', 'com.google.common', '28.2-jre') { + exports('com.google.common.eventbus') + exports('com.google.common.net') + exports('com.google.common.base') + exports('com.google.common.collect') + exports('com.google.common.io') + requires('failureaccess') + requires('java.logging') + } + module('jsr305-3.0.2.jar', 'com.google.code.findbugs.jsr305', '3.0.2') + module('j2objc-annotations-1.3.jar', 'com.google.j2objc.j2objc.annotations', '1.3') + module('jdbi3-core-3.20.0.jar', 'org.jdbi.v3.core', '3.20.0') { + exports('org.jdbi.v3.core') + exports('org.jdbi.v3.core.mapper') + exports('org.jdbi.v3.core.statement') + exports('org.jdbi.v3.core.result') + exports('org.jdbi.v3.core.h2') + exports('org.jdbi.v3.core.spi') + requires('io.leangen.geantyref') + requires('java.sql') + requires('org.slf4j') + } + module('geantyref-1.3.11.jar', 'io.leangen.geantyref', '1.3.11') { + exports('io.leangen.geantyref') + } + module('richtextfx-0.10.4.jar', 'org.fxmisc.richtext', '0.10.4') { + 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.undofx') + requires('org.fxmisc.wellbehaved') + } + module('undofx-2.1.0.jar', 'org.fxmisc.undo.undofx', '2.1.0') { + requires('javafx.base') + requires('javafx.controls') + requires('javafx.graphics') + requires('org.reactfx.reactfx') + } + module('flowless-0.6.1.jar', 'org.fxmisc.flowless', '0.6.1') { + exports('org.fxmisc.flowless') + requires('javafx.base') + requires('javafx.controls') + requires('javafx.graphics') + requires('org.reactfx.reactfx') + } + module('reactfx-2.0-M5.jar', 'org.reactfx.reactfx', '2.0-M5') { + 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('wellbehavedfx-0.3.3.jar', 'org.fxmisc.wellbehaved', '0.3.3') { + requires('javafx.base') + requires('javafx.graphics') + } + module('jai-imageio-core-1.4.0.jar', 'com.github.jai.imageio.jai.imageio.core', '1.4.0') + module('kotlin-stdlib-jdk8-1.5.20.jar', 'org.jetbrains.kotlin.kotlin.stdlib.jdk8', '1.5.20') + module('kotlin-stdlib-jdk7-1.5.20.jar', 'org.jetbrains.kotlin.kotlin.stdlib.jdk7', '1.5.20') + module('kotlin-stdlib-1.5.20.jar', 'kotlin.stdlib', '1.5.20') { + exports('kotlin') + } + module('hummingbird-1.6.1.jar', 'com.sparrowwallet.hummingbird', '1.6.1') { + exports('com.sparrowwallet.hummingbird') + exports('com.sparrowwallet.hummingbird.registry') + requires('co.nstant.in.cbor') + } + module('cbor-0.9.jar', 'co.nstant.in.cbor', '0.9') { + exports('co.nstant.in.cbor') + } + module("netlayer-jpms-${osName}-0.6.8.jar", 'netlayer.jpms', '0.6.8') { + exports('org.berndpruenster.netlayer.tor') + requires('com.github.ravn.jsocks') + requires('com.github.JesusMcCloud.jtorctl') + requires('kotlin.stdlib') + requires('commons.compress') + requires('org.tukaani.xz') + requires('java.management') + requires('io.github.microutils.kotlin.logging') + } + module('jtorctl-1.5.jar', 'com.github.JesusMcCloud.jtorctl', '1.5') { + exports('net.freehaven.tor.control') + } + module('commons-compress-1.18.jar', 'commons.compress', '1.18') { + exports('org.apache.commons.compress') + requires('org.tukaani.xz') + } + module('xz-1.6.jar', 'org.tukaani.xz', '1.6') { + exports('org.tukaani.xz') + } + module('jsocks-1.0.jar', 'com.github.ravn.jsocks', '1.0') { + exports('com.runjva.sourceforge.jsocks.protocol') + requires('org.slf4j') + } + module('jnacl-1.0.0.jar', 'eu.neilalexander.jnacl', '1.0.0') + module('logback-core-1.2.3.jar', 'logback.core', '1.2.3') { + requires('java.xml') + } + module('kotlin-stdlib-common-1.5.20.jar', 'org.jetbrains.kotlin.kotlin.stdlib.common', '1.5.20') { + exports('kotlin.jvm') + exports('kotlin.collections') + } + module('jcommander-1.81.jar', 'com.beust.jcommander', '1.81') { + exports('com.beust.jcommander') + } + module('junit-4.12.jar', 'junit', '4.12') { + exports('org.junit') + } + module('hamcrest-core-1.3.jar', 'org.hamcrest.core', '1.3') +} \ No newline at end of file diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle new file mode 100644 index 00000000..ebb5470f --- /dev/null +++ b/buildSrc/build.gradle @@ -0,0 +1,21 @@ +plugins { + id 'java-gradle-plugin' // so we can assign and ID to our plugin +} + +dependencies { + implementation 'org.ow2.asm:asm:8.0.1' +} + +repositories { + mavenCentral() +} + +gradlePlugin { + plugins { + // here we register our plugin with an ID + register("extra-java-module-info") { + id = "extra-java-module-info" + implementationClass = "org.gradle.sample.transform.javamodules.ExtraModuleInfoPlugin" + } + } +} diff --git a/buildSrc/src/main/java/org/gradle/sample/transform/javamodules/ExtraModuleInfoPlugin.java b/buildSrc/src/main/java/org/gradle/sample/transform/javamodules/ExtraModuleInfoPlugin.java new file mode 100644 index 00000000..48d0b0b2 --- /dev/null +++ b/buildSrc/src/main/java/org/gradle/sample/transform/javamodules/ExtraModuleInfoPlugin.java @@ -0,0 +1,54 @@ +package org.gradle.sample.transform.javamodules; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.attributes.Attribute; +import org.gradle.api.plugins.JavaPlugin; + +/** + * Entry point of our plugin that should be applied in the root project. + */ +public class ExtraModuleInfoPlugin implements Plugin { + + @Override + public void apply(Project project) { + // register the plugin extension as 'extraJavaModuleInfo {}' configuration block + ExtraModuleInfoPluginExtension extension = project.getObjects().newInstance(ExtraModuleInfoPluginExtension.class); + project.getExtensions().add(ExtraModuleInfoPluginExtension.class, "extraJavaModuleInfo", extension); + + // setup the transform for all projects in the build + project.getPlugins().withType(JavaPlugin.class).configureEach(javaPlugin -> configureTransform(project, extension)); + } + + private void configureTransform(Project project, ExtraModuleInfoPluginExtension extension) { + Attribute artifactType = Attribute.of("artifactType", String.class); + Attribute javaModule = Attribute.of("javaModule", Boolean.class); + + // compile and runtime classpath express that they only accept modules by requesting the javaModule=true attribute + project.getConfigurations().matching(this::isResolvingJavaPluginConfiguration).all( + c -> c.getAttributes().attribute(javaModule, true)); + + // all Jars have a javaModule=false attribute by default; the transform also recognizes modules and returns them without modification + project.getDependencies().getArtifactTypes().getByName("jar").getAttributes().attribute(javaModule, false); + + // register the transform for Jars and "javaModule=false -> javaModule=true"; the plugin extension object fills the input parameter + project.getDependencies().registerTransform(ExtraModuleInfoTransform.class, t -> { + t.parameters(p -> { + p.setModuleInfo(extension.getModuleInfo()); + p.setAutomaticModules(extension.getAutomaticModules()); + }); + t.getFrom().attribute(artifactType, "jar").attribute(javaModule, false); + t.getTo().attribute(artifactType, "jar").attribute(javaModule, true); + }); + } + + private boolean isResolvingJavaPluginConfiguration(Configuration configuration) { + if (!configuration.isCanBeResolved()) { + return false; + } + return configuration.getName().endsWith(JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME.substring(1)) + || configuration.getName().endsWith(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME.substring(1)) + || configuration.getName().endsWith(JavaPlugin.ANNOTATION_PROCESSOR_CONFIGURATION_NAME.substring(1)); + } +} diff --git a/buildSrc/src/main/java/org/gradle/sample/transform/javamodules/ExtraModuleInfoPluginExtension.java b/buildSrc/src/main/java/org/gradle/sample/transform/javamodules/ExtraModuleInfoPluginExtension.java new file mode 100644 index 00000000..d0d4e0fd --- /dev/null +++ b/buildSrc/src/main/java/org/gradle/sample/transform/javamodules/ExtraModuleInfoPluginExtension.java @@ -0,0 +1,52 @@ +package org.gradle.sample.transform.javamodules; + + +import org.gradle.api.Action; + +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; + +/** + * A data class to collect all the module information we want to add. + * Here the class is used as extension that can be configured in the build script + * and as input to the ExtraModuleInfoTransform that add the information to Jars. + */ +public class ExtraModuleInfoPluginExtension { + + private final Map moduleInfo = new HashMap<>(); + private final Map automaticModules = new HashMap<>(); + + /** + * Add full module information for a given Jar file. + */ + public void module(String jarName, String moduleName, String moduleVersion) { + module(jarName, moduleName, moduleVersion, null); + } + + /** + * Add full module information, including exported packages and dependencies, for a given Jar file. + */ + public void module(String jarName, String moduleName, String moduleVersion, @Nullable Action conf) { + ModuleInfo moduleInfo = new ModuleInfo(moduleName, moduleVersion); + if (conf != null) { + conf.execute(moduleInfo); + } + this.moduleInfo.put(jarName, moduleInfo); + } + + /** + * Add only an automatic module name to a given jar file. + */ + public void automaticModule(String jarName, String moduleName) { + automaticModules.put(jarName, moduleName); + } + + protected Map getModuleInfo() { + return moduleInfo; + } + + protected Map getAutomaticModules() { + return automaticModules; + } +} diff --git a/buildSrc/src/main/java/org/gradle/sample/transform/javamodules/ExtraModuleInfoTransform.java b/buildSrc/src/main/java/org/gradle/sample/transform/javamodules/ExtraModuleInfoTransform.java new file mode 100644 index 00000000..94e6922b --- /dev/null +++ b/buildSrc/src/main/java/org/gradle/sample/transform/javamodules/ExtraModuleInfoTransform.java @@ -0,0 +1,164 @@ +package org.gradle.sample.transform.javamodules; + +import org.gradle.api.artifacts.transform.InputArtifact; +import org.gradle.api.artifacts.transform.TransformAction; +import org.gradle.api.artifacts.transform.TransformOutputs; +import org.gradle.api.artifacts.transform.TransformParameters; +import org.gradle.api.file.FileSystemLocation; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.Input; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.ModuleVisitor; +import org.objectweb.asm.Opcodes; + +import java.io.*; +import java.util.Collections; +import java.util.Map; +import java.util.jar.*; +import java.util.regex.Pattern; +import java.util.zip.ZipEntry; + +/** + * An artifact transform that applies additional information to Jars without module information. + * The transformation fails the build if a Jar does not contain information and no extra information + * was defined for it. This way we make sure that all Jars are turned into modules. + */ +abstract public class ExtraModuleInfoTransform implements TransformAction { + + public static class Parameter implements TransformParameters, Serializable { + private Map moduleInfo = Collections.emptyMap(); + private Map automaticModules = Collections.emptyMap(); + + @Input + public Map getModuleInfo() { + return moduleInfo; + } + + @Input + public Map getAutomaticModules() { + return automaticModules; + } + + public void setModuleInfo(Map moduleInfo) { + this.moduleInfo = moduleInfo; + } + + public void setAutomaticModules(Map automaticModules) { + this.automaticModules = automaticModules; + } + } + + @InputArtifact + protected abstract Provider getInputArtifact(); + + @Override + public void transform(TransformOutputs outputs) { + Map moduleInfo = getParameters().moduleInfo; + Map automaticModules = getParameters().automaticModules; + File originalJar = getInputArtifact().get().getAsFile(); + String originalJarName = originalJar.getName(); + + if (isModule(originalJar)) { + outputs.file(originalJar); + } else if (moduleInfo.containsKey(originalJarName)) { + addModuleDescriptor(originalJar, getModuleJar(outputs, originalJar), moduleInfo.get(originalJarName)); + } else if (isAutoModule(originalJar)) { + outputs.file(originalJar); + } else if (automaticModules.containsKey(originalJarName)) { + addAutomaticModuleName(originalJar, getModuleJar(outputs, originalJar), automaticModules.get(originalJarName)); + } else { + throw new RuntimeException("Not a module and no mapping defined: " + originalJarName); + } + } + + private boolean isModule(File jar) { + Pattern moduleInfoClassMrjarPath = Pattern.compile("META-INF/versions/\\d+/module-info.class"); + try (JarInputStream inputStream = new JarInputStream(new FileInputStream(jar))) { + boolean isMultiReleaseJar = containsMultiReleaseJarEntry(inputStream); + ZipEntry next = inputStream.getNextEntry(); + while (next != null) { + if ("module-info.class".equals(next.getName())) { + return true; + } + if (isMultiReleaseJar && moduleInfoClassMrjarPath.matcher(next.getName()).matches()) { + return true; + } + next = inputStream.getNextEntry(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + return false; + } + + private boolean containsMultiReleaseJarEntry(JarInputStream jarStream) { + Manifest manifest = jarStream.getManifest(); + return manifest != null && Boolean.parseBoolean(manifest.getMainAttributes().getValue("Multi-Release")); + } + + private boolean isAutoModule(File jar) { + try (JarInputStream inputStream = new JarInputStream(new FileInputStream(jar))) { + return inputStream.getManifest().getMainAttributes().getValue("Automatic-Module-Name") != null; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private File getModuleJar(TransformOutputs outputs, File originalJar) { + return outputs.file(originalJar.getName().substring(0, originalJar.getName().lastIndexOf('.')) + "-module.jar"); + } + + private static void addAutomaticModuleName(File originalJar, File moduleJar, String moduleName) { + try (JarInputStream inputStream = new JarInputStream(new FileInputStream(originalJar))) { + Manifest manifest = inputStream.getManifest(); + manifest.getMainAttributes().put(new Attributes.Name("Automatic-Module-Name"), moduleName); + try (JarOutputStream outputStream = new JarOutputStream(new FileOutputStream(moduleJar), inputStream.getManifest())) { + copyEntries(inputStream, outputStream); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static void addModuleDescriptor(File originalJar, File moduleJar, ModuleInfo moduleInfo) { + try (JarInputStream inputStream = new JarInputStream(new FileInputStream(originalJar))) { + try (JarOutputStream outputStream = new JarOutputStream(new FileOutputStream(moduleJar), inputStream.getManifest())) { + copyEntries(inputStream, outputStream); + outputStream.putNextEntry(new JarEntry("module-info.class")); + outputStream.write(addModuleInfo(moduleInfo)); + outputStream.closeEntry(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static void copyEntries(JarInputStream inputStream, JarOutputStream outputStream) throws IOException { + JarEntry jarEntry = inputStream.getNextJarEntry(); + while (jarEntry != null) { + outputStream.putNextEntry(jarEntry); + outputStream.write(inputStream.readAllBytes()); + outputStream.closeEntry(); + jarEntry = inputStream.getNextJarEntry(); + } + } + + private static byte[] addModuleInfo(ModuleInfo moduleInfo) { + ClassWriter classWriter = new ClassWriter(0); + classWriter.visit(Opcodes.V9, Opcodes.ACC_MODULE, "module-info", null, null, null); + ModuleVisitor moduleVisitor = classWriter.visitModule(moduleInfo.getModuleName(), Opcodes.ACC_OPEN, moduleInfo.getModuleVersion()); + for (String packageName : moduleInfo.getExports()) { + moduleVisitor.visitExport(packageName.replace('.', '/'), 0); + } + moduleVisitor.visitRequire("java.base", 0, null); + for (String requireName : moduleInfo.getRequires()) { + moduleVisitor.visitRequire(requireName, 0, null); + } + for (String requireName : moduleInfo.getRequiresTransitive()) { + moduleVisitor.visitRequire(requireName, Opcodes.ACC_TRANSITIVE, null); + } + moduleVisitor.visitEnd(); + classWriter.visitEnd(); + return classWriter.toByteArray(); + } +} diff --git a/buildSrc/src/main/java/org/gradle/sample/transform/javamodules/ModuleInfo.java b/buildSrc/src/main/java/org/gradle/sample/transform/javamodules/ModuleInfo.java new file mode 100644 index 00000000..9884a911 --- /dev/null +++ b/buildSrc/src/main/java/org/gradle/sample/transform/javamodules/ModuleInfo.java @@ -0,0 +1,53 @@ +package org.gradle.sample.transform.javamodules; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * Data class to hold the information that should be added as module-info.class to an existing Jar file. + */ +public class ModuleInfo implements Serializable { + private String moduleName; + private String moduleVersion; + private List exports = new ArrayList<>(); + private List requires = new ArrayList<>(); + private List requiresTransitive = new ArrayList<>(); + + ModuleInfo(String moduleName, String moduleVersion) { + this.moduleName = moduleName; + this.moduleVersion = moduleVersion; + } + + public void exports(String exports) { + this.exports.add(exports); + } + + public void requires(String requires) { + this.requires.add(requires); + } + + public void requiresTransitive(String requiresTransitive) { + this.requiresTransitive.add(requiresTransitive); + } + + public String getModuleName() { + return moduleName; + } + + protected String getModuleVersion() { + return moduleVersion; + } + + protected List getExports() { + return exports; + } + + protected List getRequires() { + return requires; + } + + protected List getRequiresTransitive() { + return requiresTransitive; + } +} diff --git a/drongo b/drongo index c0213007..9d3c02d1 160000 --- a/drongo +++ b/drongo @@ -1 +1 @@ -Subproject commit c02130079782cc10dc47f933274f12d08c497c19 +Subproject commit 9d3c02d18440b82cb261a89f372da72dd0f87a1f diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 490fda85..e708b1c0 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a4b44297..69a97150 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 2fe81a7d..4f906e0c 100755 --- a/gradlew +++ b/gradlew @@ -82,6 +82,7 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -129,6 +130,7 @@ fi if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath diff --git a/gradlew.bat b/gradlew.bat index 9109989e..ac1b06f9 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,28 +64,14 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell diff --git a/src/main/java/com/sparrowwallet/sparrow/io/IOUtils.java b/src/main/java/com/sparrowwallet/sparrow/io/IOUtils.java index c93ac253..161f15a4 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/IOUtils.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/IOUtils.java @@ -90,7 +90,9 @@ public class IOUtils { // if it is a subdirectory, we just return the directory name entry = entry.substring(0, checkSubdir); } - result.add(entry); + if(!entry.isEmpty()) { + result.add(entry); + } } } diff --git a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java index fbc21942..cb7a6195 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/ElectrumServer.java @@ -1,7 +1,6 @@ package com.sparrowwallet.sparrow.net; import com.github.arteam.simplejsonrpc.client.Transport; -import com.google.common.collect.Iterables; import com.google.common.eventbus.Subscribe; import com.google.common.net.HostAndPort; import com.sparrowwallet.drongo.KeyPurpose; diff --git a/src/main/java/com/sparrowwallet/sparrow/preferences/ServerPreferencesController.java b/src/main/java/com/sparrowwallet/sparrow/preferences/ServerPreferencesController.java index d2b43f32..0579cc73 100644 --- a/src/main/java/com/sparrowwallet/sparrow/preferences/ServerPreferencesController.java +++ b/src/main/java/com/sparrowwallet/sparrow/preferences/ServerPreferencesController.java @@ -22,7 +22,6 @@ import javafx.stage.DirectoryChooser; import javafx.stage.FileChooser; import javafx.stage.Stage; import javafx.util.Duration; -import net.freehaven.tor.control.TorControlError; import org.berndpruenster.netlayer.tor.Tor; import org.controlsfx.control.SegmentedButton; import org.controlsfx.glyphfont.Glyph; diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java index bfb286c9..e98cc18e 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java @@ -19,7 +19,6 @@ import com.sparrowwallet.sparrow.io.Device; import com.sparrowwallet.sparrow.net.ElectrumServer; import com.sparrowwallet.sparrow.io.Storage; import com.sparrowwallet.sparrow.payjoin.Payjoin; -import com.sparrowwallet.sparrow.payjoin.PayjoinReceiverException; import com.sparrowwallet.sparrow.wallet.Entry; import com.sparrowwallet.sparrow.wallet.HashIndexEntry; import com.sparrowwallet.sparrow.wallet.TransactionEntry; diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index a8bfee62..ddbc969b 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -1,6 +1,7 @@ open module com.sparrowwallet.sparrow { requires java.desktop; requires java.net.http; + requires javafx.base; requires javafx.controls; requires javafx.fxml; requires javafx.graphics; @@ -10,7 +11,6 @@ open module com.sparrowwallet.sparrow { requires tornadofx.controls; requires com.sparrowwallet.drongo; requires com.google.common; - requires flowless; requires com.google.zxing; requires com.google.zxing.javase; requires simple.json.rpc.client; @@ -18,21 +18,22 @@ open module com.sparrowwallet.sparrow { requires simple.json.rpc.core; requires org.jetbrains.annotations; requires com.fasterxml.jackson.databind; - requires webcam.capture; requires netlayer.jpms; - requires hummingbird; - requires centerdevice.nsmenufx; - requires jcommander; requires org.slf4j; - requires bwt.jni; - requires jtorctl; - requires javacsv; - requires jul.to.slf4j; - requires bridj; requires com.google.gson; requires org.jdbi.v3.core; requires org.jdbi.v3.sqlobject; requires org.flywaydb.core; requires com.zaxxer.hikari; requires com.h2database; + requires com.sparrowwallet.hummingbird; + requires org.fxmisc.flowless; + requires com.github.sarxos.webcam.capture; + requires centerdevice.nsmenufx; + requires com.github.JesusMcCloud.jtorctl; + requires com.beust.jcommander; + requires org.slf4j.jul.to.slf4j; + requires net.sourceforge.javacsv; + requires com.nativelibs4java.bridj; + requires org.reactfx.reactfx; } \ No newline at end of file diff --git a/src/test/java/com/sparrowwallet/sparrow/io/IoTest.java b/src/test/java/com/sparrowwallet/sparrow/io/IoTest.java index 588f3a4c..f0cc1458 100644 --- a/src/test/java/com/sparrowwallet/sparrow/io/IoTest.java +++ b/src/test/java/com/sparrowwallet/sparrow/io/IoTest.java @@ -1,13 +1,23 @@ package com.sparrowwallet.sparrow.io; import java.io.File; +import java.io.IOException; import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; public class IoTest { public static final String IO_TEST_PATH = "/com/sparrowwallet/sparrow/io/"; protected File getFile(String filename) { - return new File(this.getClass().getResource(IO_TEST_PATH + filename).getFile()); + try { + Path tempFile = Files.createTempFile(filename, null); + Files.copy(getInputStream(filename), tempFile, StandardCopyOption.REPLACE_EXISTING); + return tempFile.toFile(); + } catch(IOException e) { + throw new IllegalStateException(e); + } } protected InputStream getInputStream(String filename) { diff --git a/src/test/java/com/sparrowwallet/sparrow/io/StorageTest.java b/src/test/java/com/sparrowwallet/sparrow/io/StorageTest.java index 5b5656c0..44dd4023 100644 --- a/src/test/java/com/sparrowwallet/sparrow/io/StorageTest.java +++ b/src/test/java/com/sparrowwallet/sparrow/io/StorageTest.java @@ -57,7 +57,7 @@ public class StorageTest extends IoTest { @Test public void multipleLoadTest() throws IOException, MnemonicException, StorageException { - for(int i = 0; i < 100; i++) { + for(int i = 0; i < 5; i++) { loadSeedWallet(); } }