diff --git a/src/main/kotlin/com/beust/kobalt/internal/TaskManager.kt b/src/main/kotlin/com/beust/kobalt/internal/TaskManager.kt index dee8304d..5e439e7f 100644 --- a/src/main/kotlin/com/beust/kobalt/internal/TaskManager.kt +++ b/src/main/kotlin/com/beust/kobalt/internal/TaskManager.kt @@ -6,9 +6,11 @@ import com.beust.kobalt.api.PluginTask import com.beust.kobalt.api.Project import com.beust.kobalt.misc.KobaltLogger import com.beust.kobalt.maven.KobaltException +import com.beust.kobalt.plugins import com.google.common.collect.HashMultimap import com.google.common.collect.TreeMultimap -import java.util.HashSet +import org.jetbrains.kotlin.cfg.pseudocode.or +import java.util.* import javax.inject.Inject import javax.inject.Singleton @@ -34,141 +36,157 @@ public class TaskManager @Inject constructor(val plugins: Plugins, val args: Arg wrapAfter.put(task1, task2) } - class TaskInfo(val id: String) { + data class TaskInfo(val id: String) { + constructor(project: String, task: String) : this(project + ":" + task) + val project: String? get() = if (id.contains(":")) id.split(":").get(0) else null val task: String get() = if (id.contains(":")) id.split(":").get(1) else id + fun matches(projectName: String) = project == null || project == projectName } public fun runTargets(targets: List, projects: List) { - val tasksByNames = HashMultimap.create() - + val tasksAlreadyRun = hashSetOf() projects.forEach { project -> + val projectName = project.name!! + val tasksByNames = hashMapOf() + plugins.allTasks.filter { + it.project.name == project.name + }.forEach { + tasksByNames.put(it.name, it) + } + log(1, "") log(1, " Building project ${project.name}") log(1, "") - val allTasksByNames = hashMapOf() - plugins.allTasks.forEach { allTasksByNames.put(TaskInfo(it.name).task, it)} + targets.forEach { target -> + tasksAlreadyRun.add(TaskInfo(projectName, target).id) - // - // Locate all the tasks - // - plugins.allTasks.filter { it.project.name == project.name }.forEach { rt -> - tasksByNames.put(rt.name, rt) - if (rt.runBefore.size() > 0) { - rt.runBefore.forEach { d -> - runBefore.put(rt.name, d) - } - } - } + val graph = DynamicGraph() - val freeTaskMap = hashMapOf() - tasksByNames.keySet().forEach { - if (! runBefore.containsKey(it)) freeTaskMap.put(it, tasksByNames.get(it).elementAt(0)) - } + val ti = TaskInfo(target) + if (ti.matches(projectName)) { + val task = tasksByNames.get(ti.task) + if (task != null && task.plugin.accept(project)) { + // + // Add free tasks as nodes to the graph + // + calculateFreeTasks(tasksByNames).forEach { + val thisTaskInfo = TaskInfo(projectName, it.name) + if (! tasksAlreadyRun.contains(thisTaskInfo.id)) { + graph.addNode(it) + tasksAlreadyRun.add(thisTaskInfo.id) + } + } - log(2, "Free tasks: ${freeTaskMap.keySet()}") - log(2, "Dependent tasks:") - runBefore.keySet().forEach { t -> - log(2, " ${t} -> ${runBefore.get(t)}}") - } - - // - // Find the tasks required to run the targets and add them to the dynamic graph - // - val transitiveClosure = hashSetOf() - val seen = HashSet(targets) - val toProcess = HashSet(targets) - var done = false - while (!done) { - val newToProcess = hashSetOf() - log(3, "toProcess size: " + toProcess.size()) - toProcess.forEach { target -> - val pluginTask = allTasksByNames.get(target) - // Only calculate the transitive closure for this target if its plug-in accepts the - // current project - if (pluginTask != null && pluginTask.plugin.accept(project)) { - log(3, "Processing ${target}") - val actualTarget = - if (target.contains(":")) { - // The target specifies a project explicitly - target.split(":").let { - val projectName = it[0] - if (projectName == project.name) { - it[1] - } else { - null - } + // + // Add the transitive closure of the current task as edges to the graph + // + calculateTransitiveClosure(project, tasksByNames, ti, task).forEach { pluginTask -> + val rb = runBefore.get(pluginTask.name) + rb.forEach { + val to = tasksByNames.get(it) + if (to != null) { + val taskInfo = TaskInfo(projectName, to.name) + if (! tasksAlreadyRun.contains(taskInfo.id)) { + graph.addEdge(pluginTask, to) + tasksAlreadyRun.add(taskInfo.id) } } else { - target - } - if (actualTarget != null) { - wrapAfter.get(actualTarget).let { - newToProcess.addAll(it) - } - transitiveClosure.add(actualTarget) - val tasks = tasksByNames.get(actualTarget) - if (tasks.isEmpty()) { - throw KobaltException("Unknown task: ${target}") - } - tasks.forEach { task -> - val dependencyNames = runBefore.get(task.name) - dependencyNames.forEach { dependencyName -> - if (!seen.contains(dependencyName)) { - newToProcess.add(dependencyName) - seen.add(dependencyName) - } + throw KobaltException("Should have found $it") } } - } else { - log(2, "Target ${target} specified so not running it for project ${project.name}") - } - } else if (pluginTask == null) { - throw AssertionError("Should have found the task for $target") - } - } - done = newToProcess.isEmpty() - toProcess.clear() - toProcess.addAll(newToProcess) - } - - // - // Create a dynamic graph with the transitive closure - // - val graph = DynamicGraph() - freeTaskMap.values().filter { transitiveClosure.contains(it.name) } forEach { graph.addNode(it) } - runBefore.entries().filter { - transitiveClosure.contains(it.key) - }.forEach { entry -> - plugins.findTasks(entry.key).filter { it.project.name == project.name }.forEach { from -> - plugins.findTasks(entry.value).filter { it.project.name == project.name }.forEach { to -> - if (from.project.name == to.project.name) { - graph.addEdge(from, to) } } - } - } - // - // Run the dynamic graph - // - val factory = object : IThreadWorkerFactory { - override public fun createWorkers(nodes: List): List> { - val result = arrayListOf>() - nodes.forEach { - result.add(TaskWorker(arrayListOf(it), args.dryRun)) + // + // Now that we have a full graph, run it + // + val factory = object : IThreadWorkerFactory { + override public fun createWorkers(nodes: List): List> { + val result = arrayListOf>() + nodes.forEach { + result.add(TaskWorker(arrayListOf(it), args.dryRun)) + } + return result + } } - return result + + val executor = DynamicGraphExecutor(graph, factory) + executor.run() } } - - val executor = DynamicGraphExecutor(graph, factory) - executor.run() } } + + /** + * Find the free tasks of the graph. + */ + private fun calculateFreeTasks(tasksByNames: Map): Collection { + val freeTaskMap = hashMapOf() + tasksByNames.keySet().forEach { + if (! runBefore.containsKey(it)) { + freeTaskMap.put(it, tasksByNames.get(it)!!) + } + } + + log(2, "Free tasks: ${freeTaskMap.keySet()}") + log(2, "Dependent tasks:") + runBefore.keySet().forEach { t -> + log(2, " ${t} -> ${runBefore.get(t)}}") + } + + return freeTaskMap.values() + } + + /** + * Find the transitive closure for the given TaskInfo + */ + private fun calculateTransitiveClosure(project: Project, tasksByNames: Map, ti: TaskInfo, + task: PluginTask): HashSet { + log(3, "Processing ${ti.task}") + + val transitiveClosure = hashSetOf() + val seen = hashSetOf(ti.task) + val toProcess = hashSetOf(ti) + var done = false + while (! done) { + val newToProcess = hashSetOf() + log(3, "toProcess size: " + toProcess.size()) + toProcess.forEach { target -> + +// wrapAfter.get(ti.id).let { +// newToProcess.addAll(it) +// } + + val currentTask = TaskInfo(project.name!!, target.task) + transitiveClosure.add(tasksByNames.get(currentTask.task)!!) + val task = tasksByNames.get(target.task) + if (task == null) { + throw KobaltException("Unknown task: ${target}") + } else { + val dependencyNames = runBefore.get(task.name) + dependencyNames.forEach { dependencyName -> + if (! seen.contains(dependencyName)) { + newToProcess.add(currentTask) + seen.add(dependencyName) + } + } + + runBefore.get(task.name).forEach { + newToProcess.add(TaskInfo(project.name!!, it)) + } + } + } + done = newToProcess.isEmpty() + toProcess.clear() + toProcess.addAll(newToProcess) + } + + return transitiveClosure + } } class TaskWorker(val tasks: List, val dryRun: Boolean) : IWorker, KobaltLogger {