diff --git a/src/main/kotlin/com/beust/kobalt/misc/RunCommand.kt b/src/main/kotlin/com/beust/kobalt/misc/RunCommand.kt index 05b00116..957a472a 100644 --- a/src/main/kotlin/com/beust/kobalt/misc/RunCommand.kt +++ b/src/main/kotlin/com/beust/kobalt/misc/RunCommand.kt @@ -6,65 +6,28 @@ import java.io.InputStream import java.io.InputStreamReader import java.util.concurrent.TimeUnit -open class RunCommand(val command: String) { - val DEFAULT_SUCCESS = { output: List -> } -// val DEFAULT_SUCCESS_VERBOSE = { output: List -> log(2, "Success:\n " + output.joinToString("\n"))} - val defaultSuccess = DEFAULT_SUCCESS - val DEFAULT_ERROR = { - output: List -> error(output.joinToString("\n ")) - } - - var directory = File(".") - var env = hashMapOf() +class RunCommandInfo { + lateinit var command: String + var args : List = arrayListOf() + var directory : File = File("") + var env : Map = hashMapOf() /** * Some commands fail but return 0, so the only way to find out if they failed is to look * at the error stream. However, some commands succeed but output text on the error stream. * This field is used to specify how errors are caught. */ - var useErrorStreamAsErrorIndicator = true - var useInputStreamAsErrorIndicator = false + var useErrorStreamAsErrorIndicator : Boolean = true + var useInputStreamAsErrorIndicator : Boolean = false - fun useErrorStreamAsErrorIndicator(f: Boolean) : RunCommand { - useErrorStreamAsErrorIndicator = f - return this - } + var errorCallback: Function1, Unit> = RunCommand.DEFAULT_ERROR + var successCallback: Function1, Unit> = RunCommand.DEFAULT_SUCCESS - open fun run(args: List, - errorCallback: Function1, Unit> = DEFAULT_ERROR, - successCallback: Function1, Unit> = defaultSuccess) : Int { - val allArgs = arrayListOf() - allArgs.add(command) - allArgs.addAll(args) - - val pb = ProcessBuilder(allArgs) - pb.directory(directory) - log(2, "Running command in directory ${directory.absolutePath}" + - "\n " + allArgs.joinToString(" ").replace("\\", "/")) - val process = pb.start() - pb.environment().let { pbEnv -> - env.forEach { - pbEnv.put(it.key, it.value) - } - } - val callSucceeded = process.waitFor(30, TimeUnit.SECONDS) - val input = if (process.inputStream.available() > 0) fromStream(process.inputStream) - else listOf() - val error = if (process.errorStream.available() > 0) fromStream(process.errorStream) - else listOf() - val isSuccess = isSuccess(callSucceeded, input, error) - - if (isSuccess) { - successCallback(fromStream(process.inputStream)) - } else { - errorCallback(error + input) - } - - return if (isSuccess) 0 else 1 - } - - open protected fun isSuccess(callSucceeded: Boolean, input: List, error: List) : Boolean { - var hasErrors = ! callSucceeded + var isSuccess: (Boolean, List, List) -> Boolean = { + isSuccess: Boolean, + input: List, + error: List -> + var hasErrors = ! isSuccess if (useErrorStreamAsErrorIndicator && ! hasErrors) { hasErrors = hasErrors || error.size > 0 } @@ -72,9 +35,83 @@ open class RunCommand(val command: String) { hasErrors = hasErrors || input.size > 0 } + ! hasErrors + } +} + +fun runCommand(init: RunCommandInfo.() -> Unit) = RunCommand(RunCommandInfo().apply { init() }).invoke() + +open class RunCommand(val info: RunCommandInfo) { + + companion object { + val DEFAULT_SUCCESS = { output: List -> } + // val DEFAULT_SUCCESS_VERBOSE = { output: List -> log(2, "Success:\n " + output.joinToString("\n"))} +// val defaultSuccess = DEFAULT_SUCCESS + val DEFAULT_ERROR = { + output: List -> + error(output.joinToString("\n ")) + } + } + +// fun useErrorStreamAsErrorIndicator(f: Boolean) : RunCommand { +// useErrorStreamAsErrorIndicator = f +// return this +// } + + fun invoke() : Int { + val allArgs = arrayListOf() + allArgs.add(info.command) + allArgs.addAll(info.args) + + val pb = ProcessBuilder(allArgs) + pb.directory(info.directory) + log(2, "Running command in directory ${info.directory.absolutePath}" + + "\n " + allArgs.joinToString(" ").replace("\\", "/")) + val process = pb.start() + pb.environment().let { pbEnv -> + info.env.forEach { + pbEnv.put(it.key, it.value) + } + } + + // Run the command and collect the return code and streams + val returnCode = process.waitFor(30, TimeUnit.SECONDS) + val input = if (process.inputStream.available() > 0) fromStream(process.inputStream) + else listOf() + val error = if (process.errorStream.available() > 0) fromStream(process.errorStream) + else listOf() + + // Check to see if the command succeeded + val isSuccess = isSuccess(returnCode, input, error) + + if (isSuccess) { + info.successCallback(input) + } else { + info.errorCallback(error + input) + } + + return if (isSuccess) 0 else 1 + } + + /** + * Subclasses can override this method to do their own error handling, since commands can + * have various ways to signal errors. + */ + open protected fun isSuccess(isSuccess: Boolean, input: List, error: List) : Boolean { + var hasErrors = ! isSuccess + if (info.useErrorStreamAsErrorIndicator && ! hasErrors) { + hasErrors = hasErrors || error.size > 0 + } + if (info.useInputStreamAsErrorIndicator && ! hasErrors) { + hasErrors = hasErrors || input.size > 0 + } + return ! hasErrors } + /** + * Turn the given InputStream into a list of strings. + */ private fun fromStream(ins: InputStream) : List { val result = arrayListOf() val br = BufferedReader(InputStreamReader(ins)) diff --git a/src/main/kotlin/com/beust/kobalt/plugin/android/AndroidCommand.kt b/src/main/kotlin/com/beust/kobalt/plugin/android/AndroidCommand.kt index d9218675..eb82db85 100644 --- a/src/main/kotlin/com/beust/kobalt/plugin/android/AndroidCommand.kt +++ b/src/main/kotlin/com/beust/kobalt/plugin/android/AndroidCommand.kt @@ -1,24 +1,54 @@ package com.beust.kobalt.plugin.android import com.beust.kobalt.api.Project -import com.beust.kobalt.misc.RunCommand import com.beust.kobalt.misc.log +import com.beust.kobalt.misc.runCommand import java.io.File -open class AndroidCommand(project: Project, androidHome: String, command: String, cwd: File = File(project.directory)) -: RunCommand(command) { - init { - env.put("ANDROID_HOME", androidHome) - directory = cwd - } +open class AndroidCommand(project: Project, val androidHome: String, val command: String, + val directory: File = File(project.directory), + val useErrorStreamAsErrorIndicator : Boolean = true, + val args: List) +// : RunCommand(command, directory = cwd, args = args +// , +// successCallback = { output -> +// log(1, "$command succeeded:") +// output.forEach { +// log(1, " $it") +// } +// } + { - open fun call(args: List) = run(args, + +// val SUCCESS_CALLBACK : (List) -> Unit = { output -> +// log(1, "$command succeeded:") +// output.forEach { +// log(1, " $it") +// } +// } +// +// val ERROR_CALLBACK : (List) -> Unit = { output -> +// with(StringBuilder()) { +// append("Error running $command:") +// output.forEach { +// append(" $it") +// } +// error(this.toString()) +// } +// }nComman + + open fun call(theseArgs: List) : Int { + val rc = runCommand { + args = theseArgs + useErrorStreamAsErrorIndicator = useErrorStreamAsErrorIndicator + directory = directory + env = hashMapOf("ANDROID_HOME" to androidHome) successCallback = { output -> log(1, "$command succeeded:") output.forEach { log(1, " $it") } - }, + } errorCallback = { output -> with(StringBuilder()) { append("Error running $command:") @@ -27,7 +57,10 @@ open class AndroidCommand(project: Project, androidHome: String, command: String } error(this.toString()) } - }) + } + } + return rc + } } diff --git a/src/main/kotlin/com/beust/kobalt/plugin/android/AndroidPlugin.kt b/src/main/kotlin/com/beust/kobalt/plugin/android/AndroidPlugin.kt index de11a47b..64c41675 100644 --- a/src/main/kotlin/com/beust/kobalt/plugin/android/AndroidPlugin.kt +++ b/src/main/kotlin/com/beust/kobalt/plugin/android/AndroidPlugin.kt @@ -107,15 +107,12 @@ public class AndroidPlugin @Inject constructor(val javaCompiler: JavaCompiler, v * aapt returns 0 even if it fails, so in order to detect whether it failed, we are checking * if its error stream contains anything. */ - inner class AaptCommand(project: Project, aapt: String, val aaptCommand: String, - cwd: File = File(".")) : AndroidCommand(project, androidHome(project), aapt) { - init { - directory = cwd - useErrorStreamAsErrorIndicator = true - } - - override fun call(args: List) = super.run(arrayListOf(aaptCommand) + args) - } + inner class AaptCommand(project: Project, aapt: String, val aaptCommand: String, cwd: File = File("."), + args: List) + : AndroidCommand(project, androidHome(project), aapt, + directory = cwd, + useErrorStreamAsErrorIndicator = true, + args = arrayListOf(aaptCommand) + args) private fun generateR(project: Project, generated: String, aapt: String) : Boolean { val compileSdkVersion = compileSdkVersion(project) @@ -132,7 +129,7 @@ public class AndroidPlugin @Inject constructor(val javaCompiler: JavaCompiler, v val variantDir = context.variant.toIntermediateDir() val rDirectory = KFiles.joinAndMakeDir(generated, "source", "r", variantDir).toString() - val result = AaptCommand(project, aapt, "package").call(listOf( + val result = AaptCommand(project, aapt, "package", args = listOf( "-f", "--no-crunch", "-I", androidJar.toString(), @@ -358,7 +355,7 @@ public class AndroidPlugin @Inject constructor(val javaCompiler: JavaCompiler, v * adb has weird ways of signaling errors, that's the best I've found so far. */ class AdbInstall : RunCommand(adb(project)) { - override fun isSuccess(callSucceeded: Boolean, input: List, error: List) + override fun isSuccess(isSuccess: Boolean, input: List, error: List) = input.filter { it.contains("Success")}.size > 0 } diff --git a/src/main/kotlin/com/beust/kobalt/plugin/retrolambda/RetrolambdaPlugin.kt b/src/main/kotlin/com/beust/kobalt/plugin/retrolambda/RetrolambdaPlugin.kt index f22e06b5..2b319d4c 100644 --- a/src/main/kotlin/com/beust/kobalt/plugin/retrolambda/RetrolambdaPlugin.kt +++ b/src/main/kotlin/com/beust/kobalt/plugin/retrolambda/RetrolambdaPlugin.kt @@ -55,9 +55,10 @@ class RetrolambdaPlugin @Inject constructor(val dependencyManager: DependencyMan "-Dretrolambda.bytecodeVersion=${config.byteCodeVersion}", "-jar", JAR.jarFile.get().path) - val result = RunCommand("java").apply { - directory = File(project.directory) - }.run(args) + val result = RunCommand("java", + directory = File(project.directory), + args = args) + .run() TaskResult(result == 0) } else { TaskResult()