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 package org.jetbrains.kotlin.gradle.targets.js.nodejs
import org.jetbrains.kotlin.gradle.tasks.internal.CleanableStore
import java.io.File import java.io.File
internal data class NodeJsEnv( data class NodeJsEnv(
val cleanableStore: CleanableStore,
val nodeDir: File, val nodeDir: File,
val nodeBinDir: File, val nodeBinDir: File,
val nodeExecutable: String, val nodeExecutable: String,
@@ -72,7 +72,7 @@ open class NodeJsExec : AbstractExecTask<NodeJsExec>(NodeJsExec::class.java), Re
return project.registerTask(name) { return project.registerTask(name) {
it.nodeJs = nodeJs it.nodeJs = nodeJs
it.compilation = compilation it.compilation = compilation
it.executable = nodeJs.environment.nodeExecutable it.executable = nodeJs.requireConfigured().nodeExecutable
it.dependsOn(nodeJs.npmInstallTask) it.dependsOn(nodeJs.npmInstallTask)
val compileKotlinTask = compilation.compileKotlinTask val compileKotlinTask = compilation.compileKotlinTask
@@ -1,6 +1,7 @@
package org.jetbrains.kotlin.gradle.targets.js.nodejs package org.jetbrains.kotlin.gradle.targets.js.nodejs
import org.gradle.api.Project import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.internal.ConfigurationPhaseAware
import org.jetbrains.kotlin.gradle.logging.kotlinInfo import org.jetbrains.kotlin.gradle.logging.kotlinInfo
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
import org.jetbrains.kotlin.gradle.targets.js.NpmVersions 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 org.jetbrains.kotlin.gradle.tasks.internal.CleanableStore
import java.io.File import java.io.File
open class NodeJsRootExtension(val rootProject: Project) { open class NodeJsRootExtension(val rootProject: Project) : ConfigurationPhaseAware<NodeJsEnv>() {
init { init {
check(rootProject.rootProject == rootProject) check(rootProject.rootProject == rootProject)
} }
@@ -20,16 +21,16 @@ open class NodeJsRootExtension(val rootProject: Project) {
rootProject.logger.kotlinInfo("Storing cached files in $it") rootProject.logger.kotlinInfo("Storing cached files in $it")
} }
var installationDir = gradleHome.resolve("nodejs") var installationDir by Property(gradleHome.resolve("nodejs"))
var cleanableStore = CleanableStore[installationDir.absolutePath]
var download = true var download by Property(true)
var nodeDownloadBaseUrl = "https://nodejs.org/dist"
var nodeVersion = "12.14.0"
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) private val projectProperties = PropertiesProvider(rootProject)
@@ -61,38 +62,39 @@ open class NodeJsRootExtension(val rootProject: Project) {
val nodeModulesGradleCacheDir: File val nodeModulesGradleCacheDir: File
get() = rootPackageDir.resolve("packages_imported") get() = rootPackageDir.resolve("packages_imported")
internal val environment: NodeJsEnv override fun finalizeConfiguration(): NodeJsEnv {
get() { val platform = NodeJsPlatform.name
val platform = NodeJsPlatform.name val architecture = NodeJsPlatform.architecture
val architecture = NodeJsPlatform.architecture
val nodeDirName = "node-v$nodeVersion-$platform-$architecture" val nodeDirName = "node-v$nodeVersion-$platform-$architecture"
val nodeDir = cleanableStore[nodeDirName].use() val cleanableStore = CleanableStore[installationDir.absolutePath]
val isWindows = NodeJsPlatform.name == NodeJsPlatform.WIN val nodeDir = cleanableStore[nodeDirName].use()
val nodeBinDir = if (isWindows) nodeDir else nodeDir.resolve("bin") val isWindows = NodeJsPlatform.name == NodeJsPlatform.WIN
val nodeBinDir = if (isWindows) nodeDir else nodeDir.resolve("bin")
fun getExecutable(command: String, customCommand: String, windowsExtension: String): String { fun getExecutable(command: String, customCommand: String, windowsExtension: String): String {
val finalCommand = if (isWindows && customCommand == command) "$command.$windowsExtension" else customCommand val finalCommand = if (isWindows && customCommand == command) "$command.$windowsExtension" else customCommand
return if (download) File(nodeBinDir, finalCommand).absolutePath else finalCommand 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 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() { internal fun executeSetup() {
val nodeJsEnv = environment val nodeJsEnv = requireConfigured()
if (download) { if (download) {
if (!nodeJsEnv.nodeBinDir.isDirectory) { if (!nodeJsEnv.nodeBinDir.isDirectory) {
nodeJsSetupTask.exec() nodeJsSetupTask.exec()
@@ -22,14 +22,14 @@ open class NodeJsRootPlugin : Plugin<Project> {
it.description = "Download and install a local node/npm version" 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.dependsOn(setupTask)
it.group = TASKS_GROUP_NAME it.group = TASKS_GROUP_NAME
it.description = "Find, download and link NPM dependencies and projects" it.description = "Find, download and link NPM dependencies and projects"
} }
tasks.create("node" + CleanDataTask.NAME, CleanDataTask::class.java) { tasks.register("node" + CleanDataTask.NAME_SUFFIX, CleanDataTask::class.java) {
it.cleanableStore = settings.cleanableStore it.cleanableStore = settings.requireConfigured().cleanableStore
it.group = TASKS_GROUP_NAME it.group = TASKS_GROUP_NAME
it.description = "Clean unused local node version" it.description = "Clean unused local node version"
} }
@@ -15,7 +15,7 @@ import java.net.URI
@CacheableTask @CacheableTask
open class NodeJsSetupTask : DefaultTask() { open class NodeJsSetupTask : DefaultTask() {
private val settings get() = NodeJsRootPlugin.apply(project.rootProject) 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 val ivyDependency: String
@Input get() = env.ivyDependency @Input get() = env.ivyDependency
@@ -70,7 +70,7 @@ open class NpmProject(val compilation: KotlinJsCompilation) {
fun useTool(exec: ExecSpec, tool: String, vararg args: String) { fun useTool(exec: ExecSpec, tool: String, vararg args: String) {
exec.workingDir = dir exec.workingDir = dir
exec.executable = nodeJs.environment.nodeExecutable exec.executable = nodeJs.requireConfigured().nodeExecutable
exec.args = listOf(require(tool)) + args exec.args = listOf(require(tool)) + args
} }
@@ -110,7 +110,7 @@ open class KotlinJsTest : KotlinTest(), RequiresNpmDependencies {
override fun createTestExecutionSpec(): TCServiceMessagesTestExecutionSpec { override fun createTestExecutionSpec(): TCServiceMessagesTestExecutionSpec {
val forkOptions = DefaultProcessForkOptions(fileResolver) val forkOptions = DefaultProcessForkOptions(fileResolver)
forkOptions.workingDir = compilation.npmProject.dir forkOptions.workingDir = compilation.npmProject.dir
forkOptions.executable = nodeJs.environment.nodeExecutable forkOptions.executable = nodeJs.requireConfigured().nodeExecutable
val nodeJsArgs = mutableListOf<String>() val nodeJsArgs = mutableListOf<String>()
@@ -24,13 +24,11 @@ abstract class YarnBasics : NpmApi {
vararg args: String vararg args: String
) { ) {
val nodeJs = NodeJsRootPlugin.apply(project) val nodeJs = NodeJsRootPlugin.apply(project)
val nodeJsEnv = nodeJs.environment
val yarnPlugin = YarnPlugin.apply(project) val yarnPlugin = YarnPlugin.apply(project)
val yarnEnv = yarnPlugin.environment
project.execWithProgress(description) { exec -> project.execWithProgress(description) { exec ->
exec.executable = nodeJsEnv.nodeExecutable exec.executable = nodeJs.requireConfigured().nodeExecutable
exec.args = listOf(yarnEnv.home.resolve("bin/yarn.js").absolutePath) + exec.args = listOf(yarnPlugin.requireConfigured().home.resolve("bin/yarn.js").absolutePath) +
args + args +
if (project.logger.isDebugEnabled) "--verbose" else "" if (project.logger.isDebugEnabled) "--verbose" else ""
exec.workingDir = dir exec.workingDir = dir
@@ -5,9 +5,11 @@
package org.jetbrains.kotlin.gradle.targets.js.yarn package org.jetbrains.kotlin.gradle.targets.js.yarn
import org.jetbrains.kotlin.gradle.tasks.internal.CleanableStore
import java.io.File import java.io.File
internal data class YarnEnv( data class YarnEnv(
val downloadUrl: String, val downloadUrl: String,
val cleanableStore: CleanableStore,
val home: File val home: File
) )
@@ -9,7 +9,6 @@ import org.gradle.api.Plugin
import org.gradle.api.Project import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin
import org.jetbrains.kotlin.gradle.tasks.CleanDataTask import org.jetbrains.kotlin.gradle.tasks.CleanDataTask
import org.jetbrains.kotlin.gradle.tasks.internal.CleanableStore
open class YarnPlugin : Plugin<Project> { open class YarnPlugin : Plugin<Project> {
override fun apply(project: Project): Unit = project.run { override fun apply(project: Project): Unit = project.run {
@@ -25,8 +24,8 @@ open class YarnPlugin : Plugin<Project> {
it.dependsOn(nodeJs.nodeJsSetupTask) it.dependsOn(nodeJs.nodeJsSetupTask)
} }
tasks.create("yarn" + CleanDataTask.NAME, CleanDataTask::class.java) { tasks.create("yarn" + CleanDataTask.NAME_SUFFIX, CleanDataTask::class.java) {
it.cleanableStore = yarnRootExtension.cleanableStore it.cleanableStore = yarnRootExtension.requireConfigured().cleanableStore
it.description = "Clean unused local yarn version" it.description = "Clean unused local yarn version"
} }
} }
@@ -6,11 +6,12 @@
package org.jetbrains.kotlin.gradle.targets.js.yarn package org.jetbrains.kotlin.gradle.targets.js.yarn
import org.gradle.api.Project import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.internal.ConfigurationPhaseAware
import org.jetbrains.kotlin.gradle.logging.kotlinInfo import org.jetbrains.kotlin.gradle.logging.kotlinInfo
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin
import org.jetbrains.kotlin.gradle.tasks.internal.CleanableStore import org.jetbrains.kotlin.gradle.tasks.internal.CleanableStore
open class YarnRootExtension(val project: Project) { open class YarnRootExtension(val project: Project) : ConfigurationPhaseAware<YarnEnv>() {
init { init {
check(project == project.rootProject) check(project == project.rootProject)
} }
@@ -19,32 +20,33 @@ open class YarnRootExtension(val project: Project) {
project.logger.kotlinInfo("Storing cached files in $it") 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 var downloadBaseUrl by Property("https://github.com/yarnpkg/yarn/releases/download")
val cleanableStore = CleanableStore[installationDir.path] var version by Property("1.21.1")
var downloadBaseUrl = "https://github.com/yarnpkg/yarn/releases/download"
var version = "1.21.1"
val yarnSetupTask: YarnSetupTask val yarnSetupTask: YarnSetupTask
get() = project.tasks.getByName(YarnSetupTask.NAME) as YarnSetupTask get() = project.tasks.getByName(YarnSetupTask.NAME) as YarnSetupTask
var disableWorkspaces: Boolean = false var disableWorkspaces: Boolean by Property(false)
val useWorkspaces: Boolean val useWorkspaces: Boolean
get() = !disableWorkspaces get() = !disableWorkspaces
// TODO: Split configuration and execution phases to support changed settings override fun finalizeConfiguration(): YarnEnv {
internal val environment = YarnEnv( val cleanableStore = CleanableStore[installationDir.path]
downloadUrl = "$downloadBaseUrl/v$version/yarn-v$version.tar.gz",
home = cleanableStore["yarn-v$version"].use() return YarnEnv(
) downloadUrl = "$downloadBaseUrl/v$version/yarn-v$version.tar.gz",
cleanableStore = cleanableStore,
home = cleanableStore["yarn-v$version"].use()
)
}
internal fun executeSetup() { internal fun executeSetup() {
NodeJsRootPlugin.apply(project).executeSetup() NodeJsRootPlugin.apply(project).executeSetup()
if (!environment.home.isDirectory) { if (!finalizeConfiguration().home.isDirectory) {
yarnSetupTask.setup() yarnSetupTask.setup()
} }
} }
@@ -17,7 +17,7 @@ import java.io.File
@CacheableTask @CacheableTask
open class YarnSetupTask : DefaultTask() { open class YarnSetupTask : DefaultTask() {
private val settings = project.yarn private val settings = project.yarn
private val env by lazy { settings.environment } private val env by lazy { settings.requireConfigured() }
@Suppress("MemberVisibilityCanBePrivate") @Suppress("MemberVisibilityCanBePrivate")
val downloadUrl val downloadUrl
@@ -41,7 +41,7 @@ open class CleanDataTask : DefaultTask() {
} }
companion object { companion object {
const val NAME: String = "KotlinClean" const val NAME_SUFFIX: String = "KotlinClean"
} }
} }