[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)
}
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
}
}
@@ -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<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
@@ -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<String> = 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]
}
}
@@ -10,6 +10,5 @@ import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinCompilationNpmR
internal interface RootResolverPlugin {
fun createCompilationResolverPlugins(resolver: KotlinCompilationNpmResolver): List<CompilationResolverPlugin>
fun resolve(resolution: KotlinRootNpmResolution)
fun close(resolution: KotlinRootNpmResolution)
}
@@ -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<Project, KotlinProjectNpmResolution>,
val plugins: List<RootResolverPlugin>
internal val projects: Map<Project, KotlinProjectNpmResolution>
) {
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.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<Project, KotlinProjectNpmResolution>) {
operator fun get(project: Project) =
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)
}
}
}