mirror of
https://github.com/ethauvin/kobalt.git
synced 2025-04-26 00:17:11 -07:00
Build listeners for parallel builds.
Also showing timings in the build report.
This commit is contained in:
parent
38495b0353
commit
635cc9cd3c
5 changed files with 90 additions and 49 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
override fun taskEnd(project: Project, context: KobaltContext, taskName: String, success: Boolean) {
|
|
||||||
startTimes[taskName]?.let {
|
|
||||||
timings.add(ProfilerInfo(taskName, System.currentTimeMillis() - it))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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<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
|
||||||
AsciiArt.logBox(listOf(line), AsciiArt.bottomLeft2, AsciiArt.bottomRight2)
|
// if (timings.size > 1 && hasFailures) {
|
||||||
projectStatuses.forEach { pair ->
|
val line = listOf(col1("Project"), col2("Build status"), col3("Time"))
|
||||||
val cl = listOf(col1(pair.first.name), col2(pair.second.toString())).joinToString(AsciiArt.verticalBar)
|
.joinToString(AsciiArt.verticalBar)
|
||||||
log(1, " " + AsciiArt.verticalBar + " " + cl + " " + AsciiArt.verticalBar)
|
AsciiArt.logBox(listOf(line), AsciiArt.bottomLeft2, AsciiArt.bottomRight2)
|
||||||
}
|
projectStatuses.forEach { pair ->
|
||||||
log(1, " " + AsciiArt.lowerBox(line.length))
|
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<Pair<Project, ProjectBuildStatus>>()
|
|
||||||
|
|
||||||
override fun projectEnd(project: Project, context: KobaltContext, status: ProjectBuildStatus) {
|
|
||||||
projectStatuses.add(Pair(project, status))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue