1
0
Fork 0
mirror of https://github.com/ethauvin/kobalt.git synced 2025-04-26 08:27:12 -07:00

Build listeners for parallel builds.

Also showing timings in the build report.
This commit is contained in:
Cedric Beust 2016-08-03 19:01:47 -08:00
parent 38495b0353
commit 635cc9cd3c
5 changed files with 90 additions and 49 deletions

View file

@ -1,6 +1,8 @@
package com.beust.kobalt.internal package com.beust.kobalt.internal
import com.beust.kobalt.api.KobaltContext
import com.beust.kobalt.api.Project import com.beust.kobalt.api.Project
import com.beust.kobalt.api.ProjectBuildStatus
import com.beust.kobalt.misc.log import com.beust.kobalt.misc.log
import com.google.common.annotations.VisibleForTesting import com.google.common.annotations.VisibleForTesting
import com.google.common.collect.ArrayListMultimap import com.google.common.collect.ArrayListMultimap
@ -15,6 +17,20 @@ abstract class BaseProjectRunner {
companion object { companion object {
val LOG_LEVEL = TaskManager.LOG_LEVEL 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 * Create a graph representing the tasks and their dependencies. That graph will then be run
* in topological order. * in topological order.
@ -156,5 +172,4 @@ abstract class BaseProjectRunner {
return result return result
} }
} }
} }

View file

@ -7,56 +7,82 @@ import com.beust.kobalt.misc.log
import java.util.concurrent.ConcurrentHashMap 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 BuildListeners : IBuildListener, IBuildReportContributor {
class ProfilerInfo(val taskName: String, val durationMillis: Long) class ProfilerInfo(val taskName: String, val durationMillis: Long)
class ProjectInfo(val projectName: String, var durationMillis: Long = 0)
private val startTimes = ConcurrentHashMap<String, Long>() private val startTimes = ConcurrentHashMap<String, Long>()
private val timings = arrayListOf<ProfilerInfo>() private val timings = arrayListOf<ProfilerInfo>()
private val projectInfos = hashMapOf<String, ProjectInfo>()
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) { override fun taskStart(project: Project, context: KobaltContext, taskName: String) {
startTimes.put(taskName, System.currentTimeMillis()) startTimes.put(taskName, System.currentTimeMillis())
if (! projectInfos.containsKey(project.name)) {
projectInfos.put(project.name, ProjectInfo(project.name))
}
} }
// IBuildListener
override fun taskEnd(project: Project, context: KobaltContext, taskName: String, success: Boolean) { override fun taskEnd(project: Project, context: KobaltContext, taskName: String, success: Boolean) {
if (! success) hasFailures = true
startTimes[taskName]?.let { startTimes[taskName]?.let {
timings.add(ProfilerInfo(taskName, System.currentTimeMillis() - it)) val taskTime = System.currentTimeMillis() - it
timings.add(ProfilerInfo(taskName, taskTime))
projectInfos[project.name]?.let {
it.durationMillis += taskTime.toLong()
}
} }
} }
private val projectStatuses = arrayListOf<Pair<Project, ProjectBuildStatus>>()
// IBuildListener
override fun projectEnd(project: Project, context: KobaltContext, status: ProjectBuildStatus) {
projectStatuses.add(Pair(project, status))
}
// IBuildReportContributor
override fun generateReport(context: KobaltContext) { 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) { if (profiling) {
log(1, "\n" + AsciiArt.horizontalSingleLine + " Timings (in seconds)") log(1, "\n" + AsciiArt.horizontalSingleLine + " Timings (in seconds)")
timings.sortedByDescending { it.durationMillis }.forEach { timings.sortedByDescending { it.durationMillis }.forEach {
log(1, String.format("%1$10.2f", it.durationMillis.toDouble() / 1000) log(1, formatMillisRight(it.durationMillis, 10) + " " + it.taskName)
+ " " + it.taskName)
} }
log(1, "\n") log(1, "\n")
} }
fun col1(s: String) = String.format(" %1\$-30s", s) 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) // 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) AsciiArt.logBox(listOf(line), AsciiArt.bottomLeft2, AsciiArt.bottomRight2)
projectStatuses.forEach { pair -> projectStatuses.forEach { pair ->
val cl = listOf(col1(pair.first.name), col2(pair.second.toString())).joinToString(AsciiArt.verticalBar) 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.verticalBar + " " + cl + " " + AsciiArt.verticalBar)
} }
log(1, " " + AsciiArt.lowerBox(line.length)) 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<Pair<Project, ProjectBuildStatus>>()
override fun projectEnd(project: Project, context: KobaltContext, status: ProjectBuildStatus) {
projectStatuses.add(Pair(project, status))
}
} }

View file

