Cache module dependencies on project import
#KT-27832 Fixed
This commit is contained in:
+112
-92
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.configuration
|
|||||||
|
|
||||||
import com.intellij.openapi.externalSystem.model.DataNode
|
import com.intellij.openapi.externalSystem.model.DataNode
|
||||||
import com.intellij.openapi.externalSystem.model.ProjectKeys
|
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.ModuleData
|
||||||
import com.intellij.openapi.externalSystem.model.project.ModuleDependencyData
|
import com.intellij.openapi.externalSystem.model.project.ModuleDependencyData
|
||||||
import com.intellij.openapi.externalSystem.model.project.ProjectData
|
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.CopyableDataNodeUserDataProperty
|
||||||
import org.jetbrains.kotlin.idea.util.NotNullableCopyableDataNodeUserDataProperty
|
import org.jetbrains.kotlin.idea.util.NotNullableCopyableDataNodeUserDataProperty
|
||||||
import org.jetbrains.plugins.gradle.model.ExternalProjectDependency
|
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.FileCollectionDependency
|
||||||
import org.jetbrains.plugins.gradle.model.data.GradleSourceSetData
|
import org.jetbrains.plugins.gradle.model.data.GradleSourceSetData
|
||||||
import org.jetbrains.plugins.gradle.service.project.AbstractProjectResolverExtension
|
import org.jetbrains.plugins.gradle.service.project.AbstractProjectResolverExtension
|
||||||
import org.jetbrains.plugins.gradle.service.project.GradleProjectResolver
|
import org.jetbrains.plugins.gradle.service.project.GradleProjectResolver
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.collections.HashMap
|
||||||
|
|
||||||
var DataNode<ModuleData>.isResolved
|
var DataNode<ModuleData>.isResolved
|
||||||
by NotNullableCopyableDataNodeUserDataProperty(Key.create<Boolean>("IS_RESOLVED"), false)
|
by NotNullableCopyableDataNodeUserDataProperty(Key.create<Boolean>("IS_RESOLVED"), false)
|
||||||
@@ -54,6 +57,13 @@ var DataNode<ModuleData>.platformPluginId
|
|||||||
by CopyableDataNodeUserDataProperty(Key.create<String>("PLATFORM_PLUGIN_ID"))
|
by CopyableDataNodeUserDataProperty(Key.create<String>("PLATFORM_PLUGIN_ID"))
|
||||||
var DataNode<out ModuleData>.implementedModuleNames
|
var DataNode<out ModuleData>.implementedModuleNames
|
||||||
by NotNullableCopyableDataNodeUserDataProperty(Key.create<List<String>>("IMPLEMENTED_MODULE_NAME"), emptyList())
|
by NotNullableCopyableDataNodeUserDataProperty(Key.create<List<String>>("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<out ModuleData>.dependenciesCache
|
||||||
|
by NotNullableCopyableDataNodeUserDataProperty(
|
||||||
|
Key.create<MutableMap<DataNode<ProjectData>, Collection<DataNode<out ModuleData>>>>("MODULE_DEPENDENCIES_CACHE"),
|
||||||
|
HashMap()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class KotlinGradleProjectResolverExtension : AbstractProjectResolverExtension() {
|
class KotlinGradleProjectResolverExtension : AbstractProjectResolverExtension() {
|
||||||
val isAndroidProjectKey = Key.findKeyByName("IS_ANDROID_PROJECT_KEY")
|
val isAndroidProjectKey = Key.findKeyByName("IS_ANDROID_PROJECT_KEY")
|
||||||
@@ -74,6 +84,106 @@ class KotlinGradleProjectResolverExtension : AbstractProjectResolverExtension()
|
|||||||
return resolverCtx.isResolveModulePerSourceSet
|
return resolverCtx.isResolveModulePerSourceSet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getDependencyByFiles(
|
||||||
|
files: Collection<File>,
|
||||||
|
outputToSourceSet: Map<String, com.intellij.openapi.util.Pair<String, ExternalSystemSourceType>>?,
|
||||||
|
sourceSetByName: Map<String, com.intellij.openapi.util.Pair<DataNode<GradleSourceSetData>, 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<out ModuleData>.getDependencies(ideProject: DataNode<ProjectData>): Collection<DataNode<out ModuleData>> {
|
||||||
|
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<ModuleData>? ?: 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<ModuleData>,
|
||||||
|
ideProject: DataNode<ProjectData>
|
||||||
|
) {
|
||||||
|
val moduleNodesToProcess = if (useModulePerSourceSet()) {
|
||||||
|
ExternalSystemApiUtil.findAll(ideModule, GradleSourceSetData.KEY)
|
||||||
|
} else listOf(ideModule)
|
||||||
|
|
||||||
|
for (currentModuleNode in moduleNodesToProcess) {
|
||||||
|
val toProcess = LinkedList<DataNode<out ModuleData>>()
|
||||||
|
val processed = HashSet<DataNode<out ModuleData>>()
|
||||||
|
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(
|
override fun populateModuleDependencies(
|
||||||
gradleModule: IdeaModule,
|
gradleModule: IdeaModule,
|
||||||
ideModule: DataNode<ModuleData>,
|
ideModule: DataNode<ModuleData>,
|
||||||
@@ -83,105 +193,15 @@ class KotlinGradleProjectResolverExtension : AbstractProjectResolverExtension()
|
|||||||
return super.populateModuleDependencies(gradleModule, ideModule, ideProject)
|
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)
|
val gradleModel = resolverCtx.getExtraProject(gradleModule, KotlinGradleModel::class.java)
|
||||||
?: return super.populateModuleDependencies(gradleModule, ideModule, ideProject)
|
?: return super.populateModuleDependencies(gradleModule, ideModule, ideProject)
|
||||||
|
|
||||||
val gradleIdeaProject = gradleModule.project
|
|
||||||
|
|
||||||
fun getDependencyByFiles(files: Collection<File>) = 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<out ModuleData>.getDependencies(): Collection<DataNode<out ModuleData>> {
|
|
||||||
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<ModuleData>? ?: 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<DataNode<out ModuleData>>()
|
|
||||||
val processed = HashSet<DataNode<out ModuleData>>()
|
|
||||||
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 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!useModulePerSourceSet()) {
|
if (!useModulePerSourceSet()) {
|
||||||
super.populateModuleDependencies(gradleModule, ideModule, ideProject)
|
super.populateModuleDependencies(gradleModule, ideModule, ideProject)
|
||||||
}
|
}
|
||||||
|
|
||||||
addTransitiveDependenciesOnImplementedModules()
|
addTransitiveDependenciesOnImplementedModules(gradleModule, ideModule, ideProject)
|
||||||
|
|
||||||
ideModule.isResolved = true
|
ideModule.isResolved = true
|
||||||
ideModule.hasKotlinPlugin = gradleModel.hasKotlinPlugin
|
ideModule.hasKotlinPlugin = gradleModel.hasKotlinPlugin
|
||||||
|
|||||||
Reference in New Issue
Block a user