From 923986d6a0c37670988b0a546dad4a23bcb21d4c Mon Sep 17 00:00:00 2001 From: Ilya Goncharov Date: Thu, 23 Apr 2020 12:49:28 +0300 Subject: [PATCH] [Gradle, JS] Replace close on prepare #KT-34832 fixed --- .../js/dukat/DukatRootResolverPlugin.kt | 6 +- .../js/npm/KotlinNpmResolutionManager.kt | 102 +++++++++--------- .../js/npm/plugins/RootResolverPlugin.kt | 1 - .../npm/resolved/KotlinRootNpmResolution.kt | 4 +- .../js/npm/resolver/KotlinRootNpmResolver.kt | 96 +++++++++++++---- 5 files changed, 124 insertions(+), 85 deletions(-) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/dukat/DukatRootResolverPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/dukat/DukatRootResolverPlugin.kt index 00baed408b9..30e869d6ac9 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/dukat/DukatRootResolverPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/dukat/DukatRootResolverPlugin.kt @@ -19,7 +19,7 @@ internal class DukatRootResolverPlugin(val resolver: KotlinRootNpmResolver) : Ro return listOf(plugin) } - override fun resolve(resolution: KotlinRootNpmResolution) { + override fun close(resolution: KotlinRootNpmResolution) { if (resolver.forceFullResolve) { // inside idea import compilations.forEach { @@ -27,8 +27,4 @@ internal class DukatRootResolverPlugin(val resolver: KotlinRootNpmResolver) : Ro } } } - - override fun close(resolution: KotlinRootNpmResolution) { - // do nothing - } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/KotlinNpmResolutionManager.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/KotlinNpmResolutionManager.kt index 1380ca2cb3a..2822d5d0b44 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/KotlinNpmResolutionManager.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/KotlinNpmResolutionManager.kt @@ -93,12 +93,25 @@ class KotlinNpmResolutionManager(private val nodeJsSettings: NodeJsRootExtension get() = resolver.compilations.map { it.npmProject } } - open class Prepared(val resolved: KotlinRootNpmResolution) : ResolutionState() { + open class Prepared(val preparedInstallation: KotlinRootNpmResolver.Installation) : ResolutionState() { override val npmProjects: List - get() = resolved.projects.values.flatMap { it.npmProjects.map { it.npmProject } } + get() = npmProjectsByProjectResolutions(preparedInstallation.projectResolutions) } - class Installed(resolved: KotlinRootNpmResolution) : Prepared(resolved) + class Installed(val resolved: KotlinRootNpmResolution) : ResolutionState() { + override val npmProjects: List + get() = npmProjectsByProjectResolutions(resolved.projects) + } + + companion object { + fun npmProjectsByProjectResolutions( + resolutions: Map + ): List { + return resolutions + .values + .flatMap { it.npmProjects.map { it.npmProject } } + } + } } @Incubating @@ -107,49 +120,29 @@ class KotlinNpmResolutionManager(private val nodeJsSettings: NodeJsRootExtension internal fun requireConfiguringState(): KotlinRootNpmResolver = (this.state as? ResolutionState.Configuring ?: error("NPM Dependencies already resolved and installed")).resolver - internal fun prepare() = prepareIfNeeded(requireNotInstalled = true) + internal fun prepare() = prepareIfNeeded(requireNotPrepared = true) internal fun installIfNeeded( - reason: String = "", + reason: String? = "", args: List = emptyList() ): KotlinRootNpmResolution { - if (state is ResolutionState.Installed) { - return (state as ResolutionState.Installed).resolved + synchronized(this) { + if (state is ResolutionState.Installed) { + return (state as ResolutionState.Installed).resolved + } + + val installUpToDate = nodeJsSettings.npmInstallTask.state.upToDate + val forceUpToDate = installUpToDate && !forceFullResolve + + val installation = prepareIfNeeded(requireUpToDateReason = reason) + val resolution = installation + .install(forceUpToDate, args) + state = ResolutionState.Installed(resolution) + + installation.closePlugins(resolution) + + return resolution } - - val installUpToDate = nodeJsSettings.npmInstallTask.state.upToDate - val forceUpToDate = installUpToDate && !forceFullResolve - - val npmResolution = prepareIfNeeded(requireUpToDateReason = reason) - val npmResolutions = npmResolution - .projects - .values - .flatMap { it.npmProjects } - - // we need manual up-to-date checking to avoid call package manager during - // idea import if nothing was changed - // we should call it even kotlinNpmInstall task is up-to-date (skipPackageManager is true) - // because our upToDateChecks saves state for next execution - val upToDateChecks = npmResolutions.map { - PackageJsonUpToDateCheck(it.npmProject) - } - val upToDate = forceUpToDate || upToDateChecks.all { it.upToDate } - - nodeJsSettings.packageManager.resolveRootProject( - nodeJsSettings.rootProject, - npmResolutions, - upToDate, - args - ) - - upToDateChecks.forEach { it.commit() } - - return ResolutionState.Installed((state as ResolutionState.Prepared).resolved) - .apply { - state = this - npmResolution.plugins - .forEach { it.resolve(npmResolution) } - }.resolved } internal fun requireAlreadyInstalled(project: Project, reason: String = ""): KotlinProjectNpmResolution = @@ -161,39 +154,40 @@ class KotlinNpmResolutionManager(private val nodeJsSettings: NodeJsRootExtension /** * @param requireUpToDateReason Check that project already resolved, * or it is up-to-date but just not closed. Show given message if it is not. - * @param requireNotInstalled Check that project is not resolved + * @param requireNotPrepared Check that project is not prepared */ private fun prepareIfNeeded( requireUpToDateReason: String? = null, - requireNotInstalled: Boolean = false - ): KotlinRootNpmResolution { - fun alreadyResolved(resolution: KotlinRootNpmResolution): KotlinRootNpmResolution { - if (requireNotInstalled) error("Project already resolved") - return resolution + requireNotPrepared: Boolean = false + ): KotlinRootNpmResolver.Installation { + fun alreadyResolved(installation: KotlinRootNpmResolver.Installation): KotlinRootNpmResolver.Installation { + if (requireNotPrepared) error("Project already prepared") + return installation } val state0 = this.state return when (state0) { - is ResolutionState.Prepared -> alreadyResolved(state0.resolved) + is ResolutionState.Prepared -> alreadyResolved(state0.preparedInstallation) is ResolutionState.Configuring -> { synchronized(this) { val state1 = this.state when (state1) { - is ResolutionState.Prepared -> alreadyResolved(state1.resolved) + is ResolutionState.Prepared -> alreadyResolved(state1.preparedInstallation) is ResolutionState.Configuring -> { val upToDate = nodeJsSettings.rootPackageJsonTask.state.upToDate if (requireUpToDateReason != null && !upToDate) { error("NPM dependencies should be resolved $requireUpToDateReason") } - state1.resolver.close().also { + state1.resolver.prepareInstallation().also { this.state = ResolutionState.Prepared(it) - state1.resolver.closePlugins(it) } } + is ResolutionState.Installed -> error("Project already installed") } } } + is ResolutionState.Installed -> error("Project already installed") } } @@ -202,19 +196,19 @@ class KotlinNpmResolutionManager(private val nodeJsSettings: NodeJsRootExtension val resolvedProject = if (forceFullResolve) { - prepareIfNeeded() - installIfNeeded()[project] + installIfNeeded(reason = null)[project] } else { // may return null only during npm resolution // (it can be called since NpmDependency added to configuration that // requires resolve to build package.json, in this case we should just skip this call) val state0 = state when (state0) { - is ResolutionState.Prepared -> state0.resolved[project] + is ResolutionState.Prepared -> state0.preparedInstallation[project] is ResolutionState.Configuring -> { return null //error("Cannot use NpmDependency before :kotlinNpmInstall task execution") } + is ResolutionState.Installed -> state0.resolved[project] } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/plugins/RootResolverPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/plugins/RootResolverPlugin.kt index 5d2f2a7b914..23ea745f691 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/plugins/RootResolverPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/plugins/RootResolverPlugin.kt @@ -10,6 +10,5 @@ import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinCompilationNpmR internal interface RootResolverPlugin { fun createCompilationResolverPlugins(resolver: KotlinCompilationNpmResolver): List - fun resolve(resolution: KotlinRootNpmResolution) fun close(resolution: KotlinRootNpmResolution) } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/resolved/KotlinRootNpmResolution.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/resolved/KotlinRootNpmResolution.kt index b0ec1396fc6..32f61828d30 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/resolved/KotlinRootNpmResolution.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/resolved/KotlinRootNpmResolution.kt @@ -6,12 +6,10 @@ package org.jetbrains.kotlin.gradle.targets.js.npm.resolved import org.gradle.api.Project -import org.jetbrains.kotlin.gradle.targets.js.npm.plugins.RootResolverPlugin internal class KotlinRootNpmResolution( val rootProject: Project, - val projects: Map, - val plugins: List + internal val projects: Map ) { operator fun get(project: Project) = projects[project] ?: KotlinProjectNpmResolution.empty(project) } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/resolver/KotlinRootNpmResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/resolver/KotlinRootNpmResolver.kt index 2d988292e4b..449fb2665ee 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/resolver/KotlinRootNpmResolver.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/resolver/KotlinRootNpmResolver.kt @@ -15,8 +15,10 @@ import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension import org.jetbrains.kotlin.gradle.targets.js.npm.CompositeNodeModulesCache import org.jetbrains.kotlin.gradle.targets.js.npm.GradleNodeModulesCache import org.jetbrains.kotlin.gradle.targets.js.npm.KotlinNpmResolutionManager +import org.jetbrains.kotlin.gradle.targets.js.npm.PackageJsonUpToDateCheck import org.jetbrains.kotlin.gradle.targets.js.npm.plugins.RootResolverPlugin import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinCompilationNpmResolution +import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinProjectNpmResolution import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinRootNpmResolution import org.jetbrains.kotlin.gradle.tasks.registerTask @@ -36,7 +38,14 @@ internal class KotlinRootNpmResolver internal constructor( } } - private var closed: Boolean = false + enum class State { + CONFIGURING, + PROJECTS_CLOSED, + INSTALLED + } + + @Volatile + private var state: State = State.CONFIGURING val gradleNodeModules = GradleNodeModulesCache(nodeJs) val compositeNodeModules = CompositeNodeModulesCache(nodeJs) @@ -47,7 +56,7 @@ internal class KotlinRootNpmResolver internal constructor( @Synchronized fun addProject(target: Project) { - check(!closed) { alreadyResolvedMessage("add new project: $target") } + check(state == State.CONFIGURING) { alreadyResolvedMessage("add new project: $target") } projectResolvers[target] = KotlinProjectNpmResolver(target, this) } @@ -87,32 +96,75 @@ internal class KotlinRootNpmResolver internal constructor( /** * Don't use directly, use [KotlinNpmResolutionManager.installIfNeeded] instead. */ - internal fun close(): KotlinRootNpmResolution { - check(!closed) - closed = true + internal fun prepareInstallation(): Installation { + synchronized(this@KotlinRootNpmResolver) { + check(state == State.CONFIGURING) { + "Projects must be configuring" + } + state = State.PROJECTS_CLOSED - val projectResolutions = projectResolvers.values - .map { it.close() } - .associateBy { it.project } - val allNpmPackages = projectResolutions.values.flatMap { it.npmProjects } + val projectResolutions = projectResolvers.values + .map { it.close() } + .associateBy { it.project } + val allNpmPackages = projectResolutions.values.flatMap { it.npmProjects } - gradleNodeModules.close() + gradleNodeModules.close() - nodeJs.packageManager.prepareRootProject( - rootProject, - allNpmPackages - ) + nodeJs.packageManager.prepareRootProject( + rootProject, + allNpmPackages + ) - return KotlinRootNpmResolution( - rootProject, - projectResolutions, - plugins - ) + return Installation( + projectResolutions + ) + } } - internal fun closePlugins(resolution: KotlinRootNpmResolution) { - plugins.forEach { - it.close(resolution) + open inner class Installation(val projectResolutions: Map) { + operator fun get(project: Project) = + projectResolutions[project] ?: KotlinProjectNpmResolution.empty(project) + + internal fun install( + forceUpToDate: Boolean, + args: List + ): KotlinRootNpmResolution { + synchronized(this@KotlinRootNpmResolver) { + check(state == State.PROJECTS_CLOSED) { + "Projects must be closed" + } + state = State.INSTALLED + + val allNpmPackages = projectResolutions + .values + .flatMap { it.npmProjects } + + // we need manual up-to-date checking to avoid call package manager during + // idea import if nothing was changed + // we should call it even kotlinNpmInstall task is up-to-date (skipPackageManager is true) + // because our upToDateChecks saves state for next execution + val upToDateChecks = allNpmPackages.map { + PackageJsonUpToDateCheck(it.npmProject) + } + val upToDate = forceUpToDate || upToDateChecks.all { it.upToDate } + + nodeJs.packageManager.resolveRootProject( + nodeJs.rootProject, + allNpmPackages, + upToDate, + args + ) + + upToDateChecks.forEach { it.commit() } + + return KotlinRootNpmResolution(rootProject, projectResolutions) + } + } + + internal fun closePlugins(resolution: KotlinRootNpmResolution) { + plugins.forEach { + it.close(resolution) + } } }