@ -3,7 +3,9 @@ package com.beust.kobalt.internal
import com.beust.kobalt.Args import com.beust.kobalt.Args
import com.beust.kobalt.TaskResult import com.beust.kobalt.TaskResult
import com.beust.kobalt.api.ITask import com.beust.kobalt.api.ITask
import com.beust.kobalt.api.Kobalt
import com.beust.kobalt.api.Project import com.beust.kobalt.api.Project
import com.beust.kobalt.api.ProjectBuildStatus
import com.beust.kobalt.misc.log import com.beust.kobalt.misc.log
import com.google.common.collect.ListMultimap import com.google.common.collect.ListMultimap
import com.google.common.collect.TreeMultimap import com.google.common.collect.TreeMultimap
@ -31,6 +33,8 @@ class ParallelProjectRunner(val tasksByNames: (Project) -> ListMultimap<String,
else false else false
override fun call(): TaskResult2<ProjectTask> { override fun call(): TaskResult2<ProjectTask> {
val context = Kobalt.context!!
runBuildListenersForProject(project, context, true)
val tasksByNames = tasksByNames(project) val tasksByNames = tasksByNames(project)
val graph = createTaskGraph(project.name, taskInfos, tasksByNames, val graph = createTaskGraph(project.name, taskInfos, tasksByNames,
dependsOn, reverseDependsOn, runBefore, runAfter, alwaysRunAfter, dependsOn, reverseDependsOn, runBefore, runAfter, alwaysRunAfter,
@ -42,16 +46,23 @@ class ParallelProjectRunner(val tasksByNames: (Project) -> ListMultimap<String,
toProcess.forEach { node -> toProcess.forEach { node ->
val tasks = tasksByNames[node.name] val tasks = tasksByNames[node.name]
tasks.forEach { task -> tasks.forEach { task ->
runBuildListenersForTask(project, context, task.name, start = true)
log(1, "===== " + project.name + ":" + task.name) 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) { if (lastResult.success) {
lastResult = tr lastResult = thisResult
} }
runBuildListenersForTask(project, context, task.name, start = false,
success = thisResult.success)
} }
} }
graph.freeNodes.forEach { graph.removeNode(it) } 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) return TaskResult2(lastResult.success, lastResult.errorMessage, this)
} }

View file

@ -3,7 +3,10 @@ package com.beust.kobalt.internal
import com.beust.kobalt.Args import com.beust.kobalt.Args
import com.beust.kobalt.AsciiArt import com.beust.kobalt.AsciiArt
import com.beust.kobalt.TaskResult 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.Strings
import com.beust.kobalt.misc.kobaltError import com.beust.kobalt.misc.kobaltError
import com.beust.kobalt.misc.log import com.beust.kobalt.misc.log
@ -29,13 +32,6 @@ class SequentialProjectRunner(val tasksByNames: (Project) -> ListMultimap<String
val failedProjects = hashSetOf<String>() val failedProjects = hashSetOf<String>()
val messages = Collections.synchronizedList(arrayListOf<TaskManager.ProfilerInfo>()) val messages = Collections.synchronizedList(arrayListOf<TaskManager.ProfilerInfo>())
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!! val context = Kobalt.context!!
projects.forEach { project -> projects.forEach { project ->
AsciiArt.logBox("Building ${project.name}") AsciiArt.logBox("Building ${project.name}")
@ -50,12 +46,12 @@ class SequentialProjectRunner(val tasksByNames: (Project) -> ListMultimap<String
if (fp.size > 0) { if (fp.size > 0) {
log(2, "Marking project ${project.name} as skipped") log(2, "Marking project ${project.name} as skipped")
failedProjects.add(project.name) 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 " kobaltError("Not building project ${project.name} since it depends on failed "
+ Strings.pluralize(fp.size, "project") + Strings.pluralize(fp.size, "project")
+ " " + fp.joinToString(",")) + " " + fp.joinToString(","))
} else { } else {
runProjectListeners(project, context, true) runBuildListenersForProject(project, context, true)
// There can be multiple tasks by the same name (e.g. PackagingPlugin and AndroidPlugin both // There can be multiple tasks by the same name (e.g. PackagingPlugin and AndroidPlugin both
// define "install"), so use a multimap // define "install"), so use a multimap
@ -88,7 +84,7 @@ class SequentialProjectRunner(val tasksByNames: (Project) -> ListMultimap<String
failedProjects.add(project.name) failedProjects.add(project.name)
} }
runProjectListeners(project, context, false, runBuildListenersForProject(project, context, false,
if (thisResult.success) ProjectBuildStatus.SUCCESS else ProjectBuildStatus.FAILED) if (thisResult.success) ProjectBuildStatus.SUCCESS else ProjectBuildStatus.FAILED)
if (result.success) { if (result.success) {

View file

@ -300,13 +300,6 @@ class TaskManager @Inject constructor(val args: Args,
class TaskWorker(val tasks: List<ITask>, val dryRun: Boolean, val pluginInfo: PluginInfo) : IWorker<ITask> { class TaskWorker(val tasks: List<ITask>, val dryRun: Boolean, val pluginInfo: PluginInfo) : IWorker<ITask> {
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<ITask> { override fun call() : TaskResult2<ITask> {
if (tasks.size > 0) { if (tasks.size > 0) {
tasks[0].let { tasks[0].let {
@ -318,9 +311,9 @@ class TaskWorker(val tasks: List<ITask>, val dryRun: Boolean, val pluginInfo: Pl
val context = Kobalt.context!! val context = Kobalt.context!!
tasks.forEach { tasks.forEach {
val name = it.project.name + ":" + it.name 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() 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 success = success and tr.success
if (tr.errorMessage != null) errorMessages.add(tr.errorMessage) if (tr.errorMessage != null) errorMessages.add(tr.errorMessage)
} }