From 83042afd5cd736cfc93a9b00ad3edfc3d9b4c9d5 Mon Sep 17 00:00:00 2001 From: Sergey Rostov Date: Fri, 17 May 2019 12:24:16 +0300 Subject: [PATCH] Gradle, js, webpack: report progress #KT-31013 --- .../jetbrains/kotlin/gradle/internal/exec.kt | 32 +++++++++++++---- .../kotlin/gradle/internal/progress.kt | 8 ++--- .../targets/js/testing/karma/KotlinKarma.kt | 4 ++- .../targets/js/webpack/KotlinWebpack.kt | 15 +++++--- .../js/webpack/KotlinWebpackConfigWriter.kt | 30 ++++++++++++++-- .../targets/js/webpack/KotlinWebpackRunner.kt | 35 +++++++------------ 6 files changed, 84 insertions(+), 40 deletions(-) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/exec.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/exec.kt index 4e46125f480..cafee6d7486 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/exec.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/exec.kt @@ -7,14 +7,16 @@ package org.jetbrains.kotlin.gradle.internal import org.gradle.api.Project import org.gradle.api.internal.project.ProjectInternal +import org.gradle.process.ExecResult import org.gradle.process.internal.ExecAction import org.gradle.process.internal.ExecActionFactory import java.io.ByteArrayOutputStream import java.io.PipedInputStream import java.io.PipedOutputStream +import java.nio.CharBuffer import kotlin.concurrent.thread -internal fun Project.execWithProgress(description: String, body: (ExecAction) -> Unit) { +internal fun Project.execWithProgress(description: String, readStdErr: Boolean = false, body: (ExecAction) -> Unit): ExecResult { this as ProjectInternal val stderr = ByteArrayOutputStream() @@ -22,22 +24,38 @@ internal fun Project.execWithProgress(description: String, body: (ExecAction) -> val stdInPipe = PipedInputStream() val exec = services.get(ExecActionFactory::class.java).newExecAction() body(exec) - project.operation(description) { - exec.errorOutput = stderr + return project.operation(description) { exec.standardOutput = PipedOutputStream(stdInPipe) val outputReaderThread = thread(name = "output reader for [$description]") { - stdInPipe.reader().useLines { lines -> - lines.forEach { - stdout.appendln(it) - progress(it) + stdInPipe.reader().use { reader -> + val buffer = StringBuilder() + while (true) { + val read = reader.read() + if (read == -1) break; + val ch = read.toChar() + if (ch == '\b' || ch == '\n' || ch == '\r') { + if (buffer.isNotEmpty()) { + val str = buffer.toString() + stdout.append(str) + progress(str.trim()) + buffer.setLength(0) + } + stdout.append(ch) + } else buffer.append(ch) } } } + if (readStdErr) { + exec.errorOutput = exec.standardOutput + } else { + exec.errorOutput = System.err + } exec.isIgnoreExitValue = true val result = exec.execute() outputReaderThread.join() if (result.exitValue != 0) { error(stderr.toString() + "\n" + stdout) } + result } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/progress.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/progress.kt index a43c9fdaec6..ff8922ff82d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/progress.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/progress.kt @@ -10,17 +10,17 @@ import org.gradle.api.internal.project.ProjectInternal import org.gradle.internal.logging.events.ProgressStartEvent import org.gradle.internal.logging.progress.ProgressLogger -fun Project.operation( +fun Project.operation( description: String, initialStatus: String? = null, - body: ProgressLogger.() -> Unit -) { + body: ProgressLogger.() -> T +): T { val services = (project as ProjectInternal).services val progressFactory = services.get(org.gradle.internal.logging.progress.ProgressLoggerFactory::class.java) val operation = progressFactory.newOperation(ProgressStartEvent.BUILD_OP_CATEGORY) operation.start(description, initialStatus) try { - operation.body() + return operation.body() } finally { operation.completed() } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/karma/KotlinKarma.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/karma/KotlinKarma.kt index 8f58b2a5f1c..c746d8ba2cb 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/karma/KotlinKarma.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/karma/KotlinKarma.kt @@ -114,7 +114,9 @@ class KotlinKarma(override val compilation: KotlinJsCompilation) : KotlinJsTestF KotlinWebpackConfigWriter( configDirectory = project.projectDir.resolve("webpack.config.d").takeIf { it.isDirectory }, sourceMaps = true, - export = false + export = false, + progressReporter = true, + progressReporterPathFilter = project.nodeJs.root.rootPackageDir.absolutePath ).appendTo(it) it.appendln(" return config;") diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpack.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpack.kt index 0da80aef05f..79ceceb83c9 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpack.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpack.kt @@ -54,7 +54,7 @@ open class KotlinWebpack : DefaultTask(), RequiresNpmDependencies { var saveEvaluatedConfigFile: Boolean = true open val outputPath: File - @OutputDirectory get() = project.buildDir.resolve("lib") + @OutputDirectory get() = project.buildDir.resolve("libs") open val configDirectory: File? @Optional @InputDirectory get() = project.projectDir.resolve("webpack.config.d").takeIf { it.isDirectory } @@ -98,7 +98,7 @@ open class KotlinWebpack : DefaultTask(), RequiresNpmDependencies { } private fun createRunner() = KotlinWebpackRunner( - compilation!!.npmProject, + compilation.npmProject, configFile, execHandleFactory, bin, @@ -121,13 +121,20 @@ open class KotlinWebpack : DefaultTask(), RequiresNpmDependencies { val runner = createRunner() if (project.gradle.startParameter.isContinuous) { + val continuousRunner = runner + val deploymentRegistry = services.get(DeploymentRegistry::class.java) val deploymentHandle = deploymentRegistry.get("webpack", Handle::class.java) if (deploymentHandle == null) { - deploymentRegistry.start("webpack", DeploymentRegistry.ChangeBehavior.BLOCK, Handle::class.java, runner) + deploymentRegistry.start("webpack", DeploymentRegistry.ChangeBehavior.BLOCK, Handle::class.java, continuousRunner) } } else { - runner.execute() + runner.copy( + configWriter = runner.configWriter.copy( + progressReporter = true, + progressReporterPathFilter = project.nodeJs.root.rootPackageDir.absolutePath + ) + ).execute() } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackConfigWriter.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackConfigWriter.kt index 12e130d1de2..9179955b766 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackConfigWriter.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackConfigWriter.kt @@ -15,7 +15,7 @@ import java.io.Serializable import java.io.StringWriter @Suppress("MemberVisibilityCanBePrivate") -class KotlinWebpackConfigWriter( +data class KotlinWebpackConfigWriter( val mode: Mode = Mode.DEVELOPMENT, val entry: File? = null, val outputPath: File? = null, @@ -26,7 +26,9 @@ class KotlinWebpackConfigWriter( val showProgress: Boolean = false, val sourceMaps: Boolean = false, val sourceMapsRuntime: Boolean = false, - val export: Boolean = true + val export: Boolean = true, + val progressReporter: Boolean = false, + val progressReporterPathFilter: String? = null ) { enum class Mode(val code: String) { DEVELOPMENT("development"), @@ -88,6 +90,7 @@ class KotlinWebpackConfigWriter( appendReport() appendFromConfigDir() appendEvaluatedFileReport() + appendProgressReporter() if (export) { //language=JavaScript 1.8 @@ -207,6 +210,29 @@ class KotlinWebpackConfigWriter( ) } + private fun Appendable.appendProgressReporter() { + if (!progressReporter) return + + appendln( + """ + // Report progress to console + (function(config) { + const webpack = require('webpack'); + const handler = (percentage, message, ...args) => { + const p = percentage*100; + let msg = Math.trunc(p/100) + Math.trunc(p%100) + '% ' + message + ' ' + args.join(' '); + ${if (progressReporterPathFilter == null) "" else """ + msg = msg.replace(new RegExp(${jsQuotedString(progressReporterPathFilter ?: "")}, 'g'), ''); + """.trimIndent()} + console.log(msg); + }; + + config.plugins.push(new webpack.ProgressPlugin(handler)) + })(config); + """.trimIndent() + ) + } + private fun json(obj: Any) = StringWriter().also { GsonBuilder().setPrettyPrinting().create().toJson(obj, it) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackRunner.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackRunner.kt index a3141fa6e56..43800fe1e0b 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackRunner.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackRunner.kt @@ -3,55 +3,46 @@ * that can be found in the license/LICENSE.txt file. */ -@file:Suppress( - "JSAnnotator", - "NodeJsCodingAssistanceForCoreModules", - "BadExpressionStatementJS", - "JSUnresolvedFunction" -) - package org.jetbrains.kotlin.gradle.targets.js.webpack -import org.gradle.process.ExecResult +import org.gradle.process.ExecSpec import org.gradle.process.internal.ExecHandle import org.gradle.process.internal.ExecHandleFactory +import org.jetbrains.kotlin.gradle.internal.execWithProgress import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject -import org.jetbrains.kotlin.gradle.targets.js.npm.NpmResolver import java.io.File -internal class KotlinWebpackRunner( +internal data class KotlinWebpackRunner( val npmProject: NpmProject, val configFile: File, val execHandleFactory: ExecHandleFactory, val bin: String, val configWriter: KotlinWebpackConfigWriter ) { - fun execute(): ExecResult { - val exec = start() - val result = exec.waitForFinish() - check(result.exitValue == 0) { - "Webpack exited with non zero exit code (${result.exitValue})" - } - return result + fun execute() = npmProject.project.execWithProgress("webpack") { + configureExec(it) } fun start(): ExecHandle { + val execFactory = execHandleFactory.newExec() + configureExec(execFactory) + val exec = execFactory.build() + exec.start() + return exec + } + + private fun configureExec(execFactory: ExecSpec) { check(configWriter.entry?.isFile == true) { "${this}: Entry file not existed \"${configWriter.entry}\"" } configWriter.save(configFile) - val execFactory = execHandleFactory.newExec() - val args = mutableListOf("--config", configFile.absolutePath) if (configWriter.showProgress) { args.add("--progress") } npmProject.useTool(execFactory, ".bin/$bin", *args.toTypedArray()) - val exec = execFactory.build() - exec.start() - return exec } } \ No newline at end of file