Gradle, JS: webpack support

#KT-31013
This commit is contained in:
Sergey Rostov
2019-04-22 15:35:16 +03:00
parent 32ddc2318a
commit eb7d09a562
13 changed files with 410 additions and 151 deletions
@@ -46,8 +46,8 @@ abstract class AbstractKotlinTargetConfigurator<KotlinTargetType : KotlinTarget>
}
abstract protected fun configureArchivesAndComponent(target: KotlinTargetType)
abstract protected fun configureTest(target: KotlinTargetType)
protected abstract fun configureArchivesAndComponent(target: KotlinTargetType)
protected abstract fun configureTest(target: KotlinTargetType)
private fun Project.registerOutputsForStaleOutputCleanup(kotlinCompilation: KotlinCompilation<*>) {
val cleanTask = tasks.getByName(LifecycleBasePlugin.CLEAN_TASK_NAME) as Delete
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.sources.defaultSourceSetLanguageSettingsChecker
import org.jetbrains.kotlin.gradle.plugin.sources.getSourceSetHierarchy
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
import org.jetbrains.kotlin.gradle.tasks.locateTask
import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import java.util.*
@@ -48,6 +49,9 @@ abstract class AbstractKotlinCompilation<T : KotlinCommonOptions>(
override val compileKotlinTask: KotlinCompile<T>
get() = (target.project.tasks.getByName(compileKotlinTaskName) as KotlinCompile<T>)
val compileKotlinTaskHolder: TaskHolder<KotlinCompile<T>>
get() = target.project.locateTask(compileKotlinTaskName)!!
// Don't declare this property in the constructor to avoid NPE
// when an overriding property of a subclass is accessed instead.
override val target: KotlinTarget = target
@@ -1,22 +1,19 @@
package org.jetbrains.kotlin.gradle.targets.js
import org.gradle.language.base.plugins.LifecycleBasePlugin
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationToRunnableFiles
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin
import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
import org.jetbrains.kotlin.gradle.tasks.createOrRegisterTask
import org.jetbrains.kotlin.gradle.testing.internal.configureConventions
import org.jetbrains.kotlin.gradle.testing.internal.registerTestTask
import org.jetbrains.kotlin.utils.addIfNotNull
internal class KotlinJsCompilationTestsConfigurator(
val compilation: KotlinCompilationToRunnableFiles<*>
) {
internal open class KotlinJsCompilationTestsConfigurator(val compilation: KotlinJsCompilation) {
private val target get() = compilation.target
private val disambiguationClassifier get() = target.disambiguationClassifier
private val project get() = target.project
private val compileTestKotlin2Js get() = compilation.compileKotlinTask as Kotlin2JsCompile
private val compileTask get() = compilation.compileKotlinTask
private fun disambiguate(name: String, includeCompilation: Boolean = false): MutableList<String> {
val components = mutableListOf<String>()
@@ -37,35 +34,37 @@ internal class KotlinJsCompilationTestsConfigurator(
get() = disambiguateCamelCased("test", false)
fun configure() {
// apply plugin (cannot do it lazy)
// apply plugin (cannot be done at task instantiation time)
val nodeJs = NodeJsPlugin.apply(target.project).root
val testJs = project.createOrRegisterTask<KotlinJsTest>(testTaskName) { testJs ->
testJs.group = LifecycleBasePlugin.VERIFICATION_GROUP
testJs.dependsOn(compileTestKotlin2Js, nodeJs.nodeJsSetupTask)
testJs.dependsOn(compileTask, nodeJs.nodeJsSetupTask)
testJs.onlyIf {
compileTestKotlin2Js.outputFile.exists()
compileTask.outputFile.exists()
}
testJs.runtimeDependencyHandler = compilation
testJs.targetName = disambiguationClassifier
testJs.nodeModulesToLoad.add(compileTestKotlin2Js.outputFile.name)
testJs.nodeModulesToLoad.add(compileTask.outputFile.name)
testJs.configureConventions()
}
testJs.doGetTask().useNodeJs { }
registerTestTask(testJs)
// project.afterEvaluate {
// testJs.configure {
// if (it.testFramework == null) {
// it.useNodeJs { }
// }
// }
// }
project.afterEvaluate {
testJs.configure {
if (it.testFramework == null) {
configureDefaultTestFramework(it)
}
}
}
}
protected open fun configureDefaultTestFramework(it: KotlinJsTest) {
it.useNodeJs { }
}
}
@@ -5,17 +5,19 @@
package org.jetbrains.kotlin.gradle.targets.js
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.Kotlin2JsSourceSetProcessor
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSetProcessor
import org.jetbrains.kotlin.gradle.plugin.KotlinTargetConfigurator
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinOnlyTarget
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProjectLayout
import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpack
import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider
import org.jetbrains.kotlin.gradle.tasks.createOrRegisterTask
import org.jetbrains.kotlin.gradle.tasks.registerTask
class KotlinJsTargetConfigurator(kotlinPluginVersion: String) :
KotlinTargetConfigurator<KotlinJsCompilation>(true, true, kotlinPluginVersion) {
open class KotlinJsTargetConfigurator(kotlinPluginVersion: String) :
KotlinTargetConfigurator<KotlinJsCompilation>(true, true, kotlinPluginVersion) {
override fun buildCompilationProcessor(compilation: KotlinJsCompilation): KotlinSourceSetProcessor<*> {
val tasksProvider = KotlinTasksProvider(compilation.target.targetName)
@@ -35,20 +37,42 @@ class KotlinJsTargetConfigurator(kotlinPluginVersion: String) :
}
override fun configureTest(target: KotlinOnlyTarget<KotlinJsCompilation>) {
val project = target.project
val npmProject = NpmProjectLayout[project]
target.compilations.all { compilation ->
if (isTestCompilation(compilation)) {
KotlinJsCompilationTestsConfigurator(compilation).configure()
} else {
KotlinWebpack.configure(compilation)
newTestsConfigurator(compilation).configure()
}
}
}
internal open fun newTestsConfigurator(compilation: KotlinJsCompilation) =
KotlinJsCompilationTestsConfigurator(compilation)
companion object {
internal fun isTestCompilation(it: KotlinJsCompilation) =
it.name == KotlinCompilation.TEST_COMPILATION_NAME
}
}
class KotlinJsSingleTargetConfigurator(kotlinPluginVersion: String) :
KotlinJsTargetConfigurator(kotlinPluginVersion) {
override fun configureTarget(target: KotlinOnlyTarget<KotlinJsCompilation>) {
super.configureTarget(target)
configureApplication(target)
}
private fun configureApplication(target: KotlinOnlyTarget<KotlinJsCompilation>) {
target.compilations.all {
if (it.name == KotlinCompilation.MAIN_COMPILATION_NAME) {
KotlinWebpack.configure(it)
}
}
}
override fun newTestsConfigurator(compilation: KotlinJsCompilation) =
object : KotlinJsCompilationTestsConfigurator(compilation) {
override fun configureDefaultTestFramework(it: KotlinJsTest) {
it.useMocha { }
}
}
}
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.gradle.plugin.mpp
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.targets.js.KotlinJsSingleTargetConfigurator
import org.jetbrains.kotlin.gradle.targets.js.KotlinJsTargetConfigurator
open class KotlinJsTargetPreset(
@@ -42,4 +43,6 @@ class KotlinJsSingleTargetPreset(
// In a Kotlin/JS single-platform project, we don't need any disambiguation suffixes or prefixes in the names:
override fun provideTargetDisambiguationClassifier(target: KotlinOnlyTarget<KotlinJsCompilation>): String? = null
override fun createKotlinTargetConfigurator() = KotlinJsSingleTargetConfigurator(kotlinPluginVersion)
}
@@ -71,6 +71,8 @@ class NpmProject(
else NpmProject[parent].findModule(name, src)
}
override fun toString() = "NpmProject($nodeWorkDir)"
companion object {
const val PACKAGE_JSON = "package.json"
const val NODE_MODULES = "node_modules"
@@ -5,21 +5,22 @@
package org.jetbrains.kotlin.gradle.targets.js.webpack
import com.google.gson.stream.JsonWriter
import org.gradle.api.DefaultTask
import org.gradle.api.internal.file.FileResolver
import org.gradle.api.tasks.*
import org.gradle.deployment.internal.Deployment
import org.gradle.deployment.internal.DeploymentHandle
import org.gradle.deployment.internal.DeploymentRegistry
import org.gradle.process.internal.ExecHandle
import org.gradle.process.internal.ExecHandleFactory
import org.gradle.process.internal.ExecHandleState
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationToRunnableFiles
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProjectLayout
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmResolver
import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
import org.jetbrains.kotlin.gradle.tasks.createOrRegisterTask
import org.jetbrains.kotlin.gradle.testing.internal.reportsDir
import org.jetbrains.kotlin.gradle.utils.injected
import java.io.File
import java.io.StringWriter
import javax.inject.Inject
open class KotlinWebpack : DefaultTask() {
@@ -39,9 +40,9 @@ open class KotlinWebpack : DefaultTask() {
@OutputFile get() = project.buildDir.resolve("webpack.config.js")
@Input
var saveConfigFileEffective: Boolean = true
var saveEvaluatedConfigFile: Boolean = true
open val configFileEffective: File
open val evaluatedConfigFile: File
@OutputFile get() = project.buildDir.resolve("webpack.config.evaluated.js")
open val outputPath: File
@@ -56,103 +57,54 @@ open class KotlinWebpack : DefaultTask() {
open val reportDir: File
@OutputDirectory get() = project.reportsDir.resolve("webpack").resolve(entry.nameWithoutExtension)
@Input
var bin: String = "webpack"
@Input
var devServer: KotlinWebpackConfig.DevServer? = null
internal fun createRunner() = KotlinWebpackRunner(
project,
configFile,
execHandleFactory,
bin,
KotlinWebpackConfig(
entry,
if (saveEvaluatedConfigFile) evaluatedConfigFile else null,
outputPath,
configDirectory,
if (report) reportDir else null,
devServer
)
)
@TaskAction
fun execute() {
check(entry.isFile) {
"${this}: Entry file not existed \"$entry\""
}
val runner = createRunner()
NpmResolver.resolve(project)
val npmProjectLayout = NpmProjectLayout[project]
configFile.writeText(buildConfig())
val execFactory = execHandleFactory.newExec()
npmProjectLayout.useTool(
execFactory,
".bin/webpack",
"--config", configFile.absolutePath
)
val exec = execFactory.build()
exec.start()
exec.waitForFinish()
}
private fun buildConfig(): String {
return buildString {
//language=JavaScript 1.8
append(
"""
const config = {
mode: 'development',
entry: '${entry.canonicalPath}',
output: {
path: '${outputPath.canonicalPath}',
filename: '${entry.name}'
},
resolve: {
modules: [
"node_modules"
]
},
plugins: [],
module: {
rules: []
}
};
"""
)
if (saveConfigFileEffective) {
//language=JavaScript 1.8
append(
"""
const util = require('util');
const fs = require("fs");
const evaluatedConfig = util.inspect(config, {showHidden: false, depth: null, compact: false});
fs.writeFile(${jsQuotedString(configFileEffective.canonicalPath)}, evaluatedConfig, (err) => {});
"""
)
if (project.gradle.startParameter.isContinuous) {
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)
}
if (report) {
//language=JavaScript 1.8
append(
"""
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
config.plugins.push(new BundleAnalyzerPlugin({
analyzerMode: "static",
reportFilename: "${entry.name}.report.html",
openAnalyzer: false,
generateStatsFile: true,
statsFilename: "${entry.name}.stats.json"
})
)
"""
)
}
loadConfigs(configDirectory)
append("module.exports = config")
} else {
runner.execute()
}
}
private fun jsQuotedString(str: String) = StringWriter().also {
JsonWriter(it).value(str)
}.toString()
internal open class Handle @Inject constructor(val runner: KotlinWebpackRunner) : DeploymentHandle {
var process: ExecHandle? = null
private fun StringBuilder.loadConfigs(confDir: File) {
if (confDir.isDirectory) confDir
.listFiles()
?.toList()
?.filter { it.name.endsWith(".js") }
?.forEach {
append(it.readText())
appendln()
appendln()
}
override fun isRunning() = process != null
override fun start(deployment: Deployment) {
process = runner.start()
}
override fun stop() {
process?.abort()
}
}
companion object {
@@ -174,6 +126,27 @@ open class KotlinWebpack : DefaultTask() {
it.entry = npmProject.moduleOutput(compileKotlinTask)
}
compilation.dependencies {
runtimeOnly(npm("webpack-dev-server", "3.3.1"))
}
project.createOrRegisterTask<KotlinWebpack>("run") {
val compileKotlinTask = compilation.compileKotlinTask
compileKotlinTask as Kotlin2JsCompile
it.dependsOn(compileKotlinTask)
it.bin = "webpack-dev-server"
it.entry = npmProject.moduleOutput(compileKotlinTask)
val projectDir = compilation.target.project.projectDir.canonicalPath
it.devServer = KotlinWebpackConfig.DevServer(
contentBase = listOf("$projectDir/src/main/resources")
)
it.outputs.upToDateWhen { false }
}
}
}
}
@@ -0,0 +1,144 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("NodeJsCodingAssistanceForCoreModules", "JSUnresolvedFunction")
package org.jetbrains.kotlin.gradle.targets.js.webpack
import com.google.gson.GsonBuilder
import com.google.gson.stream.JsonWriter
import java.io.File
import java.io.Serializable
import java.io.StringWriter
@Suppress("MemberVisibilityCanBePrivate")
class KotlinWebpackConfig(
val entry: File,
val reportEvaluatedConfigFile: File?,
val outputPath: File,
val configDirectory: File,
val reportDir: File?,
var devServer: DevServer? = null,
val showProgress: Boolean = false
) {
@Suppress("unused")
data class BundleAnalyzerPlugin(
val analyzerMode: String,
val reportFilename: String,
val openAnalyzer: Boolean,
val generateStatsFile: Boolean,
val statsFilename: String
) : Serializable
@Suppress("unused")
data class DevServer(
val inline: Boolean = true,
val lazy: Boolean = false,
val noInfo: Boolean = true,
val open: Any = true,
val overlay: Any = false,
val port: Int = 8080,
val proxy: Map<String, Any>? = null,
val contentBase: List<String>
) : Serializable
fun save(configFile: File) {
configFile.writeText(build())
}
fun build() = buildString {
//language=JavaScript 1.8
appendln(
"""
var config = {
mode: 'development',
entry: '${entry.canonicalPath}',
output: {
path: '${outputPath.canonicalPath}',
filename: '${entry.name}'
},
resolve: {
modules: [
"node_modules"
]
},
plugins: [],
module: {
rules: []
}
};"""
)
appendln()
if (devServer != null) {
appendln("// dev server")
appendln("config.devServer = ${json(devServer!!)};")
appendln()
}
if (reportDir != null) {
val reportBasePath = "${reportDir.canonicalPath}/${entry.name}"
val config = KotlinWebpackConfig.BundleAnalyzerPlugin(
"static",
"$reportBasePath.report.html",
false,
true,
"$reportBasePath.stats.json"
)
//language=JavaScript 1.8
appendln(
"""
// save webpack-bundle-analyzer report
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
config.plugins.push(new BundleAnalyzerPlugin(${json(config)}));
"""
)
}
appendln()
loadConfigs(configDirectory)
appendln()
if (reportEvaluatedConfigFile != null) {
val filePath = jsQuotedString(reportEvaluatedConfigFile.canonicalPath)
//language=JavaScript 1.8
appendln(
"""
// save evaluated config file
var util = require('util');
var fs = require("fs");
var evaluatedConfig = util.inspect(config, {showHidden: false, depth: null, compact: false});
fs.writeFile($filePath, evaluatedConfig, function (err) {});
"""
)
appendln()
}
appendln("module.exports = config")
}
private fun json(obj: Any) = StringWriter().also {
GsonBuilder().setPrettyPrinting().create().toJson(obj, it)
}.toString()
private fun jsQuotedString(str: String) = StringWriter().also {
JsonWriter(it).value(str)
}.toString()
private fun StringBuilder.loadConfigs(confDir: File) {
if (confDir.isDirectory) confDir
.listFiles()
?.toList()
?.filter { it.name.endsWith(".js") }
?.forEach {
appendln("// ${it.name}")
append(it.readText())
appendln()
appendln()
}
}
}
@@ -0,0 +1,56 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* 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.api.Project
import org.gradle.process.internal.ExecHandle
import org.gradle.process.internal.ExecHandleFactory
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(
val project: Project,
val configFile: File,
val execHandleFactory: ExecHandleFactory,
val bin: String,
val config: KotlinWebpackConfig
) {
fun execute() {
val exec = start()
exec.waitForFinish()
}
fun start(): ExecHandle {
check(config.entry.isFile) {
"${this}: Entry file not existed \"${config.entry}\""
}
NpmResolver.resolve(project)
val npmProjectLayout = NpmProject[project]
config.save(configFile)
val execFactory = execHandleFactory.newExec()
val args = mutableListOf<String>("--config", configFile.absolutePath)
if (config.showProgress) {
args.add("--progress")
}
npmProjectLayout.useTool(execFactory, ".bin/$bin")
val exec = execFactory.build()
exec.start()
return exec
}
}
@@ -26,28 +26,19 @@ object Yarn : NpmApi {
project: Project,
description: String,
vararg args: String,
npmProjectLayout: NpmProjectLayout = NpmProjectLayout[project]
npmProject: NpmProject = NpmProject[project]
) {
val nodeJsEnv = NodeJsPlugin.apply(project).root.environment
val yarnEnv = YarnPlugin.apply(project).environment
val packageJsonHashFile = project.packageJsonHashFile
val packageJsonHash = if (packageJsonHashFile.exists()) packageJsonHashFile.readText() else null
val hasher = (project as ProjectInternal).services.get(FileHasher::class.java)
val hash = hasher.hash(npmProjectLayout.packageJsonFile).toByteArray().toHexString()
if (packageJsonHash == hash) return
packageJsonHashFile.writeText(hash)
val nodeWorkDir = npmProjectLayout.nodeWorkDir
val nodeWorkDir = npmProject.nodeWorkDir
project.execWithProgress(description) { exec ->
exec.executable = nodeJsEnv.nodeExecutable
exec.args = listOf(yarnEnv.home.resolve("bin/yarn.js").absolutePath) + args
exec.workingDir = nodeWorkDir
}
}
private fun yarnLockReadTransitiveDependencies(
@@ -67,11 +58,13 @@ object Yarn : NpmApi {
val deps = byKey[key]
if (deps != null) {
src.dependencies.addAll(deps.dependencies.map { dep ->
val scopedName = dep.scopedName
resolveRecursively(
NpmDependency(
src.project,
dep.group,
dep.packageName,
scopedName.scope,
scopedName.name,
dep.version ?: "*"
)
)
@@ -93,8 +86,10 @@ object Yarn : NpmApi {
override fun resolveProject(npmPackage: NpmResolver.NpmPackage) {
val project = npmPackage.project
if (!project.yarn.useWorkspaces) {
yarnExec(project, NpmApi.resolveOperationDescription("yarn for ${project.path}"))
yarnLockReadTransitiveDependencies(NpmProjectLayout[project].nodeWorkDir, npmPackage.npmDependencies)
YarnAvoidance(project).updateIfNeeded {
yarnExec(project, NpmApi.resolveOperationDescription("yarn for ${project.path}"))
yarnLockReadTransitiveDependencies(NpmProject[project].nodeWorkDir, npmPackage.npmDependencies)
}
}
}
@@ -127,8 +122,7 @@ object Yarn : NpmApi {
check(rootProject == rootProject.rootProject)
if (rootProject.yarn.useWorkspaces) {
yarnExec(rootProject, NpmApi.resolveOperationDescription("yarn"))
yarnLockReadTransitiveDependencies(NpmProjectLayout[rootProject].nodeWorkDir, subprojects.flatMap { it.npmDependencies })
resolveWorkspaces(rootProject, subprojects)
} else {
if (subprojects.any { it.project != rootProject }) {
// todo: proofread message
@@ -144,6 +138,24 @@ object Yarn : NpmApi {
}
}
private fun resolveWorkspaces(
rootProject: Project,
subprojects: MutableList<NpmResolver.NpmPackage>
) {
val upToDateChecks = subprojects.map {
YarnAvoidance(it.project)
}
if (upToDateChecks.all { it.upToDate }) return
yarnExec(rootProject, NpmApi.resolveOperationDescription("yarn"))
yarnLockReadTransitiveDependencies(NpmProject[rootProject].nodeWorkDir, subprojects.flatMap { it.npmDependencies })
upToDateChecks.forEach {
it.commit()
}
}
override fun cleanProject(project: Project) {
super.cleanProject(project)
project.packageJsonHashFile.delete()
@@ -0,0 +1,41 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.targets.js.yarn
import org.gradle.api.Project
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.internal.hash.FileHasher
import org.jetbrains.kotlin.daemon.common.toHexString
import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject
import org.jetbrains.kotlin.gradle.targets.js.yarn.Yarn.packageJsonHashFile
class YarnAvoidance(val project: Project) {
private val prevHash by lazy {
val packageJsonHashFile = project.packageJsonHashFile
if (packageJsonHashFile.exists()) packageJsonHashFile.readText() else null
}
private val hash by lazy {
val hasher = (project as ProjectInternal).services.get(FileHasher::class.java)
hasher.hash(project.npmProject.packageJsonFile).toByteArray().toHexString()
}
val upToDate: Boolean
get() = prevHash == hash
fun commit() {
if (!upToDate) {
project.packageJsonHashFile.writeText(hash)
}
}
inline fun updateIfNeeded(body: () -> Unit) {
if (!upToDate) {
body()
commit()
}
}
}
@@ -50,10 +50,11 @@ open class YarnSetupTask : DefaultTask() {
}
private fun extract(archive: File, destination: File) {
val dirInTar = archive.name.removeSuffix(".tar.gz") + File.pathSeparator
val dirInTar = archive.name.removeSuffix(".tar.gz") + File.separator
project.copy {
it.from(project.tarTree(archive))
it.into(destination)
it.includeEmptyDirs = false
it.eachFile { fileCopy ->
fileCopy.path = fileCopy.path.removePrefix(dirInTar)
}
@@ -53,16 +53,16 @@ internal fun <T : Task> Project.createOrRegisterTask(name: String, type: Class<T
/**
* Locates a task by [name] and [type], without triggering its creation or configuration.
*/
internal fun <T : Task> locateTask(project: Project, name: String, type: Class<T>): TaskHolder<T>? =
internal inline fun <reified T : Task> Project.locateTask(name: String): TaskHolder<T>? =
if (canLocateTask) {
try {
TaskProviderHolder(name, project, project.tasks.named(name, type))
TaskProviderHolder(name, this, tasks.named(name, T::class.java))
} catch (e: UnknownTaskException) {
null
}
} else {
project.tasks.findByName(name)?.let {
check(type.isInstance(it))
tasks.findByName(name)?.let {
check(T::class.java.isInstance(it))
@Suppress("UNCHECKED_CAST")
LegacyTaskHolder(it as T)
@@ -74,7 +74,7 @@ internal fun <T : Task> locateTask(project: Project, name: String, type: Class<T
* with [name], type [T] and initialization script [body]
*/
internal inline fun <reified T : Task> Project.locateOrRegisterTask(name: String, noinline body: (T) -> (Unit)): TaskHolder<T> {
return locateTask(project, name, T::class.java) ?: registerTask(project, name, T::class.java, body)
return project.locateTask(name) ?: registerTask(project, name, T::class.java, body)
}
internal open class KotlinTasksProvider(val targetName: String) {