Gradle, js, webpack: report progress

#KT-31013
This commit is contained in:
Sergey Rostov
2019-05-17 12:24:16 +03:00
parent 77dd806742
commit 83042afd5c
6 changed files with 84 additions and 40 deletions
@@ -7,14 +7,16 @@ package org.jetbrains.kotlin.gradle.internal
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.internal.project.ProjectInternal import org.gradle.api.internal.project.ProjectInternal
import org.gradle.process.ExecResult
import org.gradle.process.internal.ExecAction import org.gradle.process.internal.ExecAction
import org.gradle.process.internal.ExecActionFactory import org.gradle.process.internal.ExecActionFactory
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.PipedInputStream import java.io.PipedInputStream
import java.io.PipedOutputStream import java.io.PipedOutputStream
import java.nio.CharBuffer
import kotlin.concurrent.thread 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 this as ProjectInternal
val stderr = ByteArrayOutputStream() val stderr = ByteArrayOutputStream()
@@ -22,22 +24,38 @@ internal fun Project.execWithProgress(description: String, body: (ExecAction) ->
val stdInPipe = PipedInputStream() val stdInPipe = PipedInputStream()
val exec = services.get(ExecActionFactory::class.java).newExecAction() val exec = services.get(ExecActionFactory::class.java).newExecAction()
body(exec) body(exec)
project.operation(description) { return project.operation(description) {
exec.errorOutput = stderr
exec.standardOutput = PipedOutputStream(stdInPipe) exec.standardOutput = PipedOutputStream(stdInPipe)
val outputReaderThread = thread(name = "output reader for [$description]") { val outputReaderThread = thread(name = "output reader for [$description]") {
stdInPipe.reader().useLines { lines -> stdInPipe.reader().use { reader ->
lines.forEach { val buffer = StringBuilder()
stdout.appendln(it) while (true) {
progress(it) 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 exec.isIgnoreExitValue = true
val result = exec.execute() val result = exec.execute()
outputReaderThread.join() outputReaderThread.join()
if (result.exitValue != 0) { if (result.exitValue != 0) {
error(stderr.toString() + "\n" + stdout) error(stderr.toString() + "\n" + stdout)
} }
result
} }
} }
@@ -10,17 +10,17 @@ import org.gradle.api.internal.project.ProjectInternal
import org.gradle.internal.logging.events.ProgressStartEvent import org.gradle.internal.logging.events.ProgressStartEvent
import org.gradle.internal.logging.progress.ProgressLogger import org.gradle.internal.logging.progress.ProgressLogger
fun Project.operation( fun <T> Project.operation(
description: String, description: String,
initialStatus: String? = null, initialStatus: String? = null,
body: ProgressLogger.() -> Unit body: ProgressLogger.() -> T
) { ): T {
val services = (project as ProjectInternal).services val services = (project as ProjectInternal).services
val progressFactory = services.get(org.gradle.internal.logging.progress.ProgressLoggerFactory::class.java) val progressFactory = services.get(org.gradle.internal.logging.progress.ProgressLoggerFactory::class.java)
val operation = progressFactory.newOperation(ProgressStartEvent.BUILD_OP_CATEGORY) val operation = progressFactory.newOperation(ProgressStartEvent.BUILD_OP_CATEGORY)
operation.start(description, initialStatus) operation.start(description, initialStatus)
try { try {
operation.body() return operation.body()
} finally { } finally {
operation.completed() operation.completed()
} }
@@ -114,7 +114,9 @@ class KotlinKarma(override val compilation: KotlinJsCompilation) : KotlinJsTestF
KotlinWebpackConfigWriter( KotlinWebpackConfigWriter(
configDirectory = project.projectDir.resolve("webpack.config.d").takeIf { it.isDirectory }, configDirectory = project.projectDir.resolve("webpack.config.d").takeIf { it.isDirectory },
sourceMaps = true, sourceMaps = true,
export = false export = false,
progressReporter = true,
progressReporterPathFilter = project.nodeJs.root.rootPackageDir.absolutePath
).appendTo(it) ).appendTo(it)
it.appendln(" return config;") it.appendln(" return config;")
@@ -54,7 +54,7 @@ open class KotlinWebpack : DefaultTask(), RequiresNpmDependencies {
var saveEvaluatedConfigFile: Boolean = true var saveEvaluatedConfigFile: Boolean = true
open val outputPath: File open val outputPath: File
@OutputDirectory get() = project.buildDir.resolve("lib") @OutputDirectory get() = project.buildDir.resolve("libs")
open val configDirectory: File? open val configDirectory: File?
@Optional @InputDirectory get() = project.projectDir.resolve("webpack.config.d").takeIf { it.isDirectory } @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( private fun createRunner() = KotlinWebpackRunner(
compilation!!.npmProject, compilation.npmProject,
configFile, configFile,
execHandleFactory, execHandleFactory,
bin, bin,
@@ -121,13 +121,20 @@ open class KotlinWebpack : DefaultTask(), RequiresNpmDependencies {
val runner = createRunner() val runner = createRunner()
if (project.gradle.startParameter.isContinuous) { if (project.gradle.startParameter.isContinuous) {
val continuousRunner = runner
val deploymentRegistry = services.get(DeploymentRegistry::class.java) val deploymentRegistry = services.get(DeploymentRegistry::class.java)
val deploymentHandle = deploymentRegistry.get("webpack", Handle::class.java) val deploymentHandle = deploymentRegistry.get("webpack", Handle::class.java)
if (deploymentHandle == null) { if (deploymentHandle == null) {
deploymentRegistry.start("webpack", DeploymentRegistry.ChangeBehavior.BLOCK, Handle::class.java, runner) deploymentRegistry.start("webpack", DeploymentRegistry.ChangeBehavior.BLOCK, Handle::class.java, continuousRunner)
} }
} else { } else {
runner.execute() runner.copy(
configWriter = runner.configWriter.copy(
progressReporter = true,
progressReporterPathFilter = project.nodeJs.root.rootPackageDir.absolutePath
)
).execute()
} }
} }
@@ -15,7 +15,7 @@ import java.io.Serializable
import java.io.StringWriter import java.io.StringWriter
@Suppress("MemberVisibilityCanBePrivate") @Suppress("MemberVisibilityCanBePrivate")
class KotlinWebpackConfigWriter( data class KotlinWebpackConfigWriter(
val mode: Mode = Mode.DEVELOPMENT, val mode: Mode = Mode.DEVELOPMENT,
val entry: File? = null, val entry: File? = null,
val outputPath: File? = null, val outputPath: File? = null,
@@ -26,7 +26,9 @@ class KotlinWebpackConfigWriter(
val showProgress: Boolean = false, val showProgress: Boolean = false,
val sourceMaps: Boolean = false, val sourceMaps: Boolean = false,
val sourceMapsRuntime: 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) { enum class Mode(val code: String) {
DEVELOPMENT("development"), DEVELOPMENT("development"),
@@ -88,6 +90,7 @@ class KotlinWebpackConfigWriter(
appendReport() appendReport()
appendFromConfigDir() appendFromConfigDir()
appendEvaluatedFileReport() appendEvaluatedFileReport()
appendProgressReporter()
if (export) { if (export) {
//language=JavaScript 1.8 //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 { private fun json(obj: Any) = StringWriter().also {
GsonBuilder().setPrettyPrinting().create().toJson(obj, it) GsonBuilder().setPrettyPrinting().create().toJson(obj, it)
@@ -3,55 +3,46 @@
* that can be found in the license/LICENSE.txt file. * that can be found in the license/LICENSE.txt file.
*/ */
@file:Suppress(
"JSAnnotator",
"NodeJsCodingAssistanceForCoreModules",
"BadExpressionStatementJS",
"JSUnresolvedFunction"
)
package org.jetbrains.kotlin.gradle.targets.js.webpack 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.ExecHandle
import org.gradle.process.internal.ExecHandleFactory 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.NpmProject
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmResolver
import java.io.File import java.io.File
internal class KotlinWebpackRunner( internal data class KotlinWebpackRunner(
val npmProject: NpmProject, val npmProject: NpmProject,
val configFile: File, val configFile: File,
val execHandleFactory: ExecHandleFactory, val execHandleFactory: ExecHandleFactory,
val bin: String, val bin: String,
val configWriter: KotlinWebpackConfigWriter val configWriter: KotlinWebpackConfigWriter
) { ) {
fun execute(): ExecResult { fun execute() = npmProject.project.execWithProgress("webpack") {
val exec = start() configureExec(it)
val result = exec.waitForFinish()
check(result.exitValue == 0) {
"Webpack exited with non zero exit code (${result.exitValue})"
}
return result
} }
fun start(): ExecHandle { 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) { check(configWriter.entry?.isFile == true) {
"${this}: Entry file not existed \"${configWriter.entry}\"" "${this}: Entry file not existed \"${configWriter.entry}\""
} }
configWriter.save(configFile) configWriter.save(configFile)
val execFactory = execHandleFactory.newExec()
val args = mutableListOf<String>("--config", configFile.absolutePath) val args = mutableListOf<String>("--config", configFile.absolutePath)
if (configWriter.showProgress) { if (configWriter.showProgress) {
args.add("--progress") args.add("--progress")
} }
npmProject.useTool(execFactory, ".bin/$bin", *args.toTypedArray()) npmProject.useTool(execFactory, ".bin/$bin", *args.toTypedArray())
val exec = execFactory.build()
exec.start()
return exec
} }
} }