diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/JarGenerator.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/JarGenerator.kt index 54f531bd..0fe2d18a 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/JarGenerator.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/JarGenerator.kt @@ -4,6 +4,7 @@ import com.beust.kobalt.api.KobaltContext import com.beust.kobalt.api.Project import com.beust.kobalt.archive.Archives import com.beust.kobalt.archive.Jar +import com.beust.kobalt.internal.KobaltLog import com.beust.kobalt.maven.DependencyManager import com.beust.kobalt.maven.aether.Scope import com.beust.kobalt.misc.* @@ -121,7 +122,7 @@ class JarGenerator @Inject constructor(val dependencyManager: DependencyManager) return result } - fun generateJar(project: Project, context: KobaltContext, jar: Jar) : File { + fun generateJar(project: Project, context: KobaltContext, jar: Jar, kobaltLog: KobaltLog) : File { val includedFiles = findIncludedFiles(project, context, jar) // @@ -158,7 +159,7 @@ class JarGenerator @Inject constructor(val dependencyManager: DependencyManager) val jarFactory = { os: OutputStream -> JarOutputStream(os, manifest) } return Archives.generateArchive(project, context, jar.name, ".jar", includedFiles, - true /* expandJarFiles */, jarFactory) + true /* expandJarFiles */, jarFactory, kobaltLog) } } \ No newline at end of file diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/Archives.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/Archives.kt index 31bc254a..b7506b7c 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/Archives.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/archive/Archives.kt @@ -5,12 +5,8 @@ import com.beust.kobalt.IFileSpec import com.beust.kobalt.api.KobaltContext import com.beust.kobalt.api.Project import com.beust.kobalt.api.annotation.ExportedProjectProperty -import com.beust.kobalt.misc.From -import com.beust.kobalt.misc.IncludedFile -import com.beust.kobalt.misc.JarUtils -import com.beust.kobalt.misc.KFiles -import com.beust.kobalt.misc.log -import com.beust.kobalt.misc.To +import com.beust.kobalt.internal.KobaltLog +import com.beust.kobalt.misc.* import java.io.File import java.io.FileOutputStream import java.io.OutputStream @@ -32,7 +28,8 @@ class Archives { suffix: String, includedFiles: List, expandJarFiles : Boolean = false, - outputStreamFactory: (OutputStream) -> ZipOutputStream = DEFAULT_STREAM_FACTORY) : File { + outputStreamFactory: (OutputStream) -> ZipOutputStream = DEFAULT_STREAM_FACTORY, + kobaltLog: KobaltLog) : File { val fullArchiveName = context.variant.archiveName(project, archiveName, suffix) val archiveDir = File(KFiles.libsDir(project)) val result = File(archiveDir.path, fullArchiveName) @@ -42,7 +39,7 @@ class Archives { outputStreamFactory(FileOutputStream(result)).use { JarUtils.addFiles(project.directory, includedFiles, it, expandJarFiles) log(2, text = "Added ${includedFiles.size} files to $result") - log(1, " Created $result") + kobaltLog.log(project.name, 1, " Created $result") } } catch (e: Throwable) { // make sure that incomplete archive is deleted diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/KobaltLog.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/KobaltLog.kt new file mode 100644 index 00000000..2d8884c8 --- /dev/null +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/internal/KobaltLog.kt @@ -0,0 +1,125 @@ +package com.beust.kobalt.internal + +import com.beust.kobalt.Args +import com.beust.kobalt.KobaltException +import com.beust.kobalt.misc.kobaltError +import com.beust.kobalt.misc.kobaltLog +import com.beust.kobalt.misc.kobaltWarn +import com.google.inject.Inject +import com.google.inject.Singleton +import java.util.* +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.ConcurrentLinkedQueue + +/** + * This class manages logs for parallel builds. These logs come from multiple projects interwoven as + * they are being scheduled on different threads. This class maintains a "current" project which has + * its logs always displayed instantaneously while logs from other projects are being stored for later display. + * Once the current project is done, this class will catch up all the finished project logs and then + * pick the next current project to be displayed live. + * + * Yes, this code was pretty painful to write and I'm pretty sure it still have a few bugs left. + */ +@Singleton +class KobaltLog @Inject constructor(val args: Args) { + enum class Type { LOG, WARN, ERROR } + + class LogLine(val name: String? = null, val level: Int, val message: String, val type: Type) + private val logLines = ConcurrentHashMap>() + + private val runningProjects = ConcurrentLinkedQueue() + var startTime: Long? = null + + fun onProjectStarted(name: String) { + if (startTime == null) { + startTime = System.currentTimeMillis() + } + runningProjects.add(name) + logLines[name] = arrayListOf() + if (currentName == null) { + currentName = name + } + } + + val stoppedProjects = ConcurrentHashMap() + + fun onProjectStopped(name: String) { + debug("onProjectStopped($name)") + stoppedProjects[name] = name + + if (name == currentName && runningProjects.any()) { + emptyProjectLog(name) + var nextProject = runningProjects.peek() + while (nextProject != null && stoppedProjects.containsKey(nextProject)) { + val sp = runningProjects.remove() + emptyProjectLog(sp) + nextProject = runningProjects.peek() + } + currentName = nextProject + } else { + debug("Non current project $name stopping, not doing anything") + } + } + + private fun debug(s: String) { + val time = System.currentTimeMillis() - startTime!! + println(" @@@ [$time] $s") + } + + val LOCK = Any() + var currentName: String? = null + set(newName) { + field = newName + } + + private fun displayLine(ll: LogLine) { + val time = System.currentTimeMillis() - startTime!! + val m = "### [$time] " + ll.message + when(ll.type) { + Type.LOG -> kobaltLog(ll.level, m) + Type.WARN -> kobaltWarn(m) + Type.ERROR -> kobaltError(m) + } + } + + private fun emptyProjectLog(name: String?) { + val lines = logLines[name] + if (lines != null && lines.any()) { + debug("EMPTY PROJECT LOG FOR $name") + lines.forEach { + displayLine(it) + } + lines.clear() + debug("DONE EMPTY PROJECT LOG FOR $name") +// logLines.remove(name) + } else if (lines == null) { + throw KobaltException("Didn't call onStartProject() for $name") + } + } + + private fun addLogLine(name: String, ll: LogLine) { + if (name != currentName) { + val list = logLines[name] ?: arrayListOf() + logLines[name] = list + list.add(ll) + } else { + emptyProjectLog(name) + displayLine(ll) + } + } + + fun log(name: String, level: Int, message: String) { + if (args.parallel) { + addLogLine(name, LogLine(name, level, message, Type.LOG)) + } else { + kobaltLog(level, message) + } + } + + fun shutdown() { + runningProjects.forEach { + emptyProjectLog(it) + } + println("") + } +} 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 273dcbff..f842a121 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 @@ -6,7 +6,6 @@ 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 import java.util.concurrent.Callable @@ -21,7 +20,8 @@ class ParallelProjectRunner(val tasksByNames: (Project) -> ListMultimap, val reverseDependsOn: TreeMultimap, val runBefore: TreeMultimap, val runAfter: TreeMultimap, - val alwaysRunAfter: TreeMultimap, val args: Args, val pluginInfo: PluginInfo) + val alwaysRunAfter: TreeMultimap, val args: Args, val pluginInfo: PluginInfo, + val kobaltLog: KobaltLog) : BaseProjectRunner() { override fun runProjects(taskInfos: List, projects: List) : TaskManager .RunTargetResult { @@ -38,9 +38,11 @@ class ParallelProjectRunner(val tasksByNames: (Project) -> ListMultimap task.name }, + ITask::name, { task: ITask -> task.plugin.accept(project) }) var lastResult = TaskResult() + kobaltLog.onProjectStarted(project.name) + kobaltLog.log(project.name, 1, "Parallel build of ${project.name} starting") while (graph.freeNodes.any()) { val toProcess = graph.freeNodes toProcess.forEach { node -> @@ -48,7 +50,8 @@ class ParallelProjectRunner(val tasksByNames: (Project) -> ListMultimap runBuildListenersForTask(project, context, task.name, start = true) - log(1, "===== " + project.name + ":" + task.name) + kobaltLog.log(project.name, 1, "===== " + project.name + ":" + task.name) +// log(1, "===== " + project.name + ":" + task.name) val thisResult = if (dryRun) TaskResult2(true, null, task) else task.call() if (lastResult.success) { lastResult = thisResult @@ -60,9 +63,12 @@ class ParallelProjectRunner(val tasksByNames: (Project) -> ListMultimap ListMultimap() private val reverseDependsOn = TreeMultimap.create() private val runBefore = TreeMultimap.create() @@ -99,7 +99,7 @@ class TaskManager @Inject constructor(val args: Args, val projectsToRun = findProjectsToRun(taskInfos, allProjects) val projectRunner = if (args.parallel) ParallelProjectRunner({ p -> tasksByNames(p) }, dependsOn, - reverseDependsOn, runBefore, runAfter, alwaysRunAfter, args, pluginInfo) + reverseDependsOn, runBefore, runAfter, alwaysRunAfter, args, pluginInfo, kobaltLog) else SequentialProjectRunner({ p -> tasksByNames(p) }, dependsOn, reverseDependsOn, runBefore, runAfter, alwaysRunAfter, args, pluginInfo) return projectRunner.runProjects(taskInfos, projectsToRun) diff --git a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/misc/KobaltLogger.kt b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/misc/KobaltLogger.kt index 595a635c..07729e12 100644 --- a/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/misc/KobaltLogger.kt +++ b/modules/kobalt-plugin-api/src/main/kotlin/com/beust/kobalt/misc/KobaltLogger.kt @@ -12,6 +12,8 @@ fun Any.log(level: Int, text: CharSequence, newLine : Boolean = true) { } } +fun Any.kobaltLog(level: Int, text: CharSequence, newLine : Boolean = true) = log(level, text, newLine) + fun Any.logWrap(level: Int, text1: CharSequence, text2: CharSequence, function: () -> Unit) { if (level <= KobaltLogger.LOG_LEVEL) { KobaltLogger.logger.log(javaClass.simpleName, text1, newLine = false) diff --git a/src/main/kotlin/com/beust/kobalt/plugin/java/JavaCompiler.kt b/src/main/kotlin/com/beust/kobalt/plugin/java/JavaCompiler.kt index 912831cc..fae8ce6e 100644 --- a/src/main/kotlin/com/beust/kobalt/plugin/java/JavaCompiler.kt +++ b/src/main/kotlin/com/beust/kobalt/plugin/java/JavaCompiler.kt @@ -9,6 +9,7 @@ import com.beust.kobalt.api.KobaltContext import com.beust.kobalt.api.Project import com.beust.kobalt.internal.ICompilerAction import com.beust.kobalt.internal.JvmCompiler +import com.beust.kobalt.internal.KobaltLog import com.beust.kobalt.misc.KFiles import com.beust.kobalt.misc.Strings import com.beust.kobalt.misc.log @@ -22,7 +23,8 @@ import javax.tools.JavaFileObject import javax.tools.ToolProvider @Singleton -class JavaCompiler @Inject constructor(val jvmCompiler: JvmCompiler) : ICompiler { +class JavaCompiler @Inject constructor(val jvmCompiler: JvmCompiler, + val kobaltLog: KobaltLog) : ICompiler { fun compilerAction(executable: File) = object : ICompilerAction { override fun compile(projectName: String?, info: CompilerActionInfo): TaskResult { if (info.sourceFiles.isEmpty()) { @@ -58,7 +60,8 @@ class JavaCompiler @Inject constructor(val jvmCompiler: JvmCompiler) : ICompiler command = "javac " + allArgs.joinToString(" ") + " " + info.sourceFiles.joinToString(" ") log(2, "Launching\n$command") - log(1, " Java compiling " + Strings.pluralizeAll(info.sourceFiles.size, "file")) + kobaltLog.log(projectName!!, 1, + " Java compiling " + Strings.pluralizeAll(info.sourceFiles.size, "file")) val result = task.call() errorMessage = dc.diagnostics.joinToString("\n") result diff --git a/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinCompiler.kt b/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinCompiler.kt index 032a1977..0e133d74 100644 --- a/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinCompiler.kt +++ b/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinCompiler.kt @@ -3,10 +3,7 @@ package com.beust.kobalt.plugin.kotlin import com.beust.kobalt.KobaltException import com.beust.kobalt.TaskResult import com.beust.kobalt.api.* -import com.beust.kobalt.internal.ICompilerAction -import com.beust.kobalt.internal.JvmCompiler -import com.beust.kobalt.internal.KobaltSettings -import com.beust.kobalt.internal.KotlinJarFiles +import com.beust.kobalt.internal.* import com.beust.kobalt.kotlin.ParentLastClassLoader import com.beust.kobalt.maven.DependencyManager import com.beust.kobalt.maven.dependency.FileDependency @@ -38,14 +35,16 @@ class KotlinCompiler @Inject constructor( val executors: KobaltExecutors, val settings: KobaltSettings, val jvmCompiler: JvmCompiler, - val kotlinJarFiles: KotlinJarFiles) { + val kotlinJarFiles: KotlinJarFiles, + val kobaltLog: KobaltLog) { val compilerAction = object: ICompilerAction { override fun compile(projectName: String?, info: CompilerActionInfo): TaskResult { val version = settings.kobaltCompilerVersion if (! info.outputDir.path.endsWith("ript.jar")) { // Don't display the message if compiling Build.kt - log(1, " Kotlin $version compiling " + Strings.pluralizeAll(info.sourceFiles.size, "file")) + kobaltLog.log(projectName!!, 1, + " Kotlin $version compiling " + Strings.pluralizeAll(info.sourceFiles.size, "file")) } val cp = compilerFirst(info.dependencies.map { it.jarFile.get() }) val infoDir = info.directory diff --git a/src/main/kotlin/com/beust/kobalt/plugin/packaging/PackagingPlugin.kt b/src/main/kotlin/com/beust/kobalt/plugin/packaging/PackagingPlugin.kt index d69b7acd..1d536547 100644 --- a/src/main/kotlin/com/beust/kobalt/plugin/packaging/PackagingPlugin.kt +++ b/src/main/kotlin/com/beust/kobalt/plugin/packaging/PackagingPlugin.kt @@ -9,6 +9,7 @@ import com.beust.kobalt.api.annotation.Task import com.beust.kobalt.archive.* import com.beust.kobalt.internal.IncrementalManager import com.beust.kobalt.internal.JvmCompilerPlugin +import com.beust.kobalt.internal.KobaltLog import com.beust.kobalt.maven.DependencyManager import com.beust.kobalt.maven.PomGenerator import com.beust.kobalt.misc.KFiles @@ -23,6 +24,7 @@ class PackagingPlugin @Inject constructor(val dependencyManager : DependencyMana val incrementalManagerFactory: IncrementalManager.IFactory, val executors: KobaltExecutors, val jarGenerator: JarGenerator, val warGenerator: WarGenerator, val zipGenerator: ZipGenerator, val taskContributor: TaskContributor, + val kobaltLog: KobaltLog, val pomFactory: PomGenerator.IFactory, val configActor: ConfigActor) : BasePlugin(), ITaskContributor, IIncrementalAssemblyContributor, IConfigActor by configActor { @@ -73,7 +75,8 @@ class PackagingPlugin @Inject constructor(val dependencyManager : DependencyMana { project -> try { packages.filter { it.project.name == project.name }.forEach { packageConfig -> - packageConfig.jars.forEach { jarGenerator.generateJar(packageConfig.project, context, it) } + packageConfig.jars.forEach { jarGenerator.generateJar(packageConfig.project, context, it, + kobaltLog) } packageConfig.wars.forEach { warGenerator.generateWar(packageConfig.project, context, it) } packageConfig.zips.forEach { zipGenerator.generateZip(packageConfig.project, context, it) } if (packageConfig.generatePom) { diff --git a/src/main/kotlin/com/beust/kobalt/plugin/packaging/WarGenerator.kt b/src/main/kotlin/com/beust/kobalt/plugin/packaging/WarGenerator.kt index 14384928..ef09134a 100644 --- a/src/main/kotlin/com/beust/kobalt/plugin/packaging/WarGenerator.kt +++ b/src/main/kotlin/com/beust/kobalt/plugin/packaging/WarGenerator.kt @@ -7,6 +7,7 @@ import com.beust.kobalt.api.KobaltContext import com.beust.kobalt.api.Project import com.beust.kobalt.archive.Archives import com.beust.kobalt.archive.War +import com.beust.kobalt.internal.KobaltLog import com.beust.kobalt.maven.DependencyManager import com.beust.kobalt.misc.From import com.beust.kobalt.misc.IncludedFile @@ -18,7 +19,7 @@ import java.io.OutputStream import java.nio.file.Paths import java.util.jar.JarOutputStream -class WarGenerator @Inject constructor(val dependencyManager: DependencyManager){ +class WarGenerator @Inject constructor(val dependencyManager: DependencyManager, val kobaltLog: KobaltLog) { companion object { val WEB_INF = "WEB-INF" @@ -88,7 +89,7 @@ class WarGenerator @Inject constructor(val dependencyManager: DependencyManager) val allFiles = findIncludedFiles(project, context, war) val jarFactory = { os: OutputStream -> JarOutputStream(os, manifest) } return Archives.generateArchive(project, context, war.name, ".war", allFiles, - false /* don't expand jar files */, jarFactory) + false /* don't expand jar files */, jarFactory, kobaltLog) } } diff --git a/src/main/kotlin/com/beust/kobalt/plugin/packaging/ZipGenerator.kt b/src/main/kotlin/com/beust/kobalt/plugin/packaging/ZipGenerator.kt index 21220534..f2feaf55 100644 --- a/src/main/kotlin/com/beust/kobalt/plugin/packaging/ZipGenerator.kt +++ b/src/main/kotlin/com/beust/kobalt/plugin/packaging/ZipGenerator.kt @@ -5,12 +5,13 @@ import com.beust.kobalt.api.KobaltContext import com.beust.kobalt.api.Project import com.beust.kobalt.archive.Archives import com.beust.kobalt.archive.Zip +import com.beust.kobalt.internal.KobaltLog import com.beust.kobalt.maven.DependencyManager import com.google.inject.Inject -class ZipGenerator @Inject constructor(val dependencyManager: DependencyManager){ +class ZipGenerator @Inject constructor(val dependencyManager: DependencyManager, val kobaltLog: KobaltLog) { fun generateZip(project: Project, context: KobaltContext, zip: Zip) { val allFiles = JarGenerator.findIncludedFiles(project.directory, zip.includedFiles, zip.excludes) - Archives.generateArchive(project, context, zip.name, ".zip", allFiles) + Archives.generateArchive(project, context, zip.name, ".zip", allFiles, kobaltLog = kobaltLog) } }