[Gradle, JS] Replace close on prepare

#KT-34832 fixed
This commit is contained in:
Ilya Goncharov
2020-04-23 12:49:28 +03:00
parent 7fa0faf48c
commit 923986d6a0
5 changed files with 124 additions and 85 deletions
@@ -19,7 +19,7 @@ internal class DukatRootResolverPlugin(val resolver: KotlinRootNpmResolver) : Ro
return listOf(plugin) return listOf(plugin)
} }
override fun resolve(resolution: KotlinRootNpmResolution) { override fun close(resolution: KotlinRootNpmResolution) {
if (resolver.forceFullResolve) { if (resolver.forceFullResolve) {
// inside idea import // inside idea import
compilations.forEach { compilations.forEach {
@@ -27,8 +27,4 @@ internal class DukatRootResolverPlugin(val resolver: KotlinRootNpmResolver) : Ro
} }
} }
} }
override fun close(resolution: KotlinRootNpmResolution) {
// do nothing
}
} }
@@ -93,12 +93,25 @@ class KotlinNpmResolutionManager(private val nodeJsSettings: NodeJsRootExtension
get() = resolver.compilations.map { it.npmProject } 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<NpmProject> override val npmProjects: List<NpmProject>
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<NpmProject>
get() = npmProjectsByProjectResolutions(resolved.projects)
}
companion object {
fun npmProjectsByProjectResolutions(
resolutions: Map<Project, KotlinProjectNpmResolution>
): List<NpmProject> {
return resolutions
.values
.flatMap { it.npmProjects.map { it.npmProject } }
}
}
} }
@Incubating @Incubating
@@ -107,49 +120,29 @@ class KotlinNpmResolutionManager(private val nodeJsSettings: NodeJsRootExtension
internal fun requireConfiguringState(): KotlinRootNpmResolver = internal fun requireConfiguringState(): KotlinRootNpmResolver =
(this.state as? ResolutionState.Configuring ?: error("NPM Dependencies already resolved and installed")).resolver (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( internal fun installIfNeeded(
reason: String = "", reason: String? = "",
args: List<String> = emptyList() args: List<String> = emptyList()
): KotlinRootNpmResolution { ): KotlinRootNpmResolution {
if (state is ResolutionState.Installed) { synchronized(this) {
return (state as ResolutionState.Installed).resolved 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 = 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, * @param requireUpToDateReason Check that project already resolved,
* or it is up-to-date but just not closed. Show given message if it is not. * 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( private fun prepareIfNeeded(
requireUpToDateReason: String? = null, requireUpToDateReason: String? = null,
requireNotInstalled: Boolean = false requireNotPrepared: Boolean = false
): KotlinRootNpmResolution { ): KotlinRootNpmResolver.Installation {
fun alreadyResolved(resolution: KotlinRootNpmResolution): KotlinRootNpmResolution { fun alreadyResolved(installation: KotlinRootNpmResolver.Installation): KotlinRootNpmResolver.Installation {
if (requireNotInstalled) error("Project already resolved") if (requireNotPrepared) error("Project already prepared")
return resolution return installation
} }
val state0 = this.state val state0 = this.state
return when (state0) { return when (state0) {
is ResolutionState.Prepared -> alreadyResolved(state0.resolved) is ResolutionState.Prepared -> alreadyResolved(state0.preparedInstallation)
is ResolutionState.Configuring -> { is ResolutionState.Configuring -> {
synchronized(this) { synchronized(this) {
val state1 = this.state val state1 = this.state
when (state1) { when (state1) {
is ResolutionState.Prepared -> alreadyResolved(state1.resolved) is ResolutionState.Prepared -> alreadyResolved(state1.preparedInstallation)
is ResolutionState.Configuring -> { is ResolutionState.Configuring -> {
val upToDate = nodeJsSettings.rootPackageJsonTask.state.upToDate val upToDate = nodeJsSettings.rootPackageJsonTask.state.upToDate
if (requireUpToDateReason != null && !upToDate) { if (requireUpToDateReason != null && !upToDate) {
error("NPM dependencies should be resolved $requireUpToDateReason") error("NPM dependencies should be resolved $requireUpToDateReason")
} }
state1.resolver.close().also { state1.resolver.prepareInstallation().also {
this.state = ResolutionState.Prepared(it) 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 = val resolvedProject =
if (forceFullResolve) { if (forceFullResolve) {
prepareIfNeeded() installIfNeeded(reason = null)[project]
installIfNeeded()[project]
} else { } else {
// may return null only during npm resolution // may return null only during npm resolution
// (it can be called since NpmDependency added to configuration that // (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) // requires resolve to build package.json, in this case we should just skip this call)
val state0 = state val state0 = state
when (state0) { when (state0) {
is ResolutionState.Prepared -> state0.resolved[project] is ResolutionState.Prepared -> state0.preparedInstallation[project]
is ResolutionState.Configuring -> { is ResolutionState.Configuring -> {
return null return null
//error("Cannot use NpmDependency before :kotlinNpmInstall task execution") //error("Cannot use NpmDependency before :kotlinNpmInstall task execution")
} }
is ResolutionState.Installed -> state0.resolved[project]
} }
} }
@@ -10,6 +10,5 @@ import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinCompilationNpmR
internal interface RootResolverPlugin { internal interface RootResolverPlugin {
fun createCompilationResolverPlugins(resolver: KotlinCompilationNpmResolver): List<CompilationResolverPlugin> fun createCompilationResolverPlugins(resolver: KotlinCompilationNpmResolver): List<CompilationResolverPlugin>
fun resolve(resolution: KotlinRootNpmResolution)
fun close(resolution: KotlinRootNpmResolution) fun close(resolution: KotlinRootNpmResolution)
} }
@@ -6,12 +6,10 @@
package org.jetbrains.kotlin.gradle.targets.js.npm.resolved package org.jetbrains.kotlin.gradle.targets.js.npm.resolved
import org.gradle.api.Project import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.targets.js.npm.plugins.RootResolverPlugin
internal class KotlinRootNpmResolution( internal class KotlinRootNpmResolution(
val rootProject: Project, val rootProject: Project,
val projects: Map<Project, KotlinProjectNpmResolution>, internal val projects: Map<Project, KotlinProjectNpmResolution>
val plugins: List<RootResolverPlugin>
) { ) {
operator fun get(project: Project) = projects[project] ?: KotlinProjectNpmResolution.empty(project) operator fun get(project: Project) = projects[project] ?: KotlinProjectNpmResolution.empty(project)
} }
@@ -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.CompositeNodeModulesCache
import org.jetbrains.kotlin.gradle.targets.js.npm.GradleNodeModulesCache 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.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.plugins.RootResolverPlugin
import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinCompilationNpmResolution 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.targets.js.npm.resolved.KotlinRootNpmResolution
import org.jetbrains.kotlin.gradle.tasks.registerTask 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 gradleNodeModules = GradleNodeModulesCache(nodeJs)
val compositeNodeModules = CompositeNodeModulesCache(nodeJs) val compositeNodeModules = CompositeNodeModulesCache(nodeJs)
@@ -47,7 +56,7 @@ internal class KotlinRootNpmResolver internal constructor(
@Synchronized @Synchronized
fun addProject(target: Project) { 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) projectResolvers[target] = KotlinProjectNpmResolver(target, this)
} }
@@ -87,32 +96,75 @@ internal class KotlinRootNpmResolver internal constructor(
/** /**
* Don't use directly, use [KotlinNpmResolutionManager.installIfNeeded] instead. * Don't use directly, use [KotlinNpmResolutionManager.installIfNeeded] instead.
*/ */
internal fun close(): KotlinRootNpmResolution { internal fun prepareInstallation(): Installation {
check(!closed) synchronized(this@KotlinRootNpmResolver) {
closed = true check(state == State.CONFIGURING) {
"Projects must be configuring"
}
state = State.PROJECTS_CLOSED
val projectResolutions = projectResolvers.values val projectResolutions = projectResolvers.values
.map { it.close() } .map { it.close() }
.associateBy { it.project } .associateBy { it.project }
val allNpmPackages = projectResolutions.values.flatMap { it.npmProjects } val allNpmPackages = projectResolutions.values.flatMap { it.npmProjects }
gradleNodeModules.close() gradleNodeModules.close()
nodeJs.packageManager.prepareRootProject( nodeJs.packageManager.prepareRootProject(
rootProject, rootProject,
allNpmPackages allNpmPackages
) )
return KotlinRootNpmResolution( return Installation(
rootProject, projectResolutions
projectResolutions, )
plugins }
)
} }
internal fun closePlugins(resolution: KotlinRootNpmResolution) { open inner class Installation(val projectResolutions: Map<Project, KotlinProjectNpmResolution>) {
plugins.forEach { operator fun get(project: Project) =
it.close(resolution) projectResolutions[project] ?: KotlinProjectNpmResolution.empty(project)
internal fun install(
forceUpToDate: Boolean,
args: List<String>
): 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)
}
} }
} }