Gradle, js, webpack: report progress
#KT-31013
This commit is contained in:
+25
-7
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+4
-4
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -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;")
|
||||||
|
|||||||
+11
-4
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+28
-2
@@ -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)
|
||||||
|
|||||||
+13
-22
@@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user