From ce435adf252b1818aa8dc79a9fe12bc15047050e Mon Sep 17 00:00:00 2001 From: Cedric Beust Date: Wed, 16 Mar 2016 01:26:49 +0400 Subject: [PATCH] Modify commands to report errors to the IDEA plug-in. --- .../kobalt/internal/remote/KobaltServer.kt | 2 +- .../kobalt/internal/remote/PingCommand.kt | 1 - src/main/kotlin/com/beust/kobalt/Main.kt | 15 ++++++- .../com/beust/kobalt/app/BuildFileCompiler.kt | 41 +++++++++++-------- .../beust/kobalt/app/remote/DependencyData.kt | 8 ++-- .../app/remote/GetDependenciesCommand.kt | 3 +- .../kobalt/plugin/kotlin/KotlinCompiler.kt | 31 ++++++++++---- 7 files changed, 68 insertions(+), 33 deletions(-) diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/remote/KobaltServer.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/remote/KobaltServer.kt index 9704cd0b..fcb5c4b2 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/remote/KobaltServer.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/remote/KobaltServer.kt @@ -17,7 +17,7 @@ interface ICommand { */ fun run(sender: ICommandSender, received: JsonObject) - fun toCommandData(data: String) = CommandData(name, data) + fun toCommandData(data: String, error: String? = null) = CommandData(name, data, error) } /** diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/remote/PingCommand.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/remote/PingCommand.kt index ad9b2f21..c5249497 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/remote/PingCommand.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/remote/PingCommand.kt @@ -1,6 +1,5 @@ package com.beust.kobalt.internal.remote -import com.beust.kobalt.misc.log import com.google.gson.Gson import com.google.gson.JsonObject diff --git a/src/main/kotlin/com/beust/kobalt/Main.kt b/src/main/kotlin/com/beust/kobalt/Main.kt index ed8d793d..a7072a66 100644 --- a/src/main/kotlin/com/beust/kobalt/Main.kt +++ b/src/main/kotlin/com/beust/kobalt/Main.kt @@ -6,6 +6,7 @@ import com.beust.kobalt.api.Kobalt import com.beust.kobalt.api.PluginTask import com.beust.kobalt.api.Project import com.beust.kobalt.app.* +import com.beust.kobalt.app.remote.DependencyData import com.beust.kobalt.app.remote.KobaltClient import com.beust.kobalt.app.remote.KobaltServer import com.beust.kobalt.internal.KobaltSettings @@ -65,6 +66,7 @@ private class Main @Inject constructor( val pluginInfo: PluginInfo, val projectGenerator: ProjectGenerator, val depFactory: DepFactory, + val dependencyData: DependencyData, val resolveDependency: ResolveDependency) { data class RunInfo(val jc: JCommander, val args: Args) @@ -88,7 +90,7 @@ private class Main @Inject constructor( return pluginClassLoader } - public fun run(jc: JCommander, args: Args, argv: Array): Int { + fun run(jc: JCommander, args: Args, argv: Array): Int { // // Install plug-ins requested from the command line // @@ -101,6 +103,9 @@ private class Main @Inject constructor( // pluginInfo.plugins.forEach { Plugins.addPluginInstance(it) } +// val data = dependencyData.dependenciesDataFor(homeDir("kotlin/klaxon/kobalt/src/Build.kt"), Args()) +// println("Data: $data") + // --listTemplates if (args.listTemplates) { Templates().list(pluginInfo) @@ -174,8 +179,14 @@ private class Main @Inject constructor( if (!buildFile.exists()) { error(buildFile.path.toFile().path + " does not exist") } else { - val allProjects = buildFileCompilerFactory.create(listOf(buildFile), pluginInfo) + val findProjectResult = buildFileCompilerFactory.create(listOf(buildFile), pluginInfo) .compileBuildFiles(args) + if (! findProjectResult.taskResult.success) { + throw KobaltException("Couldn't compile build file: " + + findProjectResult.taskResult.errorMessage) + } + + val allProjects = findProjectResult.projects // // Now that we have projects, add all the repos from repo contributors that need a Project diff --git a/src/main/kotlin/com/beust/kobalt/app/BuildFileCompiler.kt b/src/main/kotlin/com/beust/kobalt/app/BuildFileCompiler.kt index 6c59fe37..071dc4b2 100644 --- a/src/main/kotlin/com/beust/kobalt/app/BuildFileCompiler.kt +++ b/src/main/kotlin/com/beust/kobalt/app/BuildFileCompiler.kt @@ -2,8 +2,8 @@ package com.beust.kobalt.app import com.beust.kobalt.Args import com.beust.kobalt.Constants -import com.beust.kobalt.KobaltException import com.beust.kobalt.Plugins +import com.beust.kobalt.TaskResult import com.beust.kobalt.api.Kobalt import com.beust.kobalt.api.KobaltContext import com.beust.kobalt.api.PluginProperties @@ -39,7 +39,7 @@ public class BuildFileCompiler @Inject constructor(@Assisted("buildFiles") val b private val SCRIPT_JAR = "buildScript.jar" - fun compileBuildFiles(args: Args): List { + fun compileBuildFiles(args: Args): FindProjectResult { // // Create the KobaltContext // @@ -54,16 +54,21 @@ public class BuildFileCompiler @Inject constructor(@Assisted("buildFiles") val b // // Find all the projects in the build file, possibly compiling them // - val allProjects = findProjects(context) - plugins.applyPlugins(context, allProjects) + val projectResult = findProjects(context) + if (projectResult.taskResult.success) { + plugins.applyPlugins(context, projectResult.projects) + } - return allProjects + return projectResult } val parsedBuildFiles = arrayListOf() - private fun findProjects(context: KobaltContext): List { - val result = arrayListOf() + class FindProjectResult(val projects: List, val taskResult: TaskResult) + + private fun findProjects(context: KobaltContext): FindProjectResult { + var errorTaskResult: TaskResult? = null + val projects = arrayListOf() buildFiles.forEach { buildFile -> val parsedBuildFile = parseBuildFile(context, buildFile) parsedBuildFiles.add(parsedBuildFile) @@ -83,35 +88,39 @@ public class BuildFileCompiler @Inject constructor(@Assisted("buildFiles") val b // compile it, jar it in buildScript.jar and run it val modifiedBuildFile = KFiles.createTempFile(".kt") KFiles.saveFile(modifiedBuildFile, parsedBuildFile.buildScriptCode) - maybeCompileBuildFile(context, BuildFile(Paths.get(modifiedBuildFile.path), + val taskResult = maybeCompileBuildFile(context, BuildFile(Paths.get(modifiedBuildFile.path), "Modified ${Constants.BUILD_FILE_NAME}", buildFile.realPath), buildScriptJarFile, pluginUrls) - val projects = buildScriptUtil.runBuildScriptJarFile(buildScriptJarFile, pluginUrls, context) - result.addAll(projects) + if (taskResult.success) { + projects.addAll(buildScriptUtil.runBuildScriptJarFile(buildScriptJarFile, pluginUrls, context)) + } else { + if (errorTaskResult == null) { + errorTaskResult = taskResult + } + } } - return result + return FindProjectResult(projects, if (errorTaskResult != null) errorTaskResult!! else TaskResult()) } private fun maybeCompileBuildFile(context: KobaltContext, buildFile: BuildFile, buildScriptJarFile: File, - pluginUrls: List) { + pluginUrls: List) : TaskResult { log(2, "Running build file ${buildFile.name} jar: $buildScriptJarFile") if (buildScriptUtil.isUpToDate(buildFile, buildScriptJarFile)) { log(2, "Build file is up to date") + return TaskResult() } else { log(2, "Need to recompile ${buildFile.name}") buildScriptJarFile.delete() - kotlinCompilePrivate { + val result = kotlinCompilePrivate { classpath(files.kobaltJar) classpath(pluginUrls.map { it.file }) sourceFiles(listOf(buildFile.path.toFile().absolutePath)) output = buildScriptJarFile }.compile(context = context) - if (! buildScriptJarFile.exists()) { - throw KobaltException("Could not compile ${buildFile.name}") - } + return result } } diff --git a/src/main/kotlin/com/beust/kobalt/app/remote/DependencyData.kt b/src/main/kotlin/com/beust/kobalt/app/remote/DependencyData.kt index 1cd7d95e..cd940c2f 100644 --- a/src/main/kotlin/com/beust/kobalt/app/remote/DependencyData.kt +++ b/src/main/kotlin/com/beust/kobalt/app/remote/DependencyData.kt @@ -31,11 +31,11 @@ class DependencyData @Inject constructor(val executors: KobaltExecutors, val dep val buildFile = BuildFile(Paths.get(buildFilePath), "GetDependenciesCommand") val buildFileCompiler = buildFileCompilerFactory.create(listOf(buildFile), pluginInfo) - val projects = buildFileCompiler.compileBuildFiles(args) + val projectResult = buildFileCompiler.compileBuildFiles(args) val pluginUrls = buildFileCompiler.parsedBuildFiles.flatMap { it.pluginUrls } val pluginDependencies = pluginUrls.map { File(it.toURI()) }.map { FileDependency(it.absolutePath) } - projects.forEach { project -> + projectResult.projects.forEach { project -> val compileDependencies = pluginDependencies.map { toDependencyData(it, "compile") } + allDeps(project.compileDependencies).map { toDependencyData(it, "compile") } + allDeps(project.compileProvidedDependencies).map { toDependencyData(it, "compile") } @@ -57,7 +57,7 @@ class DependencyData @Inject constructor(val executors: KobaltExecutors, val dep compileDependencies, testDependencies, sources.second.toSet(), tests.second.toSet(), sources.first.toSet(), tests.first.toSet())) } - return GetDependenciesData(projectDatas) + return GetDependenciesData(projectDatas, projectResult.taskResult.errorMessage) } ///// @@ -72,5 +72,5 @@ class DependencyData @Inject constructor(val executors: KobaltExecutors, val dep val testDependencies: List, val sourceDirs: Set, val testDirs: Set, val sourceResourceDirs: Set, val testResourceDirs: Set) - class GetDependenciesData(val projects: List) + class GetDependenciesData(val projects: List, val errorMessage: String?) } diff --git a/src/main/kotlin/com/beust/kobalt/app/remote/GetDependenciesCommand.kt b/src/main/kotlin/com/beust/kobalt/app/remote/GetDependenciesCommand.kt index 014c77ba..0f6acf6d 100644 --- a/src/main/kotlin/com/beust/kobalt/app/remote/GetDependenciesCommand.kt +++ b/src/main/kotlin/com/beust/kobalt/app/remote/GetDependenciesCommand.kt @@ -19,7 +19,8 @@ class GetDependenciesCommand @Inject constructor(val args: Args, val dependencyD override fun run(sender: ICommandSender, received: JsonObject) { val buildFile = received.get("buildFile").asString - val data = toCommandData(Gson().toJson(dependencyData.dependenciesDataFor(buildFile, args))) + val dd = dependencyData.dependenciesDataFor(buildFile, args) + val data = toCommandData(Gson().toJson(dd), dd.errorMessage) sender.sendData(data) } } \ No newline at end of file diff --git a/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinCompiler.kt b/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinCompiler.kt index 72226ca0..f765e38e 100644 --- a/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinCompiler.kt +++ b/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinCompiler.kt @@ -14,7 +14,10 @@ import com.beust.kobalt.misc.log import org.jetbrains.kotlin.cli.common.CLICompiler import org.jetbrains.kotlin.cli.common.ExitCode import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler +import java.io.ByteArrayOutputStream import java.io.File +import java.io.PrintStream +import java.nio.charset.Charset import java.util.* import javax.inject.Inject import javax.inject.Singleton @@ -64,8 +67,7 @@ class KotlinCompiler @Inject constructor( allArgs.add("-no-stdlib") } - val success = invokeCompiler(projectName ?: "kobalt-" + Random().nextInt(), cp, allArgs) - return TaskResult(success) + return invokeCompiler(projectName ?: "kobalt-" + Random().nextInt(), cp, allArgs) } /** @@ -78,10 +80,10 @@ class KotlinCompiler @Inject constructor( * There are plenty of ways in which this method can break but this will be immediately * apparent if it happens. */ - private fun invokeCompiler(projectName: String, cp: List, args: List): Boolean { + private fun invokeCompiler(projectName: String, cp: List, args: List): TaskResult { val allArgs = listOf("-module-name", "project-" + projectName) + args log(2, "Calling kotlinc " + allArgs.joinToString(" ")) - val result : Boolean = + val result : TaskResult = if (true) { val classLoader = ParentLastClassLoader(cp.map { it.toURI().toURL() }) val compiler = classLoader.loadClass("org.jetbrains.kotlin.cli.common.CLICompiler") @@ -89,13 +91,26 @@ class KotlinCompiler @Inject constructor( it.name == "doMainNoExit" && it.parameterTypes.size == 2 }[0] val kCompiler = classLoader.loadClass("org.jetbrains.kotlin.cli.jvm.K2JVMCompiler") - val compilerInstance = kCompiler.newInstance() - val exitCode = compilerMain.invoke(null, compilerInstance, allArgs.toTypedArray()) + + // + // In order to capture the error stream, I need to invoke CLICompiler.exec(), which + // is the first method that accepts a PrintStream for the errors in parameter + // + val baos = ByteArrayOutputStream() + val ps = PrintStream(baos) + val execMethod = compiler.declaredMethods.filter { + it.name == "exec" && it.parameterTypes.size == 2 + }[0] + val exitCode = execMethod.invoke(kCompiler.newInstance(), ps, allArgs.toTypedArray()) + val errorString = baos.toString(Charset.defaultCharset().toString()) + + // The return value is an enum val nameMethod = exitCode.javaClass.getMethod("name") - "OK" == nameMethod.invoke(exitCode).toString() + val success = "OK" == nameMethod.invoke(exitCode).toString() + TaskResult(success, errorString) } else { val exitCode = CLICompiler.doMainNoExit(K2JVMCompiler(), allArgs.toTypedArray()) - exitCode == ExitCode.OK + TaskResult(exitCode == ExitCode.OK) } return result }