mirror of
https://github.com/corda/corda.git
synced 2025-02-03 01:31:24 +00:00
parent
12fa945077
commit
3dd09fd69b
@ -1,4 +1,4 @@
|
||||
gradlePluginsVersion=2.0.4
|
||||
gradlePluginsVersion=2.0.5
|
||||
kotlinVersion=1.1.50
|
||||
guavaVersion=21.0
|
||||
bouncycastleVersion=1.57
|
||||
|
@ -1,7 +1,6 @@
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'application'
|
||||
apply plugin: 'net.corda.plugins.cordformation'
|
||||
apply plugin: 'net.corda.plugins.cordapp'
|
||||
apply plugin: 'net.corda.plugins.quasar-utils'
|
||||
|
||||
repositories {
|
||||
|
@ -29,7 +29,7 @@ class CordappPlugin : Plugin<Project> {
|
||||
private fun configureCordappJar(project: Project) {
|
||||
// Note: project.afterEvaluate did not have full dependency resolution completed, hence a task is used instead
|
||||
val task = project.task("configureCordappFatJar")
|
||||
val jarTask = project.tasks.single { it.name == "jar" } as Jar
|
||||
val jarTask = project.tasks.getByName("jar") as Jar
|
||||
task.doLast {
|
||||
jarTask.from(getDirectNonCordaDependencies(project).map { project.zipTree(it)}).apply {
|
||||
exclude("META-INF/*.SF")
|
||||
@ -71,6 +71,4 @@ class CordappPlugin : Plugin<Project> {
|
||||
}
|
||||
return filteredDeps.map { runtimeConfiguration.files(it) }.flatten().toSet()
|
||||
}
|
||||
|
||||
private fun Project.configuration(name: String): Configuration = configurations.single { it.name == name }
|
||||
}
|
||||
|
@ -1,7 +1,17 @@
|
||||
package net.corda.plugins
|
||||
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.Task
|
||||
import org.gradle.api.artifacts.Configuration
|
||||
import org.gradle.api.plugins.ExtraPropertiesExtension
|
||||
|
||||
/**
|
||||
* Mimics the "project.ext" functionality in groovy which provides a direct
|
||||
* accessor to the "ext" extention (See: ExtraPropertiesExtension)
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T : Any> Project.ext(name: String): T = (extensions.findByName("ext") as ExtraPropertiesExtension).get(name) as T
|
||||
fun Project.configuration(name: String): Configuration = configurations.single { it.name == name }
|
||||
|
||||
class Utils {
|
||||
companion object {
|
||||
@ -14,4 +24,5 @@ class Utils {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -8,7 +8,6 @@ buildscript {
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'groovy'
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'net.corda.plugins.publish-utils'
|
||||
|
||||
@ -34,8 +33,8 @@ sourceSets {
|
||||
|
||||
dependencies {
|
||||
compile gradleApi()
|
||||
compile localGroovy()
|
||||
compile project(":cordapp")
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
||||
|
||||
noderunner "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
|
||||
|
||||
|
@ -1,153 +0,0 @@
|
||||
package net.corda.plugins
|
||||
|
||||
import static org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME
|
||||
import net.corda.cordform.CordformContext
|
||||
import net.corda.cordform.CordformDefinition
|
||||
import org.apache.tools.ant.filters.FixCrLfFilter
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.plugins.JavaPluginConvention
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
|
||||
/**
|
||||
* Creates nodes based on the configuration of this task in the gradle configuration DSL.
|
||||
*
|
||||
* See documentation for examples.
|
||||
*/
|
||||
class Cordform extends DefaultTask {
|
||||
/**
|
||||
* Optionally the name of a CordformDefinition subclass to which all configuration will be delegated.
|
||||
*/
|
||||
String definitionClass
|
||||
protected def directory = Paths.get("build", "nodes")
|
||||
private def nodes = new ArrayList<Node>()
|
||||
|
||||
/**
|
||||
* Set the directory to install nodes into.
|
||||
*
|
||||
* @param directory The directory the nodes will be installed into.
|
||||
* @return
|
||||
*/
|
||||
void directory(String directory) {
|
||||
this.directory = Paths.get(directory)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a node configuration.
|
||||
*
|
||||
* @param configureClosure A node configuration that will be deployed.
|
||||
*/
|
||||
void node(Closure configureClosure) {
|
||||
nodes << (Node) 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.
|
||||
*/
|
||||
private Node getNodeByName(String name) {
|
||||
for (Node node : nodes) {
|
||||
if (node.name == name) {
|
||||
return node
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs the run script into the nodes directory.
|
||||
*/
|
||||
private void installRunScript() {
|
||||
project.copy {
|
||||
from Cordformation.getPluginFile(project, "net/corda/plugins/runnodes.jar")
|
||||
fileMode 0755
|
||||
into "${directory}/"
|
||||
}
|
||||
|
||||
project.copy {
|
||||
from Cordformation.getPluginFile(project, "net/corda/plugins/runnodes")
|
||||
// Replaces end of line with lf to avoid issues with the bash interpreter and Windows style line endings.
|
||||
filter(FixCrLfFilter.class, eol: FixCrLfFilter.CrLf.newInstance("lf"))
|
||||
fileMode 0755
|
||||
into "${directory}/"
|
||||
}
|
||||
|
||||
project.copy {
|
||||
from Cordformation.getPluginFile(project, "net/corda/plugins/runnodes.bat")
|
||||
into "${directory}/"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The definitionClass needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath.
|
||||
*/
|
||||
private CordformDefinition loadCordformDefinition() {
|
||||
def plugin = project.convention.getPlugin(JavaPluginConvention.class)
|
||||
def classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath
|
||||
URL[] urls = classpath.files.collect { it.toURI().toURL() }
|
||||
(CordformDefinition) new URLClassLoader(urls, CordformDefinition.classLoader).loadClass(definitionClass).newInstance()
|
||||
}
|
||||
|
||||
/**
|
||||
* This task action will create and install the nodes based on the node configurations added.
|
||||
*/
|
||||
@TaskAction
|
||||
void build() {
|
||||
initializeConfiguration()
|
||||
installRunScript()
|
||||
nodes.each {
|
||||
it.build()
|
||||
}
|
||||
generateNodeInfos()
|
||||
}
|
||||
|
||||
private initializeConfiguration() {
|
||||
if (null != definitionClass) {
|
||||
def cd = loadCordformDefinition()
|
||||
cd.nodeConfigurers.each { nc ->
|
||||
node { Node it ->
|
||||
nc.accept it
|
||||
it.rootDir directory
|
||||
}
|
||||
}
|
||||
cd.setup new CordformContext() {
|
||||
Path baseDirectory(String nodeName) {
|
||||
project.projectDir.toPath().resolve(getNodeByName(nodeName).nodeDir.toPath())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
nodes.each {
|
||||
it.rootDir directory
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Path fullNodePath(Node node) {
|
||||
return project.projectDir.toPath().resolve(node.nodeDir.toPath())
|
||||
}
|
||||
|
||||
private generateNodeInfos() {
|
||||
nodes.each { Node node ->
|
||||
def process = new ProcessBuilder("java", "-jar", Node.NODEJAR_NAME, "--just-generate-node-info")
|
||||
.directory(fullNodePath(node).toFile())
|
||||
.redirectErrorStream(true)
|
||||
.start()
|
||||
.waitFor()
|
||||
}
|
||||
for (source in nodes) {
|
||||
for (destination in nodes) {
|
||||
if (source.nodeDir != destination.nodeDir) {
|
||||
project.copy {
|
||||
from fullNodePath(source).toString()
|
||||
include 'nodeInfo-*'
|
||||
into fullNodePath(destination).resolve(Node.NODE_INFO_DIRECTORY).toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
package net.corda.plugins
|
||||
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.artifacts.Configuration
|
||||
|
||||
/**
|
||||
* 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<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.
|
||||
*/
|
||||
protected static File getPluginFile(Project project, String filePathInJar) {
|
||||
return project.rootProject.resources.text.fromArchiveEntry(project.rootProject.buildscript.configurations.classpath.find {
|
||||
it.name.contains('cordformation')
|
||||
}, filePathInJar).asFile()
|
||||
}
|
||||
|
||||
void apply(Project project) {
|
||||
Utils.createCompileConfiguration("cordapp", project)
|
||||
}
|
||||
}
|
@ -1,268 +0,0 @@
|
||||
package net.corda.plugins
|
||||
|
||||
import com.typesafe.config.*
|
||||
import net.corda.cordform.CordformNode
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x500.style.BCStyle
|
||||
import org.gradle.api.Project
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* Represents a node that will be installed.
|
||||
*/
|
||||
class Node extends CordformNode {
|
||||
static final String NODEJAR_NAME = 'corda.jar'
|
||||
static final String WEBJAR_NAME = 'corda-webserver.jar'
|
||||
|
||||
/**
|
||||
* Set the list of CorDapps to install to the cordapps directory. Each cordapp is a fully qualified Maven
|
||||
* dependency name, eg: com.example:product-name:0.1
|
||||
*
|
||||
* @note Your app will be installed by default and does not need to be included here.
|
||||
*/
|
||||
protected List<String> cordapps = []
|
||||
|
||||
protected File nodeDir
|
||||
private Project project
|
||||
|
||||
/**
|
||||
* 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))
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the H2 port for this node
|
||||
*/
|
||||
void h2Port(Integer h2Port) {
|
||||
config = config.withValue("h2port", ConfigValueFactory.fromAnyRef(h2Port))
|
||||
}
|
||||
|
||||
void useTestClock(Boolean useTestClock) {
|
||||
config = config.withValue("useTestClock", ConfigValueFactory.fromAnyRef(useTestClock))
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 node address.
|
||||
* @param networkMapLegalName Network map node legal name.
|
||||
*/
|
||||
void networkMapAddress(String networkMapAddress, String networkMapLegalName) {
|
||||
def networkMapService = new HashMap()
|
||||
networkMapService.put("address", networkMapAddress)
|
||||
networkMapService.put("legalName", networkMapLegalName)
|
||||
config = config.withValue("networkMapService", ConfigValueFactory.fromMap(networkMapService))
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the SSHD port for this node.
|
||||
*
|
||||
* @param sshdPort The SSHD port.
|
||||
*/
|
||||
void sshdPort(Integer sshdPort) {
|
||||
config = config.withValue("sshdAddress",
|
||||
ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$sshdPort".toString()))
|
||||
}
|
||||
|
||||
Node(Project project) {
|
||||
this.project = project
|
||||
}
|
||||
|
||||
protected void rootDir(Path rootDir) {
|
||||
def dirName
|
||||
try {
|
||||
X500Name x500Name = new X500Name(name)
|
||||
dirName = x500Name.getRDNs(BCStyle.O).getAt(0).getFirst().getValue().toString()
|
||||
} catch(IllegalArgumentException ignore) {
|
||||
// Can't parse as an X500 name, use the full string
|
||||
dirName = name
|
||||
}
|
||||
nodeDir = new File(rootDir.toFile(), dirName.replaceAll("\\s",""))
|
||||
}
|
||||
|
||||
protected void build() {
|
||||
configureProperties()
|
||||
installCordaJar()
|
||||
if (config.hasPath("webAddress")) {
|
||||
installWebserverJar()
|
||||
}
|
||||
installBuiltCordapp()
|
||||
installCordapps()
|
||||
installConfig()
|
||||
appendOptionalConfig()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the artemis address for this node.
|
||||
*
|
||||
* @return This node's P2P address.
|
||||
*/
|
||||
String getP2PAddress() {
|
||||
return config.getString("p2pAddress")
|
||||
}
|
||||
|
||||
private void configureProperties() {
|
||||
config = config.withValue("rpcUsers", ConfigValueFactory.fromIterable(rpcUsers))
|
||||
if (notary) {
|
||||
config = config.withValue("notary", ConfigValueFactory.fromMap(notary))
|
||||
}
|
||||
if (extraConfig) {
|
||||
config = config.withFallback(ConfigFactory.parseMap(extraConfig))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs the corda fat JAR to the node directory.
|
||||
*/
|
||||
private void installCordaJar() {
|
||||
def cordaJar = verifyAndGetCordaJar()
|
||||
project.copy {
|
||||
from cordaJar
|
||||
into nodeDir
|
||||
rename cordaJar.name, NODEJAR_NAME
|
||||
fileMode 0755
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs the corda webserver JAR to the node directory
|
||||
*/
|
||||
private void installWebserverJar() {
|
||||
def webJar = verifyAndGetWebserverJar()
|
||||
project.copy {
|
||||
from webJar
|
||||
into nodeDir
|
||||
rename webJar.name, WEBJAR_NAME
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs this project's cordapp to this directory.
|
||||
*/
|
||||
private void installBuiltCordapp() {
|
||||
def cordappsDir = new File(nodeDir, "cordapps")
|
||||
project.copy {
|
||||
from project.jar
|
||||
into cordappsDir
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs other cordapps to this node's cordapps directory.
|
||||
*/
|
||||
private void installCordapps() {
|
||||
def cordappsDir = new File(nodeDir, "cordapps")
|
||||
def cordapps = getCordappList()
|
||||
project.copy {
|
||||
from cordapps
|
||||
into cordappsDir
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs the configuration file to this node's directory and detokenises it.
|
||||
*/
|
||||
private void installConfig() {
|
||||
def configFileText = config.root().render(new ConfigRenderOptions(false, false, true, false)).split("\n").toList()
|
||||
|
||||
// Need to write a temporary file first to use the project.copy, which resolves directories correctly.
|
||||
def tmpDir = new File(project.buildDir, "tmp")
|
||||
def tmpConfFile = new File(tmpDir, 'node.conf')
|
||||
Files.write(tmpConfFile.toPath(), configFileText, StandardCharsets.UTF_8)
|
||||
|
||||
project.copy {
|
||||
from tmpConfFile
|
||||
into nodeDir
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends installed config file with properties from an optional file.
|
||||
*/
|
||||
private void appendOptionalConfig() {
|
||||
final configFileProperty = "configFile"
|
||||
File optionalConfig
|
||||
if (project.findProperty(configFileProperty)) { //provided by -PconfigFile command line property when running Gradle task
|
||||
optionalConfig = new File(project.findProperty(configFileProperty))
|
||||
} else if (config.hasPath(configFileProperty)) {
|
||||
optionalConfig = new File(config.getString(configFileProperty))
|
||||
}
|
||||
if (optionalConfig) {
|
||||
if (!optionalConfig.exists()) {
|
||||
println "$configFileProperty '$optionalConfig' not found"
|
||||
} else {
|
||||
def confFile = new File(project.buildDir.getPath() + "/../" + nodeDir, 'node.conf')
|
||||
optionalConfig.withInputStream {
|
||||
input -> confFile << input
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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_release_version}.jar") || it.toString().contains("corda-enterprise-${project.corda_release_version}.jar")
|
||||
}
|
||||
if (maybeCordaJAR.size() == 0) {
|
||||
throw new RuntimeException("No Corda Capsule JAR found. Have you deployed the Corda project to Maven? Looked for \"corda-${project.corda_release_version}.jar\"")
|
||||
} else {
|
||||
def cordaJar = maybeCordaJAR.getSingleFile()
|
||||
assert(cordaJar.isFile())
|
||||
return cordaJar
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the corda JAR amongst the dependencies
|
||||
*
|
||||
* @return A file representing the Corda webserver JAR
|
||||
*/
|
||||
private File verifyAndGetWebserverJar() {
|
||||
def maybeJar = project.configurations.runtime.filter {
|
||||
it.toString().contains("corda-webserver-${project.corda_release_version}.jar")
|
||||
}
|
||||
if (maybeJar.size() == 0) {
|
||||
throw new RuntimeException("No Corda Webserver JAR found. Have you deployed the Corda project to Maven? Looked for \"corda-webserver-${project.corda_release_version}.jar\"")
|
||||
} else {
|
||||
def jar = maybeJar.getSingleFile()
|
||||
assert(jar.isFile())
|
||||
return jar
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of cordapps based on what dependent cordapps were specified.
|
||||
*
|
||||
* @return List of this node's cordapps.
|
||||
*/
|
||||
private Collection<File> getCordappList() {
|
||||
// Cordapps can sometimes contain a GString instance which fails the equality test with the Java string
|
||||
List<String> cordapps = this.cordapps.collect { it.toString() }
|
||||
return project.configurations.cordapp.files {
|
||||
cordapps.contains(it.group + ":" + it.name + ":" + it.version)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,198 @@
|
||||
package net.corda.plugins
|
||||
|
||||
import groovy.lang.Closure
|
||||
import net.corda.cordform.CordformDefinition
|
||||
import net.corda.cordform.CordformNode
|
||||
import org.apache.tools.ant.filters.FixCrLfFilter
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.plugins.JavaPluginConvention
|
||||
import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Creates nodes based on the configuration of this task in the gradle configuration DSL.
|
||||
*
|
||||
* See documentation for examples.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
open class Cordform : DefaultTask() {
|
||||
/**
|
||||
* Optionally the name of a CordformDefinition subclass to which all configuration will be delegated.
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanPrivate")
|
||||
var definitionClass: String? = null
|
||||
private var directory = Paths.get("build", "nodes")
|
||||
private val nodes = mutableListOf<Node>()
|
||||
|
||||
/**
|
||||
* Set the directory to install nodes into.
|
||||
*
|
||||
* @param directory The directory the nodes will be installed into.
|
||||
*/
|
||||
fun directory(directory: String) {
|
||||
this.directory = Paths.get(directory)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a node configuration.
|
||||
*
|
||||
* @param configureClosure A node configuration that will be deployed.
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanPrivate")
|
||||
fun node(configureClosure: Closure<in Node>) {
|
||||
nodes += project.configure(Node(project), configureClosure) as Node
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a node configuration
|
||||
*
|
||||
* @param configureFunc A node configuration that will be deployed
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanPrivate")
|
||||
fun node(configureFunc: Node.() -> Any?): Node {
|
||||
val node = Node(project).apply { configureFunc() }
|
||||
nodes += node
|
||||
return node
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a node by name.
|
||||
*
|
||||
* @param name The name of the node as specified in the node configuration DSL.
|
||||
* @return A node instance.
|
||||
*/
|
||||
private fun getNodeByName(name: String): Node? = nodes.firstOrNull { it.name == name }
|
||||
|
||||
/**
|
||||
* Installs the run script into the nodes directory.
|
||||
*/
|
||||
private fun installRunScript() {
|
||||
project.copy {
|
||||
it.apply {
|
||||
from(Cordformation.getPluginFile(project, "net/corda/plugins/runnodes.jar"))
|
||||
fileMode = Cordformation.executableFileMode
|
||||
into("$directory/")
|
||||
}
|
||||
}
|
||||
|
||||
project.copy {
|
||||
it.apply {
|
||||
from(Cordformation.getPluginFile(project, "net/corda/plugins/runnodes"))
|
||||
// Replaces end of line with lf to avoid issues with the bash interpreter and Windows style line endings.
|
||||
filter(mapOf("eol" to FixCrLfFilter.CrLf.newInstance("lf")), FixCrLfFilter::class.java)
|
||||
fileMode = Cordformation.executableFileMode
|
||||
into("$directory/")
|
||||
}
|
||||
}
|
||||
|
||||
project.copy {
|
||||
it.apply {
|
||||
from(Cordformation.getPluginFile(project, "net/corda/plugins/runnodes.bat"))
|
||||
into("$directory/")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The definitionClass needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath.
|
||||
*/
|
||||
private fun loadCordformDefinition(): CordformDefinition {
|
||||
val plugin = project.convention.getPlugin(JavaPluginConvention::class.java)
|
||||
val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath
|
||||
val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray()
|
||||
return URLClassLoader(urls, CordformDefinition::class.java.classLoader)
|
||||
.loadClass(definitionClass)
|
||||
.asSubclass(CordformDefinition::class.java)
|
||||
.newInstance()
|
||||
}
|
||||
|
||||
/**
|
||||
* This task action will create and install the nodes based on the node configurations added.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
@TaskAction
|
||||
fun build() {
|
||||
project.logger.info("Running Cordform task")
|
||||
initializeConfiguration()
|
||||
installRunScript()
|
||||
nodes.forEach(Node::build)
|
||||
generateAndInstallNodeInfos()
|
||||
}
|
||||
|
||||
private fun initializeConfiguration() {
|
||||
if (definitionClass != null) {
|
||||
val cd = loadCordformDefinition()
|
||||
cd.nodeConfigurers.forEach {
|
||||
val node = node { }
|
||||
it.accept(node)
|
||||
node.rootDir(directory)
|
||||
}
|
||||
cd.setup { nodeName -> project.projectDir.toPath().resolve(getNodeByName(nodeName)!!.nodeDir.toPath()) }
|
||||
} else {
|
||||
nodes.forEach {
|
||||
it.rootDir(directory)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun fullNodePath(node: Node): Path = project.projectDir.toPath().resolve(node.nodeDir.toPath())
|
||||
|
||||
private fun generateAndInstallNodeInfos() {
|
||||
generateNodeInfos()
|
||||
installNodeInfos()
|
||||
}
|
||||
|
||||
private fun generateNodeInfos() {
|
||||
project.logger.info("Generating node infos")
|
||||
val generateTimeoutSeconds = 60L
|
||||
val processes = nodes.map { node ->
|
||||
project.logger.info("Generating node info for ${fullNodePath(node)}")
|
||||
val logDir = File(fullNodePath(node).toFile(), "logs")
|
||||
logDir.mkdirs() // Directory may not exist at this point
|
||||
Pair(node, ProcessBuilder("java", "-jar", Node.nodeJarName, "--just-generate-node-info")
|
||||
.directory(fullNodePath(node).toFile())
|
||||
.redirectErrorStream(true)
|
||||
// InheritIO causes hangs on windows due the gradle buffer also not being flushed.
|
||||
// Must redirect to output or logger (node log is still written, this is just startup banner)
|
||||
.redirectOutput(File(logDir, "generate-info-log.txt"))
|
||||
.start())
|
||||
}
|
||||
try {
|
||||
processes.parallelStream().forEach { (node, process) ->
|
||||
if (!process.waitFor(generateTimeoutSeconds, TimeUnit.SECONDS)) {
|
||||
throw GradleException("Node took longer $generateTimeoutSeconds seconds than too to generate node info - see node log at ${fullNodePath(node)}/logs")
|
||||
} else if (process.exitValue() != 0) {
|
||||
throw GradleException("Node exited with ${process.exitValue()} when generating node infos - see node log at ${fullNodePath(node)}/logs")
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// This will be a no-op on success - abort remaining on failure
|
||||
processes.forEach {
|
||||
it.second.destroyForcibly()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun installNodeInfos() {
|
||||
project.logger.info("Installing node infos")
|
||||
for (source in nodes) {
|
||||
for (destination in nodes) {
|
||||
if (source.nodeDir != destination.nodeDir) {
|
||||
project.copy {
|
||||
it.apply {
|
||||
from(fullNodePath(source).toString())
|
||||
include("nodeInfo-*")
|
||||
into(fullNodePath(destination).resolve(CordformNode.NODE_INFO_DIRECTORY).toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package net.corda.plugins
|
||||
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* 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 : Plugin<Project> {
|
||||
internal companion object {
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
fun getPluginFile(project: Project, filePathInJar: String): File {
|
||||
val archive: File? = project.rootProject.buildscript.configurations.single { it.name == "classpath" }.find {
|
||||
it.name.contains("cordformation")
|
||||
}
|
||||
return project.rootProject.resources.text.fromArchiveEntry(archive, filePathInJar).asFile()
|
||||
}
|
||||
|
||||
val executableFileMode = "0755".toInt(8)
|
||||
}
|
||||
|
||||
override fun apply(project: Project) {
|
||||
Utils.createCompileConfiguration("cordapp", project)
|
||||
}
|
||||
}
|
@ -0,0 +1,284 @@
|
||||
package net.corda.plugins
|
||||
|
||||
import com.typesafe.config.*
|
||||
import net.corda.cordform.CordformNode
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x500.style.BCStyle
|
||||
import org.gradle.api.Project
|
||||
import java.io.File
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* Represents a node that will be installed.
|
||||
*/
|
||||
class Node(private val project: Project) : CordformNode() {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
val nodeJarName = "corda.jar"
|
||||
@JvmStatic
|
||||
val webJarName = "corda-webserver.jar"
|
||||
private val configFileProperty = "configFile"
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list of CorDapps to install to the plugins directory. Each cordapp is a fully qualified Maven
|
||||
* dependency name, eg: com.example:product-name:0.1
|
||||
*
|
||||
* @note Your app will be installed by default and does not need to be included here.
|
||||
* @note Type is any due to gradle's use of "GStrings" - each value will have "toString" called on it
|
||||
*/
|
||||
var cordapps = mutableListOf<Any>()
|
||||
|
||||
private val releaseVersion = project.rootProject.ext<String>("corda_release_version")
|
||||
internal lateinit var nodeDir: File
|
||||
|
||||
/**
|
||||
* Sets whether this node will use HTTPS communication.
|
||||
*
|
||||
* @param isHttps True if this node uses HTTPS communication.
|
||||
*/
|
||||
fun https(isHttps: Boolean) {
|
||||
config = config.withValue("useHTTPS", ConfigValueFactory.fromAnyRef(isHttps))
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the H2 port for this node
|
||||
*/
|
||||
fun h2Port(h2Port: Int) {
|
||||
config = config.withValue("h2port", ConfigValueFactory.fromAnyRef(h2Port))
|
||||
}
|
||||
|
||||
fun useTestClock(useTestClock: Boolean) {
|
||||
config = config.withValue("useTestClock", ConfigValueFactory.fromAnyRef(useTestClock))
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the HTTP web server port for this node.
|
||||
*
|
||||
* @param webPort The web port number for this node.
|
||||
*/
|
||||
fun webPort(webPort: Int) {
|
||||
config = config.withValue("webAddress",
|
||||
ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$webPort"))
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 node address.
|
||||
* @param networkMapLegalName Network map node legal name.
|
||||
*/
|
||||
fun networkMapAddress(networkMapAddress: String, networkMapLegalName: String) {
|
||||
val networkMapService = mutableMapOf<String, String>()
|
||||
networkMapService.put("address", networkMapAddress)
|
||||
networkMapService.put("legalName", networkMapLegalName)
|
||||
config = config.withValue("networkMapService", ConfigValueFactory.fromMap(networkMapService))
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the SSHD port for this node.
|
||||
*
|
||||
* @param sshdPort The SSHD port.
|
||||
*/
|
||||
fun sshdPort(sshdPort: Int) {
|
||||
config = config.withValue("sshdAddress",
|
||||
ConfigValueFactory.fromAnyRef("$DEFAULT_HOST:$sshdPort"))
|
||||
}
|
||||
|
||||
internal fun build() {
|
||||
configureProperties()
|
||||
installCordaJar()
|
||||
if (config.hasPath("webAddress")) {
|
||||
installWebserverJar()
|
||||
}
|
||||
installBuiltCordapp()
|
||||
installCordapps()
|
||||
installConfig()
|
||||
appendOptionalConfig()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the artemis address for this node.
|
||||
*
|
||||
* @return This node's P2P address.
|
||||
*/
|
||||
fun getP2PAddress(): String {
|
||||
return config.getString("p2pAddress")
|
||||
}
|
||||
|
||||
internal fun rootDir(rootDir: Path) {
|
||||
if(name == null) {
|
||||
project.logger.error("Node has a null name - cannot create node")
|
||||
throw IllegalStateException("Node has a null name - cannot create node")
|
||||
}
|
||||
|
||||
val dirName = try {
|
||||
X500Name(name).getRDNs(BCStyle.O).first().first.value.toString()
|
||||
} catch(_ : IllegalArgumentException) {
|
||||
// Can't parse as an X500 name, use the full string
|
||||
name
|
||||
}
|
||||
nodeDir = File(rootDir.toFile(), dirName.replace("\\s", ""))
|
||||
}
|
||||
|
||||
private fun configureProperties() {
|
||||
config = config.withValue("rpcUsers", ConfigValueFactory.fromIterable(rpcUsers))
|
||||
if (notary != null) {
|
||||
config = config.withValue("notary", ConfigValueFactory.fromMap(notary))
|
||||
}
|
||||
if (extraConfig != null) {
|
||||
config = config.withFallback(ConfigFactory.parseMap(extraConfig))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs the corda fat JAR to the node directory.
|
||||
*/
|
||||
private fun installCordaJar() {
|
||||
val cordaJar = verifyAndGetCordaJar()
|
||||
project.copy {
|
||||
it.apply {
|
||||
from(cordaJar)
|
||||
into(nodeDir)
|
||||
rename(cordaJar.name, nodeJarName)
|
||||
fileMode = Cordformation.executableFileMode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs the corda webserver JAR to the node directory
|
||||
*/
|
||||
private fun installWebserverJar() {
|
||||
val webJar = verifyAndGetWebserverJar()
|
||||
project.copy {
|
||||
it.apply {
|
||||
from(webJar)
|
||||
into(nodeDir)
|
||||
rename(webJar.name, webJarName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs this project's cordapp to this directory.
|
||||
*/
|
||||
private fun installBuiltCordapp() {
|
||||
val cordappsDir = File(nodeDir, "cordapps")
|
||||
project.copy {
|
||||
it.apply {
|
||||
from(project.tasks.getByName("jar"))
|
||||
into(cordappsDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs other cordapps to this node's cordapps directory.
|
||||
*/
|
||||
private fun installCordapps() {
|
||||
val cordappsDir = File(nodeDir, "cordapps")
|
||||
val cordapps = getCordappList()
|
||||
project.copy {
|
||||
it.apply {
|
||||
from(cordapps)
|
||||
into(cordappsDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs the configuration file to this node's directory and detokenises it.
|
||||
*/
|
||||
private fun installConfig() {
|
||||
val options = ConfigRenderOptions.defaults().setOriginComments(false).setComments(false).setFormatted(false).setJson(false)
|
||||
val configFileText = config.root().render(options).split("\n").toList()
|
||||
|
||||
// Need to write a temporary file first to use the project.copy, which resolves directories correctly.
|
||||
val tmpDir = File(project.buildDir, "tmp")
|
||||
val tmpConfFile = File(tmpDir, "node.conf")
|
||||
Files.write(tmpConfFile.toPath(), configFileText, StandardCharsets.UTF_8)
|
||||
|
||||
project.copy {
|
||||
it.apply {
|
||||
from(tmpConfFile)
|
||||
into(nodeDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends installed config file with properties from an optional file.
|
||||
*/
|
||||
private fun appendOptionalConfig() {
|
||||
val optionalConfig: File? = when {
|
||||
project.findProperty(configFileProperty) != null -> //provided by -PconfigFile command line property when running Gradle task
|
||||
File(project.findProperty(configFileProperty) as String)
|
||||
config.hasPath(configFileProperty) -> File(config.getString(configFileProperty))
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (optionalConfig != null) {
|
||||
if (!optionalConfig.exists()) {
|
||||
project.logger.error("$configFileProperty '$optionalConfig' not found")
|
||||
} else {
|
||||
val confFile = File(project.buildDir.path + "/../" + nodeDir, "node.conf")
|
||||
confFile.appendBytes(optionalConfig.readBytes())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the corda JAR amongst the dependencies.
|
||||
*
|
||||
* @return A file representing the Corda JAR.
|
||||
*/
|
||||
private fun verifyAndGetCordaJar(): File {
|
||||
val maybeCordaJAR = project.configuration("runtime").filter {
|
||||
it.toString().contains("corda-$releaseVersion.jar") || it.toString().contains("corda-enterprise-$releaseVersion.jar")
|
||||
}
|
||||
if (maybeCordaJAR.isEmpty) {
|
||||
throw RuntimeException("No Corda Capsule JAR found. Have you deployed the Corda project to Maven? Looked for \"corda-$releaseVersion.jar\"")
|
||||
} else {
|
||||
val cordaJar = maybeCordaJAR.singleFile
|
||||
assert(cordaJar.isFile)
|
||||
return cordaJar
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the corda JAR amongst the dependencies
|
||||
*
|
||||
* @return A file representing the Corda webserver JAR
|
||||
*/
|
||||
private fun verifyAndGetWebserverJar(): File {
|
||||
val maybeJar = project.configuration("runtime").filter {
|
||||
it.toString().contains("corda-webserver-$releaseVersion.jar")
|
||||
}
|
||||
if (maybeJar.isEmpty) {
|
||||
throw RuntimeException("No Corda Webserver JAR found. Have you deployed the Corda project to Maven? Looked for \"corda-webserver-$releaseVersion.jar\"")
|
||||
} else {
|
||||
val jar = maybeJar.singleFile
|
||||
assert(jar.isFile)
|
||||
return jar
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of cordapps based on what dependent cordapps were specified.
|
||||
*
|
||||
* @return List of this node's cordapps.
|
||||
*/
|
||||
private fun getCordappList(): Collection<File> {
|
||||
// Cordapps can sometimes contain a GString instance which fails the equality test with the Java string
|
||||
@Suppress("RemoveRedundantCallsOfConversionMethods")
|
||||
val cordapps: List<String> = cordapps.map { it.toString() }
|
||||
return project.configuration("cordapp").files {
|
||||
cordapps.contains(it.group + ":" + it.name + ":" + it.version)
|
||||
}
|
||||
}
|
||||
}
|
@ -80,6 +80,7 @@ open class NodeStartup(val args: Array<String>) {
|
||||
exitProcess(1)
|
||||
}
|
||||
|
||||
logger.info("Node exiting successfully")
|
||||
exitProcess(0)
|
||||
}
|
||||
|
||||
|
@ -19,14 +19,14 @@ import net.corda.testing.internal.demorun.notary
|
||||
import net.corda.testing.internal.demorun.rpcUsers
|
||||
import net.corda.testing.internal.demorun.runNodes
|
||||
|
||||
fun main(args: Array<String>) = BFTNotaryCordform.runNodes()
|
||||
fun main(args: Array<String>) = BFTNotaryCordform().runNodes()
|
||||
|
||||
private val clusterSize = 4 // Minimum size that tolerates a faulty replica.
|
||||
private val notaryNames = createNotaryNames(clusterSize)
|
||||
|
||||
// This is not the intended final design for how to use CordformDefinition, please treat this as experimental and DO
|
||||
// NOT use this as a design to copy.
|
||||
object BFTNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") {
|
||||
class BFTNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") {
|
||||
private val clusterName = CordaX500Name(BFTNonValidatingNotaryService.id, "BFT", "Zurich", "CH")
|
||||
|
||||
init {
|
||||
|
@ -3,7 +3,7 @@ package net.corda.notarydemo
|
||||
import net.corda.testing.internal.demorun.clean
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
listOf(SingleNotaryCordform, RaftNotaryCordform, BFTNotaryCordform).forEach {
|
||||
listOf(SingleNotaryCordform(), RaftNotaryCordform(), BFTNotaryCordform()).forEach {
|
||||
it.clean()
|
||||
}
|
||||
}
|
||||
|
@ -9,9 +9,9 @@ import net.corda.testing.BOB
|
||||
import net.corda.testing.DUMMY_NOTARY
|
||||
import net.corda.testing.internal.demorun.*
|
||||
|
||||
fun main(args: Array<String>) = CustomNotaryCordform.runNodes()
|
||||
fun main(args: Array<String>) = CustomNotaryCordform().runNodes()
|
||||
|
||||
object CustomNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") {
|
||||
class CustomNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") {
|
||||
init {
|
||||
node {
|
||||
name(ALICE.name)
|
||||
|
@ -14,7 +14,7 @@ import net.corda.testing.ALICE
|
||||
import net.corda.testing.BOB
|
||||
import net.corda.testing.internal.demorun.*
|
||||
|
||||
fun main(args: Array<String>) = RaftNotaryCordform.runNodes()
|
||||
fun main(args: Array<String>) = RaftNotaryCordform().runNodes()
|
||||
|
||||
internal fun createNotaryNames(clusterSize: Int) = (0 until clusterSize).map { CordaX500Name("Notary Service $it", "Zurich", "CH") }
|
||||
|
||||
@ -22,7 +22,7 @@ private val notaryNames = createNotaryNames(3)
|
||||
|
||||
// This is not the intended final design for how to use CordformDefinition, please treat this as experimental and DO
|
||||
// NOT use this as a design to copy.
|
||||
object RaftNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") {
|
||||
class RaftNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") {
|
||||
private val clusterName = CordaX500Name(RaftValidatingNotaryService.id, "Raft", "Zurich", "CH")
|
||||
|
||||
init {
|
||||
|
@ -17,13 +17,13 @@ import net.corda.testing.internal.demorun.notary
|
||||
import net.corda.testing.internal.demorun.rpcUsers
|
||||
import net.corda.testing.internal.demorun.runNodes
|
||||
|
||||
fun main(args: Array<String>) = SingleNotaryCordform.runNodes()
|
||||
fun main(args: Array<String>) = SingleNotaryCordform().runNodes()
|
||||
|
||||
val notaryDemoUser = User("demou", "demop", setOf(startFlowPermission<DummyIssueAndMove>(), startFlowPermission<RPCStartableNotaryFlowClient>()))
|
||||
|
||||
// This is not the intended final design for how to use CordformDefinition, please treat this as experimental and DO
|
||||
// NOT use this as a design to copy.
|
||||
object SingleNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") {
|
||||
class SingleNotaryCordform : CordformDefinition("build" / "notary-demo-nodes") {
|
||||
init {
|
||||
node {
|
||||
name(ALICE.name)
|
||||
|
Loading…
x
Reference in New Issue
Block a user