From ff1e3c2d13a5f603fc171b9654231ca381ea37b0 Mon Sep 17 00:00:00 2001 From: Cedric Beust Date: Mon, 6 Jun 2016 22:08:30 -0800 Subject: [PATCH] GITHUB-230: Fail build if invalid project requested. Fixes https://github.com/cbeust/kobalt/issues/230 --- .../kobalt/internal/IncrementalManager.kt | 8 +-- .../com/beust/kobalt/internal/TaskManager.kt | 69 ++++++++++++++----- .../beust/kobalt/internal/TaskManagerTest.kt | 2 +- 3 files changed, 58 insertions(+), 21 deletions(-) diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/IncrementalManager.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/IncrementalManager.kt index 6b134f42..a0211b9c 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/IncrementalManager.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/IncrementalManager.kt @@ -19,16 +19,16 @@ import java.nio.file.Files import java.nio.file.Paths import java.util.* -data class TaskInfo(val taskName: String, var inputChecksum: String? = null, var outputChecksum: String? = null) - -class BuildInfo(var tasks: List) - /** * Manage the file .kobalt/buildInfo.json, which keeps track of input and output checksums to manage * incremental builds. */ class IncrementalManager @Inject constructor(val args: Args, @Assisted val fileName : String) { + private data class TaskInfo(val taskName: String, var inputChecksum: String? = null, var outputChecksum: String? = null) + + private class BuildInfo(var tasks: List) + interface IFactory { fun create(@Assisted fileName: String = IncrementalManager.BUILD_INFO_FILE) : IncrementalManager } 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 432e1471..c590197d 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 @@ -81,11 +81,44 @@ class TaskManager @Inject constructor(val args: Args, } } - fun runTargets(passedTaskNames: List, projects: List): RunTargetResult { + fun runTargets(passedTaskNames: List, allProjects: List): RunTargetResult { + val taskInfos = calculateDependentTaskNames(passedTaskNames, allProjects) + val projectsToRun = findProjectsToRun(passedTaskNames, allProjects) + return runProjects(taskInfos, projectsToRun) + } + + /** + * Determine which projects to run based on the request tasks. Also make sure that all the requested projects + * exist. + */ + private fun findProjectsToRun(passedTaskNames: List, projects: List) : List { + + // Validate projects + val result = arrayListOf() + val projectMap = HashMap().apply { + projects.forEach { put(it.name, it)} + } + + // Extract all the projects we need to run from the tasks + val taskInfos = calculateDependentTaskNames(passedTaskNames, projects) + taskInfos.forEach { + val p = it.project + if (p != null) { + if (! projectMap.containsKey(p)) { + throw KobaltException("Unknown project: ${it.project}") + } + result.add(projectMap[it.project]!!) + } + } + + // If at least one task didn't specify a project, run them all + return if (result.any()) result else projects + } + + private fun runProjects(taskInfos: List, projects: List) : RunTargetResult { var result = 0 val failedProjects = hashSetOf() val messages = Collections.synchronizedList(arrayListOf()) - val taskNames = calculateDependentTaskNames(passedTaskNames, projects) projects.forEach { project -> AsciiArt.logBox("Building ${project.name}") @@ -112,7 +145,7 @@ class TaskManager @Inject constructor(val args: Args, log(3, " $it: " + tasksByNames.get(it)) } - val graph = createGraph2(project.name, taskNames, tasksByNames, + val graph = createGraph2(project.name, taskInfos, tasksByNames, dependsOn, reverseDependsOn, runBefore, runAfter, alwaysRunAfter, { task: ITask -> task.name }, { task: ITask -> task.plugin.accept(project) }) @@ -138,6 +171,7 @@ class TaskManager @Inject constructor(val args: Args, } } } + return RunTargetResult(result, messages) } @@ -146,11 +180,11 @@ class TaskManager @Inject constructor(val args: Args, * see if that project depends on others and if it does, invoke these tasks on all of them. This * function returns all these task names (including dependent). */ - private fun calculateDependentTaskNames(taskNames: List, projects: List): List { + private fun calculateDependentTaskNames(taskNames: List, projects: List): List { val projectMap = hashMapOf().apply { projects.forEach { put(it.name, it)} } - val result = ArrayList(taskNames) + val result = ArrayList(taskNames.map { TaskInfo(it) }) val toProcess = ArrayList(taskNames) val newToProcess = arrayListOf() val seen = hashSetOf() @@ -161,7 +195,7 @@ class TaskManager @Inject constructor(val args: Args, projectMap[ti.project]?.let { project -> project.projectExtra.dependsOn.forEach { dp -> val newTask = TaskInfo(dp.projectName, ti.taskName).id - result.add(newTask) + result.add(TaskInfo(newTask)) if (! seen.contains(newTask)) { newToProcess.add(newTask) seen.add(newTask) @@ -188,7 +222,8 @@ class TaskManager @Inject constructor(val args: Args, * we'll be adding to the graph while @toName extracts the name of a node. */ @VisibleForTesting - fun createGraph2(projectName: String, taskNames: List, nodeMap: Multimap, + fun createGraph2(projectName: String, passedTasks: List, + nodeMap: Multimap, dependsOn: Multimap, reverseDependsOn: Multimap, runBefore: Multimap, @@ -214,9 +249,9 @@ class TaskManager @Inject constructor(val args: Args, } // - // Turn the task names into the more useful TaskInfo and do some sanity checking on the way + // Keep only the tasks we need to run. // - val taskInfos = taskNames.map { TaskInfo(it) }.filter { + val taskInfos = passedTasks.filter { if (!nodeMap.keys().contains(it.taskName)) { throw KobaltException("Unknown task: $it") } @@ -300,14 +335,16 @@ class TaskManager @Inject constructor(val args: Args, // runBefore and runAfter (task ordering) are only considered for explicit tasks (tasks that were // explicitly requested by the user) // - runBefore[taskName].forEach { from -> - if (taskNames.contains(from)) { - addEdge(result, from, taskName, newToProcess, "runBefore") + passedTasks.map { it.id }.let { taskNames -> + runBefore[taskName].forEach { from -> + if (taskNames.contains(from)) { + addEdge(result, from, taskName, newToProcess, "runBefore") + } } - } - runAfter[taskName].forEach { to -> - if (taskNames.contains(to)) { - addEdge(result, taskName, to, newToProcess, "runAfter") + runAfter[taskName].forEach { to -> + if (taskNames.contains(to)) { + addEdge(result, taskName, to, newToProcess, "runAfter") + } } } seen.add(taskName) diff --git a/src/test/kotlin/com/beust/kobalt/internal/TaskManagerTest.kt b/src/test/kotlin/com/beust/kobalt/internal/TaskManagerTest.kt index ee1caa50..252e1228 100644 --- a/src/test/kotlin/com/beust/kobalt/internal/TaskManagerTest.kt +++ b/src/test/kotlin/com/beust/kobalt/internal/TaskManagerTest.kt @@ -80,7 +80,7 @@ class TaskManagerTest @Inject constructor(val taskManager: TaskManager) { } } - val graph = taskManager.createGraph2("", tasks, dependencies, + val graph = taskManager.createGraph2("", tasks.map { TaskManager.TaskInfo(it) }, dependencies, dependsOn, reverseDependsOn, runBefore, runAfter, alwaysRunAfter, { it }, { t -> true }) val result = DryRunGraphExecutor(graph).run()