diff --git a/build.gradle b/build.gradle index b11d908fa3..3b5c63bd42 100644 --- a/build.gradle +++ b/build.gradle @@ -264,6 +264,9 @@ task installTemplateNodes(dependsOn: 'buildCordaJAR') << { } } +// Aliasing the publishToMavenLocal for simplicity. +task(install, dependsOn: 'publishToMavenLocal') + publishing { publications { corda(MavenPublication) { diff --git a/core/build.gradle b/core/build.gradle index 208e2d82c5..28b10af031 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -1,7 +1,6 @@ apply plugin: 'kotlin' apply plugin: QuasarPlugin // Applying the maven plugin means this will get installed locally when running "gradle install" -apply plugin: 'maven' apply plugin: DefaultPublishTasks buildscript { diff --git a/docs/source/creating-a-cordapp.rst b/docs/source/creating-a-cordapp.rst index 8f67960851..f610ccdf81 100644 --- a/docs/source/creating-a-cordapp.rst +++ b/docs/source/creating-a-cordapp.rst @@ -106,10 +106,58 @@ root directory of Corda This will publish corda-$version.jar, contracts-$version.jar, core-$version.jar and node-$version.jar to the group com.r3corda. You can now depend on these as you normally would a Maven dependency. -In Gradle you can depend on these by adding/modifying your build.gradle file to contain the following: +Gradle Plugins for Cordapps +=========================== + +There are several Gradle plugins that reduce your build.gradle boilerplate and make development of Cordapps easier. +The available plugins are in the gradle-plugins directory of the Corda repository. + +Building Gradle Plugins +----------------------- + +To install to your local Maven repository the plugins that Cordapp gradle files require, run the following from the +root of the Corda project: + +.. code-block:: text + + ./gradlew publishToMavenLocal + +The plugins will now be installed to your local Maven repository in ~/.m2 on Unix and %HOMEPATH%\.m2 on Windows. + +Using Gradle Plugins +-------------------- + +To use the plugins, if you are not already using the Cordapp template project, you must modify your build.gradle. Add +the following segments to the relevant part of your build.gradle. + +Template build.gradle +===================== + +To build against Corda and the plugins that cordapps use, update your build.gradle to contain the following: .. code-block:: groovy + buildscript { + ext.corda_version = '' + ... your buildscript ... + + repositories { + ... other repositories ... + mavenLocal() + } + + dependencies { + ... your dependencies ... + classpath "com.r3corda.plugins:cordformation:$corda_version" + classpath "com.r3corda.plugins:quasar-utils:$corda_version" + classpath "com.r3corda.plugins:publish-utils:$corda_version" + } + } + + apply plugin: 'com.r3corda.plugins.cordformation' + apply plugin: 'com.r3corda.plugins.quasar-utils' + apply plugin: 'com.r3corda.plugins.publish-utils' + repositories { mavenLocal() ... other repositories here ... @@ -122,3 +170,78 @@ In Gradle you can depend on these by adding/modifying your build.gradle file to compile "com.r3corda:corda:$corda_version" ... other dependencies here ... } + + ... your tasks ... + + // Sets the classes for Quasar to scan. Loaded by the the quasar-utils plugin. + quasarScan.dependsOn('classes', ... your dependent subprojects...) + + // Standard way to publish Cordapps to maven local with the maven-publish and publish-utils plugin. + publishing { + publications { + jarAndSources(MavenPublication) { + from components.java + // The two lines below are the tasks added by this plugin. + artifact sourceJar + artifact javadocJar + } + } + } + + + +Cordformation +============= + +Cordformation is the local node deployment system for Cordapps, the nodes generated are intended to be used for +experimenting, debugging, and testing node configurations and setups but not intended for production or testnet +deployment. + +To use this gradle plugin you must add a new task that is of the type `com.r3corda.plugins.Cordform` to your +build.gradle and then configure the nodes you wish to deploy with the Node and nodes configuration DSL. +This DSL is specified in the `JavaDoc `_. An example of this is in the template-cordapp and below +is a three node example; + +.. code-block:: text + + task deployNodes(type: com.r3corda.plugins.Cordform, dependsOn: ['build']) { + directory "./build/nodes" // The output directory + networkMap "Controller" // The artemis address of the node named here will be used as the networkMapAddress on all other nodes. + node { + name "Controller" + dirName "controller" + nearestCity "London" + notary true // Sets this node to be a notary + advertisedServices [] + artemisPort 12345 + webPort 12346 + cordapps [] + } + node { + name "NodeA" + dirName "nodea" + nearestCity "London" + advertisedServices [] + artemisPort 31337 + webPort 31339 + cordapps [] + } + node { + name "NodeB" + dirName "nodeb" + nearestCity "New York" + advertisedServices [] + artemisPort 31338 + webPort 31340 + cordapps [] + } + } + +You can create more configurations with new tasks that extend Cordform. + +New nodes can be added by simply adding another node block and giving it a different name, directory and ports. When you +run this task it will install the nodes to the directory specified and a script will be generated (for UNIX users only +at present) to run the nodes with one command. + +Other cordapps can also be specified if they are already specified as classpath or compile dependencies in your +build.gradle. \ No newline at end of file diff --git a/gradle-plugins/README.rst b/gradle-plugins/README.rst new file mode 100644 index 0000000000..8ecc89abe0 --- /dev/null +++ b/gradle-plugins/README.rst @@ -0,0 +1,11 @@ +Gradle Plugins for Cordapps +=========================== + +The projects at this level of the project are gradle plugins for cordapps and are published to Maven Local with +the rest of the Corda libraries. + +.. note:: + + Some of the plugins here are duplicated with the ones in buildSrc. While the duplication is unwanted any + currently known solution (such as publishing from buildSrc or setting up a separate project/repo) would + introduce a two step build which is less convenient. diff --git a/gradle-plugins/cordformation/README.rst b/gradle-plugins/cordformation/README.rst new file mode 100644 index 0000000000..77671f5a26 --- /dev/null +++ b/gradle-plugins/cordformation/README.rst @@ -0,0 +1 @@ +Please refer to the documentation in /doc/build/html/creating-a-cordapp.html#cordformation. \ No newline at end of file diff --git a/gradle-plugins/cordformation/build.gradle b/gradle-plugins/cordformation/build.gradle new file mode 100644 index 0000000000..f973d9fb17 --- /dev/null +++ b/gradle-plugins/cordformation/build.gradle @@ -0,0 +1,23 @@ +apply plugin: 'maven-publish' +apply plugin: 'groovy' + +dependencies { + compile gradleApi() + compile localGroovy() + + compile "com.typesafe:config:1.3.0" +} + +repositories { + mavenCentral() +} + +publishing { + publications { + plugin(MavenPublication) { + from components.java + groupId 'com.r3corda.plugins' + artifactId 'cordformation' + } + } +} \ No newline at end of file diff --git a/gradle-plugins/cordformation/src/main/groovy/com/r3corda/plugins/Cordform.groovy b/gradle-plugins/cordformation/src/main/groovy/com/r3corda/plugins/Cordform.groovy new file mode 100644 index 0000000000..e082834d12 --- /dev/null +++ b/gradle-plugins/cordformation/src/main/groovy/com/r3corda/plugins/Cordform.groovy @@ -0,0 +1,92 @@ +package com.r3corda.plugins + +import org.gradle.api.DefaultTask +import org.gradle.api.tasks.TaskAction +import java.nio.file.Path +import java.nio.file.Paths +import org.gradle.api.Project + +/** + * Creates nodes based on the configuration of this task in the gradle configuration DSL. + * + * See documentation for examples. + */ +class Cordform extends DefaultTask { + protected Path directory = Paths.get("./build/nodes") + protected List nodes = new ArrayList() + protected String networkMapNodeName + + /** + * Set the directory to install nodes into. + * + * @param directory The directory the nodes will be installed into. + * @return + */ + public void directory(String directory) { + this.directory = Paths.get(directory) + } + + /** + * Set the network map node. + * + * @warning Ensure the node name is one of the configured nodes. + * @param nodeName The name of the node that will host the network map. + */ + public void networkMap(String nodeName) { + networkMapNodeName = nodeName + } + + /** + * Add a node configuration. + * + * @param configureClosure A node configuration that will be deployed. + */ + public void node(Closure configureClosure) { + nodes << project.configure(new Node(project), configureClosure) + } + + /** + * Returns a node by name. + * + * @param name The name of the node as specified in the node configuration DSL. + * @return A node instance. + */ + protected Node getNodeByName(String name) { + for(Node node : nodes) { + if(node.name.equals(networkMapNodeName)) { + return node + } + } + + return null + } + + /** + * Installs the run script into the nodes directory. + */ + protected void installRunScript() { + project.copy { + from Cordformation.getPluginFile(project, "com/r3corda/plugins/runnodes") + filter { String line -> line.replace("JAR_NAME", Node.JAR_NAME) } + // Replaces end of line with lf to avoid issues with the bash interpreter and Windows style line endings. + filter(org.apache.tools.ant.filters.FixCrLfFilter.class, eol: org.apache.tools.ant.filters.FixCrLfFilter.CrLf.newInstance("lf")) + into "${directory}/" + } + } + + /** + * This task action will create and install the nodes based on the node configurations added. + */ + @TaskAction + void build() { + installRunScript() + Node networkMapNode = getNodeByName(networkMapNodeName) + nodes.each { + if(it != networkMapNode) { + it.networkMapAddress(networkMapNode.getArtemisAddress()) + } + it.build(directory.toFile()) + } + } +} + diff --git a/gradle-plugins/cordformation/src/main/groovy/com/r3corda/plugins/Cordformation.groovy b/gradle-plugins/cordformation/src/main/groovy/com/r3corda/plugins/Cordformation.groovy new file mode 100644 index 0000000000..5ebafd50e5 --- /dev/null +++ b/gradle-plugins/cordformation/src/main/groovy/com/r3corda/plugins/Cordformation.groovy @@ -0,0 +1,27 @@ +package com.r3corda.plugins + +import org.gradle.api.Plugin +import org.gradle.api.Project + +/** + * The Cordformation plugin deploys nodes to a directory in a state ready to be used by a developer for experimentation, + * testing, and debugging. It will prepopulate several fields in the configuration and create a simple node runner. + */ +class Cordformation implements Plugin { + void apply(Project project) { + + } + + /** + * Gets a resource file from this plugin's JAR file. + * + * @param project The project environment this plugin executes in. + * @param filePathInJar The file in the JAR, relative to root, you wish to access. + * @return A file handle to the file in the JAR. + */ + static File getPluginFile(Project project, String filePathInJar) { + return project.resources.text.fromArchiveEntry(project.buildscript.configurations.classpath.find { + it.name.contains('cordformation') + }, filePathInJar).asFile() + } +} diff --git a/gradle-plugins/cordformation/src/main/groovy/com/r3corda/plugins/Node.groovy b/gradle-plugins/cordformation/src/main/groovy/com/r3corda/plugins/Node.groovy new file mode 100644 index 0000000000..019c0d02ab --- /dev/null +++ b/gradle-plugins/cordformation/src/main/groovy/com/r3corda/plugins/Node.groovy @@ -0,0 +1,240 @@ +package com.r3corda.plugins + +import org.gradle.api.internal.file.AbstractFileCollection +import org.gradle.api.Project +import java.nio.file.Files +import java.nio.charset.StandardCharsets +import com.typesafe.config.Config +import com.typesafe.config.ConfigFactory +import com.typesafe.config.ConfigValueFactory +import com.typesafe.config.ConfigRenderOptions + +/** + * Represents a node that will be installed. + */ +class Node { + static final String JAR_NAME = 'corda.jar' + static final String DEFAULT_HOST = 'localhost' + + /** + * Name of the node. + */ + public String name + /** + * A list of advertised services ID strings. + */ + protected List advertisedServices = [] + /** + * Set thThe list of cordapps to install to the plugins directory. + * + * @note Your app will be installed by default and does not need to be included here. + */ + protected List cordapps = [] + + private String dirName + private Config config = ConfigFactory.empty() + //private Map config = new HashMap() + private File nodeDir + private def project + + /** + * Set the name of the node. + * + * @param name The node name. + */ + void name(String name) { + this.name = name + config = config.withValue("myLegalName", ConfigValueFactory.fromAnyRef(name)) + } + + /** + * Set the directory the node will be installed to relative to the directory specified in Cordform task. + * + * @param dirName Subdirectory name for node to be installed to. Must be valid directory name on all OSes. + */ + void dirName(String dirName) { + this.dirName = dirName + config = config.withValue("basedir", ConfigValueFactory.fromAnyRef(dirName)) + } + + /** + * Set the nearest city to the node. + * + * @param nearestCity The name of the nearest city to the node. + */ + void nearestCity(String nearestCity) { + config = config.withValue("nearestCity", ConfigValueFactory.fromAnyRef(nearestCity)) + } + + /** + * Sets whether this node will use HTTPS communication. + * + * @param isHttps True if this node uses HTTPS communication. + */ + void https(Boolean isHttps) { + config = config.withValue("useHTTPS", ConfigValueFactory.fromAnyRef(isHttps)) + } + + /** + * Set the artemis port for this node. + * + * @param artemisPort The artemis messaging queue port. + */ + void artemisPort(Integer artemisPort) { + config = config.withValue("artemisAddress", + ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$artemisPort".toString())) + } + + /** + * Set the HTTP web server port for this node. + * + * @param webPort The web port number for this node. + */ + void webPort(Integer webPort) { + config = config.withValue("webAddress", + ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$webPort".toString())) + } + + /** + * Set the network map address for this node. + * + * @warning This should not be directly set unless you know what you are doing. Use the networkMapName in the + * Cordform task instead. + * @param networkMapAddress Network map address. + */ + void networkMapAddress(String networkMapAddress) { + config = config.withValue("networkMapAddress", + ConfigValueFactory.fromAnyRef(networkMapAddress)) + } + + Node(Project project) { + this.project = project + } + + /** + * Install the nodes to the given base directory. + * + * @param baseDir The base directory for this node. All other paths are relative to it + this nodes dir name. + */ + void build(File baseDir) { + nodeDir = new File(baseDir, dirName) + installCordaJAR() + installBuiltPlugin() + installCordapps() + installDependencies() + installConfig() + } + + /** + * Get the artemis address for this node. + * + * @return This node's artemis address. + */ + String getArtemisAddress() { + return config.getString("artemisAddress") + } + + /** + * Installs the corda fat JAR to the node directory. + */ + private void installCordaJAR() { + def cordaJar = verifyAndGetCordaJar() + project.copy { + from cordaJar + into nodeDir + rename cordaJar.name, JAR_NAME + } + } + + /** + * Installs this project's cordapp to this directory. + */ + private void installBuiltPlugin() { + def pluginsDir = getAndCreateDirectory(nodeDir, "plugins") + project.copy { + from project.jar + into pluginsDir + } + } + + /** + * Installs other cordapps to this node's plugins directory. + */ + private void installCordapps() { + def pluginsDir = getAndCreateDirectory(nodeDir, "plugins") + def cordapps = getCordappList() + project.copy { + from cordapps + into pluginsDir + } + } + + /** + * Installs other dependencies to this node's dependencies directory. + */ + private void installDependencies() { + def cordaJar = verifyAndGetCordaJar() + def cordappList = getCordappList() + def depsDir = getAndCreateDirectory(nodeDir, "dependencies") + def appDeps = project.configurations.runtime.filter { it != cordaJar && !cordappList.contains(it) } + project.copy { + from appDeps + into depsDir + } + } + + /** + * Installs the configuration file to this node's directory and detokenises it. + */ + private void installConfig() { + // Adding required default values + config = config.withValue('extraAdvertisedServiceIds', + ConfigValueFactory.fromAnyRef(advertisedServices.join(','))) + + def configFileText = config.root().render(new ConfigRenderOptions(false, false, true, false)).split("\n").toList() + Files.write(new File(nodeDir, 'node.conf').toPath(), configFileText, StandardCharsets.UTF_8) + } + + /** + * Find the corda JAR amongst the dependencies. + * + * @return A file representing the Corda JAR. + */ + private File verifyAndGetCordaJar() { + def maybeCordaJAR = project.configurations.runtime.filter { it.toString().contains("corda-${project.corda_version}.jar")} + if(maybeCordaJAR.size() == 0) { + throw new RuntimeException("No Corda Capsule JAR found. Have you deployed the Corda project to Maven?") + } else { + def cordaJar = maybeCordaJAR.getSingleFile() + assert(cordaJar.isFile()) + return cordaJar + } + } + + /** + * Gets a list of cordapps based on what dependent cordapps were specified. + * + * @return List of this node's cordapps. + */ + private AbstractFileCollection getCordappList() { + def cordaJar = verifyAndGetCordaJar() + return project.configurations.runtime.filter { + def jarName = it.name.split('-').first() + return (it != cordaJar) && cordapps.contains(jarName) + } + } + + /** + * Create a directory if it doesn't exist and return the file representation of it. + * + * @param baseDir The base directory to create the directory at. + * @param subDirName A valid name of the subdirectory to get and create if not exists. + * @return A file representing the subdirectory. + */ + private static File getAndCreateDirectory(File baseDir, String subDirName) { + File dir = new File(baseDir, subDirName) + assert(!dir.exists() || dir.isDirectory()) + dir.mkdirs() + return dir + } +} diff --git a/gradle-plugins/cordformation/src/main/resources/META-INF/gradle-plugins/com.r3corda.plugins.cordformation.properties b/gradle-plugins/cordformation/src/main/resources/META-INF/gradle-plugins/com.r3corda.plugins.cordformation.properties new file mode 100644 index 0000000000..e8873b3702 --- /dev/null +++ b/gradle-plugins/cordformation/src/main/resources/META-INF/gradle-plugins/com.r3corda.plugins.cordformation.properties @@ -0,0 +1 @@ +implementation-class=com.r3corda.plugins.Cordformation \ No newline at end of file diff --git a/gradle-plugins/cordformation/src/main/resources/com/r3corda/plugins/runnodes b/gradle-plugins/cordformation/src/main/resources/com/r3corda/plugins/runnodes new file mode 100644 index 0000000000..9db806a649 --- /dev/null +++ b/gradle-plugins/cordformation/src/main/resources/com/r3corda/plugins/runnodes @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# Will attempt to execute a corda node within all subdirectories in the current working directory. +# TODO: Use screens or separate windows when starting instances. + +set -euo pipefail +trap 'kill $(jobs -p)' EXIT +export CAPSULE_CACHE_DIR=cache + +function runNode { + pushd $1 + ( java -jar JAR_NAME )& + popd +} + +for dir in `ls`; do + if [ -d $dir ]; then + runNode $dir + fi +done + +read -p 'Any key to exit' +kill $(jobs -p) diff --git a/gradle-plugins/publish-utils/README.rst b/gradle-plugins/publish-utils/README.rst new file mode 100644 index 0000000000..e75667abc9 --- /dev/null +++ b/gradle-plugins/publish-utils/README.rst @@ -0,0 +1,25 @@ +Publish Utils +============= + +Publishing utilities adds a couple of tasks to any project it is applied to that hide some boilerplate that would +otherwise be placed in the Cordapp template's build.gradle. + +There are two tasks exposed: `sourceJar` and `javadocJar` and both return a `FileCollection`. + +It is used within the `publishing` block of a build.gradle as such; + +.. code-block:: text + + // This will publish the sources, javadoc, and Java components to Maven. + // See the `maven-publish` plugin for more info: https://docs.gradle.org/current/userguide/publishing_maven.html + publishing { + publications { + jarAndSources(MavenPublication) { + from components.java + // The two lines below are the tasks added by this plugin. + artifact sourceJar + artifact javadocJar + } + } + } + diff --git a/gradle-plugins/publish-utils/build.gradle b/gradle-plugins/publish-utils/build.gradle new file mode 100644 index 0000000000..7fd9800011 --- /dev/null +++ b/gradle-plugins/publish-utils/build.gradle @@ -0,0 +1,21 @@ +apply plugin: 'maven-publish' +apply plugin: 'groovy' + +dependencies { + compile gradleApi() + compile localGroovy() +} + +repositories { + mavenCentral() +} + +publishing { + publications { + plugin(MavenPublication) { + from components.java + groupId 'com.r3corda.plugins' + artifactId 'publish-utils' + } + } +} \ No newline at end of file diff --git a/gradle-plugins/publish-utils/src/main/groovy/com/r3corda/plugins/DefaultPublishTasks.groovy b/gradle-plugins/publish-utils/src/main/groovy/com/r3corda/plugins/DefaultPublishTasks.groovy new file mode 100644 index 0000000000..6ff1c2f2db --- /dev/null +++ b/gradle-plugins/publish-utils/src/main/groovy/com/r3corda/plugins/DefaultPublishTasks.groovy @@ -0,0 +1,23 @@ +package com.r3corda.plugins + +import org.gradle.api.* +import org.gradle.api.tasks.bundling.Jar +import org.gradle.api.tasks.javadoc.Javadoc +import org.gradle.api.Project + +/** + * A utility plugin that when applied will automatically create source and javadoc publishing tasks + */ +class DefaultPublishTasks implements Plugin { + void apply(Project project) { + project.task("sourceJar", type: Jar, dependsOn: project.classes) { + classifier = 'sources' + from project.sourceSets.main.allSource + } + + project.task("javadocJar", type: Jar, dependsOn: project.javadoc) { + classifier = 'javadoc' + from project.javadoc.destinationDir + } + } +} diff --git a/gradle-plugins/publish-utils/src/main/resources/META-INF/gradle-plugins/com.r3corda.plugins.publish-utils.properties b/gradle-plugins/publish-utils/src/main/resources/META-INF/gradle-plugins/com.r3corda.plugins.publish-utils.properties new file mode 100644 index 0000000000..c8dbb68b40 --- /dev/null +++ b/gradle-plugins/publish-utils/src/main/resources/META-INF/gradle-plugins/com.r3corda.plugins.publish-utils.properties @@ -0,0 +1 @@ +implementation-class=com.r3corda.plugins.DefaultPublishTasks \ No newline at end of file diff --git a/gradle-plugins/quasar-utils/README.rst b/gradle-plugins/quasar-utils/README.rst new file mode 100644 index 0000000000..f542385326 --- /dev/null +++ b/gradle-plugins/quasar-utils/README.rst @@ -0,0 +1,16 @@ +Quasar Utils +============ + +Quasar utilities adds several tasks and configuration that provide a default Quasar setup and removes some boilerplate. +One line must be added to your build.gradle once you apply this plugin: + +.. code-block:: text + + quasarScan.dependsOn('classes') + +If any sub-projects are added that this project depends on then add the gradle target for that project to the depends +on statement. eg: + +.. code-block:: text + + quasarScan.dependsOn('classes', 'subproject:subsubproject', ...) diff --git a/gradle-plugins/quasar-utils/build.gradle b/gradle-plugins/quasar-utils/build.gradle new file mode 100644 index 0000000000..3cd1afb5cc --- /dev/null +++ b/gradle-plugins/quasar-utils/build.gradle @@ -0,0 +1,21 @@ +apply plugin: 'maven-publish' +apply plugin: 'groovy' + +dependencies { + compile gradleApi() + compile localGroovy() +} + +repositories { + mavenCentral() +} + +publishing { + publications { + plugin(MavenPublication) { + from components.java + groupId 'com.r3corda.plugins' + artifactId 'quasar-utils' + } + } +} \ No newline at end of file diff --git a/gradle-plugins/quasar-utils/src/main/groovy/com/r3corda/plugins/QuasarPlugin.groovy b/gradle-plugins/quasar-utils/src/main/groovy/com/r3corda/plugins/QuasarPlugin.groovy new file mode 100644 index 0000000000..3b9aa9e4f3 --- /dev/null +++ b/gradle-plugins/quasar-utils/src/main/groovy/com/r3corda/plugins/QuasarPlugin.groovy @@ -0,0 +1,60 @@ +package com.r3corda.plugins + +import org.gradle.api.Project +import org.gradle.api.Plugin +import org.gradle.api.tasks.testing.Test +import org.gradle.api.tasks.JavaExec + +/** + * QuasarPlugin creates a "quasar" configuration, adds quasar as a dependency and creates a "quasarScan" task that scans + * for `@Suspendable`s in the code + */ +class QuasarPlugin implements Plugin { + void apply(Project project) { + + project.repositories { + mavenCentral() + } + + project.configurations.create("quasar") +// To add a local .jar dependency: +// project.dependencies.add("quasar", project.files("${project.rootProject.projectDir}/lib/quasar.jar")) + project.dependencies.add("quasar", "co.paralleluniverse:quasar-core:${project.rootProject.ext.quasar_version}:jdk8@jar") + project.dependencies.add("compile", project.configurations.getByName("quasar")) + + project.tasks.withType(Test) { + jvmArgs "-javaagent:${project.configurations.quasar.singleFile}" + jvmArgs "-Dco.paralleluniverse.fibers.verifyInstrumentation" + } + project.tasks.withType(JavaExec) { + jvmArgs "-javaagent:${project.configurations.quasar.singleFile}" + jvmArgs "-Dco.paralleluniverse.fibers.verifyInstrumentation" + } + + project.task("quasarScan") { + inputs.files(project.sourceSets.main.output) + outputs.files( + "$project.sourceSets.main.output.resourcesDir/META-INF/suspendables", + "$project.sourceSets.main.output.resourcesDir/META-INF/suspendable-supers" + ) + } << { + + // These lines tell gradle to run the Quasar suspendables scanner to look for unannotated super methods + // that have @Suspendable sub implementations. These tend to cause NPEs and are not caught by the verifier + // NOTE: need to make sure the output isn't on the classpath or every other run it generates empty results, so + // we explicitly delete to avoid that happening. We also need to turn off what seems to be a spurious warning in the IDE + ant.taskdef(name:'scanSuspendables', classname:'co.paralleluniverse.fibers.instrument.SuspendablesScanner', + classpath: "${project.sourceSets.main.output.classesDir}:${project.sourceSets.main.output.resourcesDir}:${project.configurations.runtime.asPath}") + project.delete "$project.sourceSets.main.output.resourcesDir/META-INF/suspendables", "$project.sourceSets.main.output.resourcesDir/META-INF/suspendable-supers" + ant.scanSuspendables( + auto:false, + suspendablesFile: "$project.sourceSets.main.output.resourcesDir/META-INF/suspendables", + supersFile: "$project.sourceSets.main.output.resourcesDir/META-INF/suspendable-supers") { + fileset(dir: project.sourceSets.main.output.classesDir) + } + + } + + project.jar.dependsOn project.quasarScan + } +} diff --git a/gradle-plugins/quasar-utils/src/main/resources/META-INF/gradle-plugins/com.r3corda.plugins.quasar-utils.properties b/gradle-plugins/quasar-utils/src/main/resources/META-INF/gradle-plugins/com.r3corda.plugins.quasar-utils.properties new file mode 100644 index 0000000000..5c9be1f00b --- /dev/null +++ b/gradle-plugins/quasar-utils/src/main/resources/META-INF/gradle-plugins/com.r3corda.plugins.quasar-utils.properties @@ -0,0 +1 @@ +implementation-class=com.r3corda.plugins.QuasarPlugin \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 7b1a89a8ab..6abd5e0d6c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,4 +8,7 @@ include 'experimental' include 'test-utils' include 'network-simulator' include 'explorer' +include 'gradle-plugins:quasar-utils' +include 'gradle-plugins:publish-utils' +include 'gradle-plugins:cordformation' include 'docs/source/example-code' diff --git a/src/main/java/CordaCaplet.java b/src/main/java/CordaCaplet.java index 9f734bc968..8cdf47f5d8 100644 --- a/src/main/java/CordaCaplet.java +++ b/src/main/java/CordaCaplet.java @@ -25,15 +25,15 @@ public class CordaCaplet extends Capsule { // defined as public static final fields on the Capsule class, therefore referential equality is safe. if(ATTR_APP_CLASS_PATH == attr) { T cp = super.attribute(attr); - List classpath = (List) cp; - return (T) augmentClasspath(classpath); + List classpath = augmentClasspath((List) cp, "plugins"); + return (T) augmentClasspath(classpath, "dependencies"); } return super.attribute(attr); } // TODO: Make directory configurable via the capsule manifest. // TODO: Add working directory variable to capsules string replacement variables. - private List augmentClasspath(List classpath) { + private List augmentClasspath(List classpath, String dirName) { File dir = new File("plugins"); if(!dir.exists()) { dir.mkdir();