diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt index 5193703c59..057b1861b5 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Cordform.kt @@ -11,6 +11,7 @@ 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.Files import java.nio.file.Path import java.nio.file.Paths import java.util.concurrent.TimeUnit @@ -141,8 +142,6 @@ open class Cordform : DefaultTask() { } } - private fun fullNodePath(node: Node): Path = project.projectDir.toPath().resolve(node.nodeDir.toPath()) - private fun generateAndInstallNodeInfos() { generateNodeInfos() installNodeInfos() @@ -150,35 +149,65 @@ open class Cordform : DefaultTask() { 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()) - } + var nodeProcesses = buildNodeProcesses() 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") - } - } + validateNodeProcessess(nodeProcesses) } finally { - // This will be a no-op on success - abort remaining on failure - processes.forEach { - it.second.destroyForcibly() - } + destroyNodeProcesses(nodeProcesses) } } + private fun buildNodeProcesses(): Map { + return nodes + .map { buildNodeProcess(it) } + .toMap() + } + + private fun validateNodeProcessess(nodeProcesses: Map) { + nodeProcesses.forEach { (node, process) -> + validateNodeProcess(node, process) + } + } + + private fun destroyNodeProcesses(nodeProcesses: Map) { + nodeProcesses.forEach { (_, process) -> + process.destroyForcibly() + } + } + + private fun buildNodeProcess(node: Node): Pair { + node.makeLogDirectory() + var process = ProcessBuilder(generateNodeInfoCommand()) + .directory(node.fullPath().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(node.logFile().toFile()) + .addEnvironment("CAPSULE_CACHE_DIR", Node.capsuleCacheDir) + .start() + return Pair(node, process) + } + + private fun generateNodeInfoCommand(): List = listOf( + "java", + "-Dcapsule.log=verbose", + "-Dcapsule.dir=${Node.capsuleCacheDir}", + "-jar", + Node.nodeJarName, + "--just-generate-node-info" + ) + + private fun validateNodeProcess(node: Node, process: Process) { + val generateTimeoutSeconds = 60L + if (!process.waitFor(generateTimeoutSeconds, TimeUnit.SECONDS)) { + throw GradleException("Node took longer $generateTimeoutSeconds seconds than too to generate node info - see node log at ${node.fullPath()}/logs") + } + if (process.exitValue() != 0) { + throw GradleException("Node exited with ${process.exitValue()} when generating node infos - see node log at ${node.fullPath()}/logs") + } + project.logger.info("Generated node info for ${node.fullPath()}") + } + private fun installNodeInfos() { project.logger.info("Installing node infos") for (source in nodes) { @@ -186,13 +215,15 @@ open class Cordform : DefaultTask() { if (source.nodeDir != destination.nodeDir) { project.copy { it.apply { - from(fullNodePath(source).toString()) + from(source.fullPath().toString()) include("nodeInfo-*") - into(fullNodePath(destination).resolve(CordformNode.NODE_INFO_DIRECTORY).toString()) + into(destination.fullPath().resolve(CordformNode.NODE_INFO_DIRECTORY).toString()) } } } } } } + private fun Node.logFile(): Path = this.logDirectory().resolve("generate-info.log") + private fun ProcessBuilder.addEnvironment(key: String, value: String) = this.apply { environment().put(key, value) } } diff --git a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt index b0660a9245..d08016bf3c 100644 --- a/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt +++ b/gradle-plugins/cordformation/src/main/kotlin/net/corda/plugins/Node.kt @@ -21,8 +21,13 @@ class Node(private val project: Project) : CordformNode() { @JvmStatic val webJarName = "corda-webserver.jar" private val configFileProperty = "configFile" + val capsuleCacheDir: String = "./cache" } + fun fullPath(): Path = project.projectDir.toPath().resolve(nodeDir.toPath()) + fun logDirectory(): Path = fullPath().resolve("logs") + fun makeLogDirectory() = Files.createDirectories(logDirectory()) + /** * 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 @@ -201,7 +206,12 @@ class Node(private val project: Project) : CordformNode() { * 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 options = ConfigRenderOptions + .defaults() + .setOriginComments(false) + .setComments(false) + .setFormatted(true) + .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.