diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/TaskManager.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/TaskManager.kt index ea800e5d..0cda20eb 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/TaskManager.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/TaskManager.kt @@ -340,6 +340,15 @@ class TaskManager @Inject constructor(val args: Args, runAfter.forEach { runAfter(it, name) } } + /** + * Invoked by the server whenever it's done processing a command so the state can be reset for the next command. + */ + private fun cleanUp() { + annotationTasks.clear() + dynamicTasks.clear() + taskAnnotations.clear() + } + // // Manage the tasks ///// 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 fcb5c4b2..9c2935c5 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 @@ -1,5 +1,6 @@ package com.beust.kobalt.internal.remote +import com.beust.kobalt.api.Project import com.google.gson.JsonObject /** @@ -14,15 +15,16 @@ interface ICommand { /** * Run this command based on the information received from the client. When done, use * the sender object to send back a response. + * @param initCallback The string is a path to the build file */ - fun run(sender: ICommandSender, received: JsonObject) + fun run(sender: ICommandSender, received: JsonObject, initCallback: (String) -> List) fun toCommandData(data: String, error: String? = null) = CommandData(name, data, error) } /** * Passed to a command in its `run` method so it can send information back to the caller. - * @param The string content that will be sent in the "data" field. + * @param commandData The string content that will be sent in the "data" field. */ interface ICommandSender { fun sendData(commandData: CommandData) 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 c5249497..555ded39 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,5 +1,6 @@ package com.beust.kobalt.internal.remote +import com.beust.kobalt.api.Project import com.google.gson.Gson import com.google.gson.JsonObject @@ -14,7 +15,7 @@ import com.google.gson.JsonObject class PingCommand() : ICommand { override val name = "ping" - override fun run(sender: ICommandSender, received: JsonObject) { + override fun run(sender: ICommandSender, received: JsonObject, initCallback: (String) -> List) { sender.sendData(toCommandData(Gson().toJson(PingData(received.toString())))) } diff --git a/src/main/kotlin/com/beust/kobalt/Main.kt b/src/main/kotlin/com/beust/kobalt/Main.kt index df88e06a..123440d7 100644 --- a/src/main/kotlin/com/beust/kobalt/Main.kt +++ b/src/main/kotlin/com/beust/kobalt/Main.kt @@ -157,7 +157,11 @@ private class Main @Inject constructor( } else if (args.usage) { jc.usage() } else if (args.serverMode) { - val port = KobaltServer(args.force, { pluginInfo.shutdown()}).call() + // --server + val port = KobaltServer(args.force, + { buildFile -> initForBuildFile(BuildFile(Paths.get(buildFile), buildFile), args)}, + { cleanUp() }) + .call() } else { // Options that don't need Build.kt to be parsed first if (args.gc) { @@ -172,37 +176,8 @@ private class Main @Inject constructor( if (!buildFile.exists()) { error(buildFile.path.toFile().path + " does not exist") } else { - 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 - // - allProjects.forEach { project -> - pluginInfo.repoContributors.forEach { - it.reposFor(project).forEach { - Kobalt.addRepo(it) - } - } - } - - // - // Run all the dependencies through the IDependencyInterceptors - // - runClasspathInterceptors(allProjects) - - log(2, "Final list of repos:\n " + Kobalt.repos.joinToString("\n ")) - - // - // Call apply() on all plug-ins now that the repos are set up - // - plugins.applyPlugins(Kobalt.context!!, allProjects) + val allProjects = initForBuildFile(buildFile, args) // DONOTCOMMIT // val data = dependencyData.dependenciesDataFor(homeDir("kotlin/klaxon/kobalt/src/Build.kt"), Args()) @@ -245,6 +220,47 @@ private class Main @Inject constructor( return result } + private fun cleanUp() { + pluginInfo.shutdown() + taskManager.shutdown() + } + + private fun initForBuildFile(buildFile: BuildFile, args: Args): List { + 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 + // + allProjects.forEach { project -> + pluginInfo.repoContributors.forEach { + it.reposFor(project).forEach { + Kobalt.addRepo(it) + } + } + } + + // + // Run all the dependencies through the IDependencyInterceptors + // + runClasspathInterceptors(allProjects) + + log(2, "Final list of repos:\n " + Kobalt.repos.joinToString("\n ")) + + // + // Call apply() on all plug-ins now that the repos are set up + // + plugins.applyPlugins(Kobalt.context!!, allProjects) + + return allProjects + } + private fun displayTasks() { // // List of tasks, --tasks 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 0f6acf6d..21d5f59f 100644 --- a/src/main/kotlin/com/beust/kobalt/app/remote/GetDependenciesCommand.kt +++ b/src/main/kotlin/com/beust/kobalt/app/remote/GetDependenciesCommand.kt @@ -1,6 +1,7 @@ package com.beust.kobalt.app.remote import com.beust.kobalt.Args +import com.beust.kobalt.api.Project import com.beust.kobalt.internal.remote.ICommand import com.beust.kobalt.internal.remote.ICommandSender import com.google.gson.Gson @@ -17,8 +18,9 @@ class GetDependenciesCommand @Inject constructor(val args: Args, val dependencyD override val name = "getDependencies" - override fun run(sender: ICommandSender, received: JsonObject) { + override fun run(sender: ICommandSender, received: JsonObject, initCallback: (String) -> List) { val buildFile = received.get("buildFile").asString + val projects = initCallback(buildFile) val dd = dependencyData.dependenciesDataFor(buildFile, args) val data = toCommandData(Gson().toJson(dd), dd.errorMessage) sender.sendData(data) diff --git a/src/main/kotlin/com/beust/kobalt/app/remote/KobaltClient.kt b/src/main/kotlin/com/beust/kobalt/app/remote/KobaltClient.kt index e3712918..e474998d 100644 --- a/src/main/kotlin/com/beust/kobalt/app/remote/KobaltClient.kt +++ b/src/main/kotlin/com/beust/kobalt/app/remote/KobaltClient.kt @@ -36,7 +36,7 @@ class ServerProcess { var port = launchPrivate() while (port == 0) { executor.submit { - KobaltServer(force = true, shutdownCallback = {}).call() + KobaltServer(force = true, initCallback = { buildFile -> emptyList()}, cleanUpCallback = {}).call() } // launchServer(ProcessUtil.findAvailablePort()) port = launchPrivate() diff --git a/src/main/kotlin/com/beust/kobalt/app/remote/KobaltServer.kt b/src/main/kotlin/com/beust/kobalt/app/remote/KobaltServer.kt index 6973e362..881b41d1 100644 --- a/src/main/kotlin/com/beust/kobalt/app/remote/KobaltServer.kt +++ b/src/main/kotlin/com/beust/kobalt/app/remote/KobaltServer.kt @@ -1,6 +1,7 @@ package com.beust.kobalt.app.remote import com.beust.kobalt.api.Kobalt +import com.beust.kobalt.api.Project import com.beust.kobalt.homeDir import com.beust.kobalt.internal.remote.CommandData import com.beust.kobalt.internal.remote.ICommandSender @@ -17,7 +18,16 @@ import java.net.SocketException import java.util.* import java.util.concurrent.Callable -class KobaltServer(val force: Boolean, val shutdownCallback: () -> Unit) : Callable, ICommandSender { +/** + * Launch a Kobalt server. If @param{force} is specified, a new server will be launched even if one was detected + * to be already running (from the ~/.kobalt/kobaltServer.properties file). + * + * The callbacks are used to initialize and clean up the state before and after each command, so that Kobalt's state + * can be properly reset, making the server reentrant. + */ +class KobaltServer(val force: Boolean, + val initCallback: (String) -> List, + val cleanUpCallback: () -> Unit) : Callable, ICommandSender { // var outgoing: PrintWriter? = null val pending = arrayListOf() @@ -110,13 +120,13 @@ class KobaltServer(val force: Boolean, val shutdownCallback: () -> Unit) : Calla log(1, "Quitting") quit = true } else { - runCommand(jo) + runCommand(jo, initCallback) // Done, send a quit to the client sendData(CommandData("quit", "")) // Clean up all the plug-in actors - shutdownCallback() + cleanUpCallback() line = serverInfo.reader.readLine() } } @@ -137,10 +147,10 @@ class KobaltServer(val force: Boolean, val shutdownCallback: () -> Unit) : Calla } } - private fun runCommand(jo: JsonObject) { + private fun runCommand(jo: JsonObject, initCallback: (String) -> List) { val command = jo.get("name").asString if (command != null) { - (COMMANDS[command] ?: COMMANDS["ping"])!!.run(this, jo) + (COMMANDS[command] ?: COMMANDS["ping"])!!.run(this, jo, initCallback) } else { error("Did not find a name in command: $jo") }