NodeRunner enhancements (#614)

* Use same java.home as caller in terminal window case, previously only headless case did that
* Bump gradlePluginsVersion as I've changed NodeRunner

On Linux:
* Prefer JAVA_HOME over PATH
* Use correct escaping
* Start sh on failure so that terminals don't disappear taking error messages with them
This commit is contained in:
Andrzej Cichocki 2017-05-09 10:22:22 +01:00 committed by GitHub
parent ccb8827107
commit 45997ccd13
3 changed files with 70 additions and 81 deletions

View File

@ -1,4 +1,4 @@
gradlePluginsVersion=0.11.2
gradlePluginsVersion=0.12.0
kotlinVersion=1.1.1
guavaVersion=21.0
bouncycastleVersion=1.56

View File

@ -9,5 +9,5 @@ cd "$basedir"
if which osascript >/dev/null; then
/usr/libexec/java_home -v 1.8 --exec java -jar runnodes.jar "$@"
else
java -jar runnodes.jar "$@"
"${JAVA_HOME:+$JAVA_HOME/bin/}java" -jar runnodes.jar "$@"
fi

View File

@ -3,119 +3,108 @@ package net.corda.plugins
import java.awt.GraphicsEnvironment
import java.io.File
import java.nio.file.Files
import java.nio.file.Paths
import java.util.*
private val nodeJarName = "corda.jar"
private val webJarName = "corda-webserver.jar"
private val nodeConfName = "node.conf"
private val HEADLESS_FLAG = "--headless"
private val os: OS by lazy {
private val os by lazy {
val osName = System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH)
if ((osName.indexOf("mac") >= 0) || (osName.indexOf("darwin") >= 0)) OS.MACOS
else if (osName.indexOf("win") >= 0) OS.WINDOWS
if ("mac" in osName || "darwin" in osName) OS.MACOS
else if ("win" in osName) OS.WINDOWS
else OS.LINUX
}
private enum class OS { MACOS, WINDOWS, LINUX }
data class IncrementalPortAllocator(var basePort: Int = 5005) {
fun next(): Int = basePort++
private object debugPortAlloc {
private var basePort = 5005
internal fun next() = basePort++
}
val debugPortAlloc: IncrementalPortAllocator = IncrementalPortAllocator()
fun main(args: Array<String>) {
val startedProcesses = mutableListOf<Process>()
val headless = GraphicsEnvironment.isHeadless() || (args.isNotEmpty() && args[0] == HEADLESS_FLAG)
val workingDir = Paths.get(System.getProperty("user.dir")).toFile()
val workingDir = File(System.getProperty("user.dir"))
val javaArgs = args.filter { it != HEADLESS_FLAG }
println("Starting nodes in $workingDir")
workingDir.list().map { File(workingDir, it) }.forEach {
if (isNode(it)) {
startedProcesses += startJarProcess(headless, it, nodeJarName, javaArgs)
}
if (isWebserver(it)) {
startedProcesses += startJarProcess(headless, it, webJarName, javaArgs)
workingDir.listFiles { file -> file.isDirectory }.forEach { dir ->
listOf(NodeJarType, WebJarType).forEach { jarType ->
jarType.acceptDirAndStartProcess(dir, headless, javaArgs)?.let { startedProcesses += it }
}
}
println("Started ${startedProcesses.size} processes")
println("Finished starting nodes")
}
private fun startJarProcess(headless: Boolean, dir: File, jarName: String, javaArgs: List<String>) : Process {
val runJar = getJarRunner(headless)
val debugPort = debugPortAlloc.next()
println("Starting $jarName in $dir on debug port $debugPort")
val proc = runJar(jarName, dir, javaArgs, debugPort)
if (os == OS.MACOS) Thread.sleep(1000)
return proc
private abstract class JarType(private val jarName: String) {
internal abstract fun acceptNodeConf(nodeConf: File): Boolean
internal fun acceptDirAndStartProcess(dir: File, headless: Boolean, javaArgs: List<String>): Process? {
if (!File(dir, jarName).exists()) {
return null
}
if (!File(dir, "node.conf").let { it.exists() && acceptNodeConf(it) }) {
return null
}
val debugPort = debugPortAlloc.next()
println("Starting $jarName in $dir on debug port $debugPort")
val process = (if (headless) ::HeadlessJavaCommand else ::TerminalWindowJavaCommand)(jarName, dir, debugPort, javaArgs).start()
if (os == OS.MACOS) Thread.sleep(1000)
return process
}
}
private fun isNode(maybeNodeDir: File) = maybeNodeDir.isDirectory
&& File(maybeNodeDir, nodeJarName).exists()
&& File(maybeNodeDir, nodeConfName).exists()
private fun isWebserver(maybeWebserverDir: File) = maybeWebserverDir.isDirectory
&& File(maybeWebserverDir, webJarName).exists()
&& File(maybeWebserverDir, nodeConfName).exists()
&& hasWebserverPort(maybeWebserverDir)
// TODO: Add a webserver.conf, or use TypeSafe config instead of this hack
private fun hasWebserverPort(nodeConfDir: File) = Files.readAllLines(File(nodeConfDir, nodeConfName).toPath()).joinToString { it }.contains("webAddress")
private fun getDebugPortArg(debugPort: Int?) = if (debugPort != null) {
listOf("-Dcapsule.jvm.args=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=$debugPort")
} else {
emptyList()
private object NodeJarType : JarType("corda.jar") {
override fun acceptNodeConf(nodeConf: File) = true
}
private fun getJarRunner(headless: Boolean): (String, File, List<String>, Int?) -> Process = if (headless) ::execJar else ::execJarInTerminalWindow
private fun execJar(jarName: String, dir: File, args: List<String> = listOf(), debugPort: Int?): Process {
val nodeName = dir.toPath().fileName
val separator = System.getProperty("file.separator")
val path = System.getProperty("java.home") + separator + "bin" + separator + "java"
val builder = ProcessBuilder(listOf(path, "-Dname=$nodeName") + getDebugPortArg(debugPort) + listOf("-jar", jarName, "--no-local-shell") + args)
builder.redirectError(Paths.get("error.${dir.toPath().fileName}.log").toFile())
builder.inheritIO()
builder.directory(dir)
return builder.start()
private object WebJarType : JarType("corda-webserver.jar") {
// TODO: Add a webserver.conf, or use TypeSafe config instead of this hack
override fun acceptNodeConf(nodeConf: File) = Files.lines(nodeConf.toPath()).anyMatch { "webAddress" in it }
}
private fun execJarInTerminalWindow(jarName: String, dir: File, args: List<String> = listOf(), debugPort: Int?): Process {
val nodeName = "${dir.toPath().fileName}-$jarName"
val javaCmd = (listOf("java", "-Dname=$nodeName") + getDebugPortArg(debugPort) + listOf("-jar", jarName) + args).joinToString(" ") { it }
val builder = when (os) {
OS.MACOS -> ProcessBuilder(
"osascript", "-e",
"""tell app "Terminal"
private abstract class JavaCommand(jarName: String, internal val dir: File, debugPort: Int?, internal val nodeName: String, init: MutableList<String>.() -> Unit, args: List<String>) {
internal val command: List<String> = mutableListOf<String>().apply {
add(File(File(System.getProperty("java.home"), "bin"), "java").path)
add("-Dname=$nodeName")
null != debugPort && add("-Dcapsule.jvm.args=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=$debugPort")
add("-jar"); add(jarName)
init()
addAll(args)
}
internal abstract fun processBuilder(): ProcessBuilder
internal fun start() = processBuilder().directory(dir).start()
}
private class HeadlessJavaCommand(jarName: String, dir: File, debugPort: Int?, args: List<String>) : JavaCommand(jarName, dir, debugPort, dir.name, { add("--no-local-shell") }, args) {
override fun processBuilder() = ProcessBuilder(command).redirectError(File("error.$nodeName.log")).inheritIO()
}
private class TerminalWindowJavaCommand(jarName: String, dir: File, debugPort: Int?, args: List<String>) : JavaCommand(jarName, dir, debugPort, "${dir.name}-$jarName", {}, args) {
override fun processBuilder() = ProcessBuilder(when (os) {
OS.MACOS -> {
listOf("osascript", "-e", """tell app "Terminal"
activate
tell app "System Events" to tell process "Terminal" to keystroke "t" using command down
delay 0.5
do script "bash -c 'cd $dir; /usr/libexec/java_home -v 1.8 --exec $javaCmd && exit'" in selected tab of the front window
end tell"""
)
OS.WINDOWS -> ProcessBuilder(
"cmd"
, "/C", "start $javaCmd"
)
do script "bash -c 'cd $dir; /usr/libexec/java_home -v 1.8 --exec ${command.joinToString(" ")} && exit'" in selected tab of the front window
end tell""")
}
OS.WINDOWS -> {
listOf("cmd", "/C", "start ${command.joinToString(" ")}")
}
OS.LINUX -> {
val isTmux = System.getenv("TMUX")?.isNotEmpty() ?: false
if (isTmux) {
ProcessBuilder(
"tmux", "new-window", "-n", nodeName, javaCmd
)
val command = "${unixCommand()} || sh"
if (isTmux()) {
listOf("tmux", "new-window", "-n", nodeName, command)
} else {
ProcessBuilder(
"xterm", "-T", nodeName, "-e", javaCmd
)
listOf("xterm", "-T", nodeName, "-e", command)
}
}
}
return builder.directory(dir).start()
})
private fun unixCommand() = command.map(::quotedFormOf).joinToString(" ")
}
private fun quotedFormOf(text: String) = "'${text.replace("'", "'\\''")}'" // Suitable for UNIX shells.
private fun isTmux() = System.getenv("TMUX")?.isNotEmpty() ?: false