From 53414aca87b55d3183ca154a1d9f29a3edc635cb Mon Sep 17 00:00:00 2001 From: Andrey Uskov Date: Tue, 13 Nov 2018 17:06:58 +0300 Subject: [PATCH] Cache module dependencies on project import #KT-27832 Fixed --- .../KotlinGradleProjectResolverExtension.kt | 204 ++++++++++-------- 1 file changed, 112 insertions(+), 92 deletions(-) diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleProjectResolverExtension.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleProjectResolverExtension.kt index 7d126915b98..272c74e02f3 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleProjectResolverExtension.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleProjectResolverExtension.kt @@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.configuration import com.intellij.openapi.externalSystem.model.DataNode import com.intellij.openapi.externalSystem.model.ProjectKeys +import com.intellij.openapi.externalSystem.model.project.ExternalSystemSourceType import com.intellij.openapi.externalSystem.model.project.ModuleData import com.intellij.openapi.externalSystem.model.project.ModuleDependencyData import com.intellij.openapi.externalSystem.model.project.ProjectData @@ -35,12 +36,14 @@ import org.jetbrains.kotlin.idea.inspections.gradle.getDependencyModules import org.jetbrains.kotlin.idea.util.CopyableDataNodeUserDataProperty import org.jetbrains.kotlin.idea.util.NotNullableCopyableDataNodeUserDataProperty import org.jetbrains.plugins.gradle.model.ExternalProjectDependency +import org.jetbrains.plugins.gradle.model.ExternalSourceSet import org.jetbrains.plugins.gradle.model.FileCollectionDependency import org.jetbrains.plugins.gradle.model.data.GradleSourceSetData import org.jetbrains.plugins.gradle.service.project.AbstractProjectResolverExtension import org.jetbrains.plugins.gradle.service.project.GradleProjectResolver import java.io.File import java.util.* +import kotlin.collections.HashMap var DataNode.isResolved by NotNullableCopyableDataNodeUserDataProperty(Key.create("IS_RESOLVED"), false) @@ -54,6 +57,13 @@ var DataNode.platformPluginId by CopyableDataNodeUserDataProperty(Key.create("PLATFORM_PLUGIN_ID")) var DataNode.implementedModuleNames by NotNullableCopyableDataNodeUserDataProperty(Key.create>("IMPLEMENTED_MODULE_NAME"), emptyList()) +// Project is usually the same during all import, thus keeping Map Project->Dependencies makes model a bit more complicated but allows to avoid future problems +var DataNode.dependenciesCache + by NotNullableCopyableDataNodeUserDataProperty( + Key.create, Collection>>>("MODULE_DEPENDENCIES_CACHE"), + HashMap() + ) + class KotlinGradleProjectResolverExtension : AbstractProjectResolverExtension() { val isAndroidProjectKey = Key.findKeyByName("IS_ANDROID_PROJECT_KEY") @@ -74,6 +84,106 @@ class KotlinGradleProjectResolverExtension : AbstractProjectResolverExtension() return resolverCtx.isResolveModulePerSourceSet } + private fun getDependencyByFiles( + files: Collection, + outputToSourceSet: Map>?, + sourceSetByName: Map, ExternalSourceSet>>? + ) = files + .mapTo(HashSet()) { + val path = FileUtil.toSystemIndependentName(it.path) + val targetSourceSetId = outputToSourceSet?.get(path)?.first ?: return@mapTo null + sourceSetByName?.get(targetSourceSetId)?.first + } + .singleOrNull() + + private fun DataNode.getDependencies(ideProject: DataNode): Collection> { + if (dependenciesCache.containsKey(ideProject)) { + return dependenciesCache[ideProject]!! + } + val outputToSourceSet = ideProject.getUserData(GradleProjectResolver.MODULES_OUTPUTS) + val sourceSetByName = ideProject.getUserData(GradleProjectResolver.RESOLVED_SOURCE_SETS) ?: return emptySet() + + val externalSourceSet = sourceSetByName[data.id]?.second ?: return emptySet() + val result = externalSourceSet.dependencies.mapNotNullTo(LinkedHashSet()) { dependency -> + when (dependency) { + is ExternalProjectDependency -> { + if (dependency.configurationName == Dependency.DEFAULT_CONFIGURATION) { + val targetModuleNode = ExternalSystemApiUtil.findFirstRecursively(ideProject) { + (it.data as? ModuleData)?.id == dependency.projectPath + } as DataNode? ?: return@mapNotNullTo null + ExternalSystemApiUtil.findAll(targetModuleNode, GradleSourceSetData.KEY) + .firstOrNull { it.sourceSetName == "main" } + } else { + getDependencyByFiles(dependency.projectDependencyArtifacts, outputToSourceSet, sourceSetByName) + } + } + is FileCollectionDependency -> { + getDependencyByFiles(dependency.files, outputToSourceSet, sourceSetByName) + } + else -> null + } + } + dependenciesCache[ideProject] = result + return result + } + + private fun addTransitiveDependenciesOnImplementedModules( + gradleModule: IdeaModule, + ideModule: DataNode, + ideProject: DataNode + ) { + val moduleNodesToProcess = if (useModulePerSourceSet()) { + ExternalSystemApiUtil.findAll(ideModule, GradleSourceSetData.KEY) + } else listOf(ideModule) + + for (currentModuleNode in moduleNodesToProcess) { + val toProcess = LinkedList>() + val processed = HashSet>() + toProcess.add(currentModuleNode) + + while (toProcess.isNotEmpty()) { + val moduleNode = toProcess.pollLast() + if (processed.contains(moduleNode)) continue + + processed.add(moduleNode) + + val moduleNodeForGradleModel = if (useModulePerSourceSet()) { + ExternalSystemApiUtil.findParent(moduleNode, ProjectKeys.MODULE) + } else moduleNode + + val ideaModule = if (moduleNodeForGradleModel != ideModule) { + gradleModule.project.modules.firstOrNull { it.gradleProject.path == moduleNodeForGradleModel?.data?.id } + } else gradleModule + + val implementsModuleIds = resolverCtx.getExtraProject(ideaModule, KotlinGradleModel::class.java)?.implements + ?: emptyList() + + for (implementsModuleId in implementsModuleIds) { + val targetModule = findModuleById(ideProject, gradleModule, implementsModuleId) ?: continue + + if (useModulePerSourceSet()) { + val targetSourceSetsByName = ExternalSystemApiUtil + .findAll(targetModule, GradleSourceSetData.KEY) + .associateBy { it.sourceSetName } + val targetMainSourceSet = targetSourceSetsByName["main"] ?: targetModule + val targetSourceSet = targetSourceSetsByName[currentModuleNode.sourceSetName] + if (targetSourceSet != null) { + addDependency(currentModuleNode, targetSourceSet) + } + if (currentModuleNode.sourceSetName == "test" && targetMainSourceSet != targetSourceSet) { + addDependency(currentModuleNode, targetMainSourceSet) + } + } else { + addDependency(currentModuleNode, targetModule) + } + } + + val dependencies = if (useModulePerSourceSet()) moduleNode.getDependencies(ideProject) else getDependencyModules(ideModule, gradleModule.project) + dependencies.filterTo(toProcess) { it !in processed } + } + } + } + override fun populateModuleDependencies( gradleModule: IdeaModule, ideModule: DataNode, @@ -83,105 +193,15 @@ class KotlinGradleProjectResolverExtension : AbstractProjectResolverExtension() return super.populateModuleDependencies(gradleModule, ideModule, ideProject) } - val outputToSourceSet = ideProject.getUserData(GradleProjectResolver.MODULES_OUTPUTS) - val sourceSetByName = ideProject.getUserData(GradleProjectResolver.RESOLVED_SOURCE_SETS) val gradleModel = resolverCtx.getExtraProject(gradleModule, KotlinGradleModel::class.java) - ?: return super.populateModuleDependencies(gradleModule, ideModule, ideProject) - - val gradleIdeaProject = gradleModule.project - - fun getDependencyByFiles(files: Collection) = files - .mapTo(HashSet()) { - val path = FileUtil.toSystemIndependentName(it.path) - val targetSourceSetId = outputToSourceSet?.get(path)?.first ?: return@mapTo null - sourceSetByName?.get(targetSourceSetId)?.first - } - .singleOrNull() - - fun DataNode.getDependencies(): Collection> { - if (useModulePerSourceSet()) { - if (sourceSetByName == null) return emptySet() - val externalSourceSet = sourceSetByName[data.id]?.second ?: return emptySet() - return externalSourceSet.dependencies.mapNotNullTo(LinkedHashSet()) { dependency -> - when (dependency) { - is ExternalProjectDependency -> { - if (dependency.configurationName == Dependency.DEFAULT_CONFIGURATION) { - val targetModuleNode = ExternalSystemApiUtil.findFirstRecursively(ideProject) { - (it.data as? ModuleData)?.id == dependency.projectPath - } as DataNode? ?: return@mapNotNullTo null - ExternalSystemApiUtil.findAll(targetModuleNode, GradleSourceSetData.KEY) - .firstOrNull { it.sourceSetName == "main" } - } else { - getDependencyByFiles(dependency.projectDependencyArtifacts) - } - } - is FileCollectionDependency -> { - getDependencyByFiles(dependency.files) - } - else -> null - } - } - } - - return getDependencyModules(ideModule, gradleIdeaProject) - } - - fun addTransitiveDependenciesOnImplementedModules() { - val moduleNodesToProcess = if (useModulePerSourceSet()) { - ExternalSystemApiUtil.findAll(ideModule, GradleSourceSetData.KEY) - } else listOf(ideModule) - - for (currentModuleNode in moduleNodesToProcess) { - val toProcess = LinkedList>() - val processed = HashSet>() - toProcess.add(currentModuleNode) - - while (toProcess.isNotEmpty()) { - val moduleNode = toProcess.pollFirst() - if (processed.contains(moduleNode)) continue - - processed.add(moduleNode) - - val moduleNodeForGradleModel = if (useModulePerSourceSet()) { - ExternalSystemApiUtil.findParent(moduleNode, ProjectKeys.MODULE) - } else moduleNode - val ideaModule = if (moduleNodeForGradleModel != ideModule) { - gradleIdeaProject.modules.firstOrNull { it.gradleProject.path == moduleNodeForGradleModel?.data?.id } - } else gradleModule - val implementsModuleIds = resolverCtx.getExtraProject(ideaModule, KotlinGradleModel::class.java)?.implements - ?: emptyList() - - for (implementsModuleId in implementsModuleIds) { - val targetModule = findModuleById(ideProject, gradleModule, implementsModuleId) ?: continue - - if (useModulePerSourceSet()) { - val targetSourceSetsByName = ExternalSystemApiUtil - .findAll(targetModule, GradleSourceSetData.KEY) - .associateBy { it.sourceSetName } - val targetMainSourceSet = targetSourceSetsByName["main"] ?: targetModule - val targetSourceSet = targetSourceSetsByName[currentModuleNode.sourceSetName] - if (targetSourceSet != null) { - addDependency(currentModuleNode, targetSourceSet) - } - if (currentModuleNode.sourceSetName == "test" && targetMainSourceSet != targetSourceSet) { - addDependency(currentModuleNode, targetMainSourceSet) - } - } else { - addDependency(currentModuleNode, targetModule) - } - } - - moduleNode.getDependencies().filterTo(toProcess) { it !in processed } - } - } - } + ?: return super.populateModuleDependencies(gradleModule, ideModule, ideProject) if (!useModulePerSourceSet()) { super.populateModuleDependencies(gradleModule, ideModule, ideProject) } - addTransitiveDependenciesOnImplementedModules() + addTransitiveDependenciesOnImplementedModules(gradleModule, ideModule, ideProject) ideModule.isResolved = true ideModule.hasKotlinPlugin = gradleModel.hasKotlinPlugin