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.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
}
}
@@ -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 <T> 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()
}
@@ -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;")
@@ -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()
}
}
@@ -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)
@@ -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<String>("--config", configFile.absolutePath)
if (configWriter.showProgress) {
args.add("--progress")
}
npmProject.useTool(execFactory, ".bin/$bin", *args.toTypedArray())
val exec = execFactory.build()
exec.start()
return exec
}
}