From 635cc9cd3cbe1527351f024feb787ecdfda4bd3d Mon Sep 17 00:00:00 2001 From: Cedric Beust Date: Wed, 3 Aug 2016 19:01:47 -0800 Subject: [PATCH] Build listeners for parallel builds. Also showing timings in the build report. --- .../kobalt/internal/BaseProjectRunner.kt | 17 +++- .../beust/kobalt/internal/BuildListeners.kt | 78 ++++++++++++------- .../kobalt/internal/ParallelProjectRunner.kt | 15 +++- .../internal/SequentialProjectRunner.kt | 18 ++--- .../com/beust/kobalt/internal/TaskManager.kt | 11 +-- 5 files changed, 90 insertions(+), 49 deletions(-) diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/BaseProjectRunner.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/BaseProjectRunner.kt index 40859d83..c75e4330 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/BaseProjectRunner.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/BaseProjectRunner.kt @@ -1,6 +1,8 @@ package com.beust.kobalt.internal +import com.beust.kobalt.api.KobaltContext import com.beust.kobalt.api.Project +import com.beust.kobalt.api.ProjectBuildStatus import com.beust.kobalt.misc.log import com.google.common.annotations.VisibleForTesting import com.google.common.collect.ArrayListMultimap @@ -15,6 +17,20 @@ abstract class BaseProjectRunner { companion object { val LOG_LEVEL = TaskManager.LOG_LEVEL + fun runBuildListenersForProject(project: Project, context: KobaltContext, start: Boolean, + status: ProjectBuildStatus = ProjectBuildStatus.SUCCESS) { + context.pluginInfo.buildListeners.forEach { + if (start) it.projectStart(project, context) else it.projectEnd(project, context, status) + } + } + + fun runBuildListenersForTask(project: Project, context: KobaltContext, taskName: String, start: Boolean, + success: Boolean = false) { + context.pluginInfo.buildListeners.forEach { + if (start) it.taskStart(project, context, taskName) else it.taskEnd(project, context, taskName, success) + } + } + /** * Create a graph representing the tasks and their dependencies. That graph will then be run * in topological order. @@ -156,5 +172,4 @@ abstract class BaseProjectRunner { return result } } - } diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/BuildListeners.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/BuildListeners.kt index 96b6e10e..e4876785 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/BuildListeners.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/BuildListeners.kt @@ -7,56 +7,82 @@ import com.beust.kobalt.misc.log import java.util.concurrent.ConcurrentHashMap /** - * Record timings and display them at the end of the build. + * Record timings and statuses for tasks and projects and display them at the end of the build. */ class BuildListeners : IBuildListener, IBuildReportContributor { class ProfilerInfo(val taskName: String, val durationMillis: Long) + class ProjectInfo(val projectName: String, var durationMillis: Long = 0) private val startTimes = ConcurrentHashMap() private val timings = arrayListOf() + private val projectInfos = hashMapOf() + private var hasFailures = false + private val args: Args get() = Kobalt.INJECTOR.getInstance(Args::class.java) + // IBuildListener override fun taskStart(project: Project, context: KobaltContext, taskName: String) { startTimes.put(taskName, System.currentTimeMillis()) - } - - override fun taskEnd(project: Project, context: KobaltContext, taskName: String, success: Boolean) { - startTimes[taskName]?.let { - timings.add(ProfilerInfo(taskName, System.currentTimeMillis() - it)) + if (! projectInfos.containsKey(project.name)) { + projectInfos.put(project.name, ProjectInfo(project.name)) } } + // IBuildListener + override fun taskEnd(project: Project, context: KobaltContext, taskName: String, success: Boolean) { + if (! success) hasFailures = true + startTimes[taskName]?.let { + val taskTime = System.currentTimeMillis() - it + timings.add(ProfilerInfo(taskName, taskTime)) + projectInfos[project.name]?.let { + it.durationMillis += taskTime.toLong() + } + } + } + + private val projectStatuses = arrayListOf>() + + // IBuildListener + override fun projectEnd(project: Project, context: KobaltContext, status: ProjectBuildStatus) { + projectStatuses.add(Pair(project, status)) + } + + // IBuildReportContributor override fun generateReport(context: KobaltContext) { - val profiling = Kobalt.INJECTOR.getInstance(Args::class.java).profiling + fun formatMillis(millis: Long, format: String) = String.format(format, millis.toDouble() / 1000) + fun formatMillisRight(millis: Long, length: Int) = formatMillis(millis, "%1\$$length.2f") + fun formatMillisLeft(millis: Long, length: Int) = formatMillis(millis, "%1\$-$length.2f") + + val profiling = args.profiling if (profiling) { log(1, "\n" + AsciiArt.horizontalSingleLine + " Timings (in seconds)") timings.sortedByDescending { it.durationMillis }.forEach { - log(1, String.format("%1$10.2f", it.durationMillis.toDouble() / 1000) - + " " + it.taskName) + log(1, formatMillisRight(it.durationMillis, 10) + " " + it.taskName) } log(1, "\n") } fun col1(s: String) = String.format(" %1\$-30s", s) - fun col2(s: String) = String.format(" %1\$-10s", s) + fun col2(s: String) = String.format(" %1\$-13s", s) + fun col3(s: String) = String.format(" %1\$-8s", s) - val line = listOf(col1("Project"), col2("Build status")).joinToString(AsciiArt.verticalBar) - AsciiArt.logBox(listOf(line), AsciiArt.bottomLeft2, AsciiArt.bottomRight2) - projectStatuses.forEach { pair -> - val cl = listOf(col1(pair.first.name), col2(pair.second.toString())).joinToString(AsciiArt.verticalBar) - log(1, " " + AsciiArt.verticalBar + " " + cl + " " + AsciiArt.verticalBar) - } - log(1, " " + AsciiArt.lowerBox(line.length)) + // Only print the build report if there is more than one project and at least one of them failed +// if (timings.size > 1 && hasFailures) { + val line = listOf(col1("Project"), col2("Build status"), col3("Time")) + .joinToString(AsciiArt.verticalBar) + AsciiArt.logBox(listOf(line), AsciiArt.bottomLeft2, AsciiArt.bottomRight2) + projectStatuses.forEach { pair -> + val projectName = pair.first.name + val cl = listOf(col1(projectName), col2(pair.second.toString()), + col3(formatMillisLeft(projectInfos[projectName]!!.durationMillis, 8))) + .joinToString(AsciiArt.verticalBar) + log(1, " " + AsciiArt.verticalBar + " " + cl + " " + AsciiArt.verticalBar) + } + log(1, " " + AsciiArt.lowerBox(line.length)) + if (args.parallel) log(1, "Sequential build time would be " + + (projectInfos.values.sumByDouble { it.durationMillis.toDouble() }) / 1000 + " seconds") +// } } - -// override fun projectStart(project: Project, context: KobaltContext) {} - - private val projectStatuses = arrayListOf>() - - override fun projectEnd(project: Project, context: KobaltContext, status: ProjectBuildStatus) { - projectStatuses.add(Pair(project, status)) - } - } diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/ParallelProjectRunner.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/ParallelProjectRunner.kt index b6c60284..bee0248b 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/ParallelProjectRunner.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/ParallelProjectRunner.kt @@ -3,7 +3,9 @@ package com.beust.kobalt.internal import com.beust.kobalt.Args import com.beust.kobalt.TaskResult import com.beust.kobalt.api.ITask +import com.beust.kobalt.api.Kobalt import com.beust.kobalt.api.Project +import com.beust.kobalt.api.ProjectBuildStatus import com.beust.kobalt.misc.log import com.google.common.collect.ListMultimap import com.google.common.collect.TreeMultimap @@ -31,6 +33,8 @@ class ParallelProjectRunner(val tasksByNames: (Project) -> ListMultimap { + val context = Kobalt.context!! + runBuildListenersForProject(project, context, true) val tasksByNames = tasksByNames(project) val graph = createTaskGraph(project.name, taskInfos, tasksByNames, dependsOn, reverseDependsOn, runBefore, runAfter, alwaysRunAfter, @@ -42,16 +46,23 @@ class ParallelProjectRunner(val tasksByNames: (Project) -> ListMultimap val tasks = tasksByNames[node.name] tasks.forEach { task -> + + runBuildListenersForTask(project, context, task.name, start = true) log(1, "===== " + project.name + ":" + task.name) - val tr = if (dryRun) TaskResult2(true, null, task) else task.call() + val thisResult = if (dryRun) TaskResult2(true, null, task) else task.call() if (lastResult.success) { - lastResult = tr + lastResult = thisResult } + runBuildListenersForTask(project, context, task.name, start = false, + success = thisResult.success) } } graph.freeNodes.forEach { graph.removeNode(it) } } + runBuildListenersForProject(project, context, false, + if (lastResult.success) ProjectBuildStatus.SUCCESS else ProjectBuildStatus.FAILED) + return TaskResult2(lastResult.success, lastResult.errorMessage, this) } diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/SequentialProjectRunner.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/SequentialProjectRunner.kt index 82fd9c48..2316ae00 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/SequentialProjectRunner.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/SequentialProjectRunner.kt @@ -3,7 +3,10 @@ package com.beust.kobalt.internal import com.beust.kobalt.Args import com.beust.kobalt.AsciiArt import com.beust.kobalt.TaskResult -import com.beust.kobalt.api.* +import com.beust.kobalt.api.ITask +import com.beust.kobalt.api.Kobalt +import com.beust.kobalt.api.Project +import com.beust.kobalt.api.ProjectBuildStatus import com.beust.kobalt.misc.Strings import com.beust.kobalt.misc.kobaltError import com.beust.kobalt.misc.log @@ -29,13 +32,6 @@ class SequentialProjectRunner(val tasksByNames: (Project) -> ListMultimap() val messages = Collections.synchronizedList(arrayListOf()) - fun runProjectListeners(project: Project, context: KobaltContext, start: Boolean, - status: ProjectBuildStatus = ProjectBuildStatus.SUCCESS) { - context.pluginInfo.buildListeners.forEach { - if (start) it.projectStart(project, context) else it.projectEnd(project, context, status) - } - } - val context = Kobalt.context!! projects.forEach { project -> AsciiArt.logBox("Building ${project.name}") @@ -50,12 +46,12 @@ class SequentialProjectRunner(val tasksByNames: (Project) -> ListMultimap 0) { log(2, "Marking project ${project.name} as skipped") failedProjects.add(project.name) - runProjectListeners(project, context, false, ProjectBuildStatus.SKIPPED) + runBuildListenersForProject(project, context, false, ProjectBuildStatus.SKIPPED) kobaltError("Not building project ${project.name} since it depends on failed " + Strings.pluralize(fp.size, "project") + " " + fp.joinToString(",")) } else { - runProjectListeners(project, context, true) + runBuildListenersForProject(project, context, true) // There can be multiple tasks by the same name (e.g. PackagingPlugin and AndroidPlugin both // define "install"), so use a multimap @@ -88,7 +84,7 @@ class SequentialProjectRunner(val tasksByNames: (Project) -> ListMultimap, val dryRun: Boolean, val pluginInfo: PluginInfo) : IWorker { - private fun runBuildListeners(project: Project, context: KobaltContext, taskName: String, start: Boolean, - success: Boolean = false) { - context.pluginInfo.buildListeners.forEach { - if (start) it.taskStart(project, context, taskName) else it.taskEnd(project, context, taskName, success) - } - } - override fun call() : TaskResult2 { if (tasks.size > 0) { tasks[0].let { @@ -318,9 +311,9 @@ class TaskWorker(val tasks: List, val dryRun: Boolean, val pluginInfo: Pl val context = Kobalt.context!! tasks.forEach { val name = it.project.name + ":" + it.name - runBuildListeners(it.project, context, name, start = true) + BaseProjectRunner.runBuildListenersForTask(it.project, context, name, start = true) val tr = if (dryRun) TaskResult() else it.call() - runBuildListeners(it.project, context, name, start = false, success = tr.success) + BaseProjectRunner.runBuildListenersForTask(it.project, context, name, start = false, success = tr.success) success = success and tr.success if (tr.errorMessage != null) errorMessages.add(tr.errorMessage) }