NodeJsRootExtension, YarnRootExtension: introduce configuration phase

Some properties were calculated based on yarn and nodeJs version. So,
when version is changed properties were not updated. This change moves
calculation to the end of configuration phase and adds assertion to
ensure that version and other poperties are not changed
after configuration was done.
This commit is contained in:
Sergey Rostov
2020-01-15 12:23:23 +03:00
committed by nataliya.valtman
parent 9c08ea3bf1
commit 6ac21856cc
14 changed files with 109 additions and 67 deletions
@@ -0,0 +1,37 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.internal
import kotlin.reflect.KProperty
abstract class ConfigurationPhaseAware<C : Any> {
private var configured: C? = null
@Synchronized
fun requireConfigured(): C {
if (configured == null) {
configured = finalizeConfiguration()
}
return configured!!
}
protected fun requireNotConfigured() {
check(configured == null) { "Configuration already finalized for previous property values" }
}
inner class Property<T>(var value: T) {
operator fun getValue(receiver: Any, property: KProperty<*>): T = value
operator fun setValue(receiver: Any, property: KProperty<*>, value: T) {
requireNotConfigured()
this.value = value
}
}
protected abstract fun finalizeConfiguration(): C
}
@@ -1,8 +1,10 @@
package org.jetbrains.kotlin.gradle.targets.js.nodejs
import org.jetbrains.kotlin.gradle.tasks.internal.CleanableStore
import java.io.File
internal data class NodeJsEnv(
data class NodeJsEnv(
val cleanableStore: CleanableStore,
val nodeDir: File,
val nodeBinDir: File,
val nodeExecutable: String,
@@ -72,7 +72,7 @@ open class NodeJsExec : AbstractExecTask<NodeJsExec>(NodeJsExec::class.java), Re
return project.registerTask(name) {
it.nodeJs = nodeJs
it.compilation = compilation
it.executable = nodeJs.environment.nodeExecutable
it.executable = nodeJs.requireConfigured().nodeExecutable
it.dependsOn(nodeJs.npmInstallTask)
val compileKotlinTask = compilation.compileKotlinTask
@@ -1,6 +1,7 @@
package org.jetbrains.kotlin.gradle.targets.js.nodejs
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.internal.ConfigurationPhaseAware
import org.jetbrains.kotlin.gradle.logging.kotlinInfo
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
import org.jetbrains.kotlin.gradle.targets.js.NpmVersions
@@ -11,7 +12,7 @@ import org.jetbrains.kotlin.gradle.targets.js.yarn.Yarn
import org.jetbrains.kotlin.gradle.tasks.internal.CleanableStore
import java.io.File
open class NodeJsRootExtension(val rootProject: Project) {
open class NodeJsRootExtension(val rootProject: Project) : ConfigurationPhaseAware<NodeJsEnv>() {
init {
check(rootProject.rootProject == rootProject)
}
@@ -20,16 +21,16 @@ open class NodeJsRootExtension(val rootProject: Project) {
rootProject.logger.kotlinInfo("Storing cached files in $it")
}
var installationDir = gradleHome.resolve("nodejs")
var cleanableStore = CleanableStore[installationDir.absolutePath]
var installationDir by Property(gradleHome.resolve("nodejs"))
var download = true
var nodeDownloadBaseUrl = "https://nodejs.org/dist"
var nodeVersion = "12.14.0"
var download by Property(true)
var nodeCommand = "node"
var nodeDownloadBaseUrl by Property("https://nodejs.org/dist")
var nodeVersion by Property("12.14.0")
var packageManager: NpmApi = Yarn
var nodeCommand by Property("node")
var packageManager: NpmApi by Property(Yarn)
private val projectProperties = PropertiesProvider(rootProject)
@@ -61,38 +62,39 @@ open class NodeJsRootExtension(val rootProject: Project) {
val nodeModulesGradleCacheDir: File
get() = rootPackageDir.resolve("packages_imported")
internal val environment: NodeJsEnv
get() {
val platform = NodeJsPlatform.name
val architecture = NodeJsPlatform.architecture
override fun finalizeConfiguration(): NodeJsEnv {
val platform = NodeJsPlatform.name
val architecture = NodeJsPlatform.architecture
val nodeDirName = "node-v$nodeVersion-$platform-$architecture"
val nodeDir = cleanableStore[nodeDirName].use()
val isWindows = NodeJsPlatform.name == NodeJsPlatform.WIN
val nodeBinDir = if (isWindows) nodeDir else nodeDir.resolve("bin")
val nodeDirName = "node-v$nodeVersion-$platform-$architecture"
val cleanableStore = CleanableStore[installationDir.absolutePath]
val nodeDir = cleanableStore[nodeDirName].use()
val isWindows = NodeJsPlatform.name == NodeJsPlatform.WIN
val nodeBinDir = if (isWindows) nodeDir else nodeDir.resolve("bin")
fun getExecutable(command: String, customCommand: String, windowsExtension: String): String {
val finalCommand = if (isWindows && customCommand == command) "$command.$windowsExtension" else customCommand
return if (download) File(nodeBinDir, finalCommand).absolutePath else finalCommand
}
fun getIvyDependency(): String {
val type = if (isWindows) "zip" else "tar.gz"
return "org.nodejs:node:$nodeVersion:$platform-$architecture@$type"
}
return NodeJsEnv(
nodeDir = nodeDir,
nodeBinDir = nodeBinDir,
nodeExecutable = getExecutable("node", nodeCommand, "exe"),
platformName = platform,
architectureName = architecture,
ivyDependency = getIvyDependency()
)
fun getExecutable(command: String, customCommand: String, windowsExtension: String): String {
val finalCommand = if (isWindows && customCommand == command) "$command.$windowsExtension" else customCommand
return if (download) File(nodeBinDir, finalCommand).absolutePath else finalCommand
}
fun getIvyDependency(): String {
val type = if (isWindows) "zip" else "tar.gz"
return "org.nodejs:node:$nodeVersion:$platform-$architecture@$type"
}
return NodeJsEnv(
cleanableStore = cleanableStore,
nodeDir = nodeDir,
nodeBinDir = nodeBinDir,
nodeExecutable = getExecutable("node", nodeCommand, "exe"),
platformName = platform,
architectureName = architecture,
ivyDependency = getIvyDependency()
)
}
internal fun executeSetup() {
val nodeJsEnv = environment
val nodeJsEnv = requireConfigured()
if (download) {
if (!nodeJsEnv.nodeBinDir.isDirectory) {
nodeJsSetupTask.exec()
@@ -22,14 +22,14 @@ open class NodeJsRootPlugin : Plugin<Project> {
it.description = "Download and install a local node/npm version"
}
tasks.create(KotlinNpmInstallTask.NAME, KotlinNpmInstallTask::class.java) {
tasks.register(KotlinNpmInstallTask.NAME, KotlinNpmInstallTask::class.java) {
it.dependsOn(setupTask)
it.group = TASKS_GROUP_NAME
it.description = "Find, download and link NPM dependencies and projects"
}
tasks.create("node" + CleanDataTask.NAME, CleanDataTask::class.java) {
it.cleanableStore = settings.cleanableStore
tasks.register("node" + CleanDataTask.NAME_SUFFIX, CleanDataTask::class.java) {
it.cleanableStore = settings.requireConfigured().cleanableStore
it.group = TASKS_GROUP_NAME
it.description = "Clean unused local node version"
}
@@ -15,7 +15,7 @@ import java.net.URI
@CacheableTask
open class NodeJsSetupTask : DefaultTask() {
private val settings get() = NodeJsRootPlugin.apply(project.rootProject)
private val env by lazy { settings.environment }
private val env by lazy { settings.requireConfigured() }
val ivyDependency: String
@Input get() = env.ivyDependency
@@ -70,7 +70,7 @@ open class NpmProject(val compilation: KotlinJsCompilation) {
fun useTool(exec: ExecSpec, tool: String, vararg args: String) {
exec.workingDir = dir
exec.executable = nodeJs.environment.nodeExecutable
exec.executable = nodeJs.requireConfigured().nodeExecutable
exec.args = listOf(require(tool)) + args
}
@@ -110,7 +110,7 @@ open class KotlinJsTest : KotlinTest(), RequiresNpmDependencies {
override fun createTestExecutionSpec(): TCServiceMessagesTestExecutionSpec {
val forkOptions = DefaultProcessForkOptions(fileResolver)
forkOptions.workingDir = compilation.npmProject.dir
forkOptions.executable = nodeJs.environment.nodeExecutable
forkOptions.executable = nodeJs.requireConfigured().nodeExecutable
val nodeJsArgs = mutableListOf<String>()
@@ -24,13 +24,11 @@ abstract class YarnBasics : NpmApi {
vararg args: String
) {
val nodeJs = NodeJsRootPlugin.apply(project)
val nodeJsEnv = nodeJs.environment
val yarnPlugin = YarnPlugin.apply(project)
val yarnEnv = yarnPlugin.environment
project.execWithProgress(description) { exec ->
exec.executable = nodeJsEnv.nodeExecutable
exec.args = listOf(yarnEnv.home.resolve("bin/yarn.js").absolutePath) +
exec.executable = nodeJs.requireConfigured().nodeExecutable
exec.args = listOf(yarnPlugin.requireConfigured().home.resolve("bin/yarn.js").absolutePath) +
args +
if (project.logger.isDebugEnabled) "--verbose" else ""
exec.workingDir = dir
@@ -5,9 +5,11 @@
package org.jetbrains.kotlin.gradle.targets.js.yarn
import org.jetbrains.kotlin.gradle.tasks.internal.CleanableStore
import java.io.File
internal data class YarnEnv(
data class YarnEnv(
val downloadUrl: String,
val cleanableStore: CleanableStore,
val home: File
)
@@ -9,7 +9,6 @@ import org.gradle.api.Plugin
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin
import org.jetbrains.kotlin.gradle.tasks.CleanDataTask
import org.jetbrains.kotlin.gradle.tasks.internal.CleanableStore
open class YarnPlugin : Plugin<Project> {
override fun apply(project: Project): Unit = project.run {
@@ -25,8 +24,8 @@ open class YarnPlugin : Plugin<Project> {
it.dependsOn(nodeJs.nodeJsSetupTask)
}
tasks.create("yarn" + CleanDataTask.NAME, CleanDataTask::class.java) {
it.cleanableStore = yarnRootExtension.cleanableStore
tasks.create("yarn" + CleanDataTask.NAME_SUFFIX, CleanDataTask::class.java) {
it.cleanableStore = yarnRootExtension.requireConfigured().cleanableStore
it.description = "Clean unused local yarn version"
}
}
@@ -6,11 +6,12 @@
package org.jetbrains.kotlin.gradle.targets.js.yarn
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.internal.ConfigurationPhaseAware
import org.jetbrains.kotlin.gradle.logging.kotlinInfo
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin
import org.jetbrains.kotlin.gradle.tasks.internal.CleanableStore
open class YarnRootExtension(val project: Project) {
open class YarnRootExtension(val project: Project) : ConfigurationPhaseAware<YarnEnv>() {
init {
check(project == project.rootProject)
}
@@ -19,32 +20,33 @@ open class YarnRootExtension(val project: Project) {
project.logger.kotlinInfo("Storing cached files in $it")
}
var installationDir = gradleHome.resolve("yarn")
var installationDir by Property(gradleHome.resolve("yarn"))
// TODO: Split configuration and execution phases to support changed installationDir
val cleanableStore = CleanableStore[installationDir.path]
var downloadBaseUrl = "https://github.com/yarnpkg/yarn/releases/download"
var version = "1.21.1"
var downloadBaseUrl by Property("https://github.com/yarnpkg/yarn/releases/download")
var version by Property("1.21.1")
val yarnSetupTask: YarnSetupTask
get() = project.tasks.getByName(YarnSetupTask.NAME) as YarnSetupTask
var disableWorkspaces: Boolean = false
var disableWorkspaces: Boolean by Property(false)
val useWorkspaces: Boolean
get() = !disableWorkspaces
// TODO: Split configuration and execution phases to support changed settings
internal val environment = YarnEnv(
downloadUrl = "$downloadBaseUrl/v$version/yarn-v$version.tar.gz",
home = cleanableStore["yarn-v$version"].use()
)
override fun finalizeConfiguration(): YarnEnv {
val cleanableStore = CleanableStore[installationDir.path]
return YarnEnv(
downloadUrl = "$downloadBaseUrl/v$version/yarn-v$version.tar.gz",
cleanableStore = cleanableStore,
home = cleanableStore["yarn-v$version"].use()
)
}
internal fun executeSetup() {
NodeJsRootPlugin.apply(project).executeSetup()
if (!environment.home.isDirectory) {
if (!finalizeConfiguration().home.isDirectory) {
yarnSetupTask.setup()
}
}
@@ -17,7 +17,7 @@ import java.io.File
@CacheableTask
open class YarnSetupTask : DefaultTask() {
private val settings = project.yarn
private val env by lazy { settings.environment }
private val env by lazy { settings.requireConfigured() }
@Suppress("MemberVisibilityCanBePrivate")
val downloadUrl
@@ -41,7 +41,7 @@ open class CleanDataTask : DefaultTask() {
}
companion object {
const val NAME: String = "KotlinClean"
const val NAME_SUFFIX: String = "KotlinClean"
}
}