From e8a54d6c75ac6fbb12404db966eef361ae8bd4c4 Mon Sep 17 00:00:00 2001 From: Andrey Uskov Date: Thu, 27 Feb 2020 06:53:46 +0300 Subject: [PATCH] Fix import of MPP projects in IDEA 2020.1 --- .../KotlinMPPGradleProjectResolver.kt | 138 +++++++++++------- .../kotlin/gradle/projectStructureDSL.kt | 10 +- .../src/KotlinMPPGradleModelBuilder.kt | 23 ++- .../src/KotlinMPPGradleModelBuilderBunch.kt | 10 ++ .../KotlinMPPGradleModelBuilderBunch.kt.201 | 15 ++ 5 files changed, 141 insertions(+), 55 deletions(-) create mode 100644 idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilderBunch.kt create mode 100644 idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilderBunch.kt.201 diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPGradleProjectResolver.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPGradleProjectResolver.kt index be6def2f0f6..49697f54480 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPGradleProjectResolver.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPGradleProjectResolver.kt @@ -142,59 +142,15 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp } } - private fun ExternalDependency.getDependencyArtifacts(): Collection = - when (this) { - is ExternalProjectDependency -> this.projectDependencyArtifacts - is FileCollectionDependency -> this.files - else -> emptyList() - } - - private fun ExternalDependency.addDependencyArtifactInternal(file: File) { - when (this) { - is ExternalProjectDependency -> this.projectDependencyArtifacts.add(file) - is FileCollectionDependency -> this.files.add(file) - } - } - override fun populateModuleDependencies(gradleModule: IdeaModule, ideModule: DataNode, ideProject: DataNode) { - if (resolverCtx.getExtraProject(gradleModule, KotlinMPPGradleModel::class.java) == null) { - // Add mpp-artifacts into map used for dependency substitution - val mppArtifacts = ideProject.getUserData(MPP_CONFIGURATION_ARTIFACTS) - val configArtifacts = ideProject.getUserData(CONFIGURATION_ARTIFACTS) - if (mppArtifacts != null && configArtifacts != null) { - // processing case when one artifact could be produced by several (actualized!)source sets - if (mppArtifacts.isNotEmpty() && resolverCtx.isResolveModulePerSourceSet) { - val externalProject = resolverCtx.getExtraProject(gradleModule, ExternalProject::class.java) - - //Note! Should not use MultiValuesMap as it contains Set of values, but we need comparision === instead of == - val artifactToDependency = HashMap>() - externalProject?.sourceSets?.values?.forEach { sourceSet -> - sourceSet.dependencies.forEach { dependency -> - dependency.getDependencyArtifacts().map { toCanonicalPath(it.absolutePath) } - .filter { mppArtifacts.keys.contains(it) }.forEach { filePath -> - (artifactToDependency[filePath] ?: ArrayList().also { newCollection -> - artifactToDependency[filePath] = newCollection - }).add(dependency) - } - - } - } - // create 'fake' dependency artifact files and put them into dependency substitution map - mppArtifacts.forEach { (filePath, moduleIds) -> - moduleIds.firstOrNull()?.also { configArtifacts[filePath] = it } - artifactToDependency[filePath]?.forEach { externalDependency -> - for ((index, module) in moduleIds.withIndex()) { - if (index != 0) { - val fakeArtifact = "$filePath-MPP-$index" - configArtifacts[fakeArtifact] = module - externalDependency.addDependencyArtifactInternal(File(fakeArtifact)) - } - } - } - } - } + val mppModel = resolverCtx.getExtraProject(gradleModule, KotlinMPPGradleModel::class.java) + if (mppModel == null) { + resolverCtx.getExtraProject(gradleModule, ExternalProject::class.java)?.sourceSets?.values?.forEach { sourceSet -> + sourceSet.dependencies.modifyDependenciesOnMppModules(ideProject, resolverCtx) } super.populateModuleDependencies(gradleModule, ideModule, ideProject)//TODO add dependencies on mpp module + } else { + mppModel.dependencyMap.values.modifyDependenciesOnMppModules(ideProject, resolverCtx) } populateModuleDependencies(gradleModule, ideProject, ideModule, resolverCtx) } @@ -236,6 +192,72 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp private var nativeDebugAdvertised = false private val androidPluginPresent = PluginManager.getPlugin(PluginId.findId("org.jetbrains.android"))?.isEnabled ?: false + private fun ExternalDependency.getDependencyArtifacts(): Collection = + when (this) { + is ExternalProjectDependency -> this.projectDependencyArtifacts + is FileCollectionDependency -> this.files + else -> emptyList() + } + + private fun Collection.modifyDependenciesOnMppModules( + ideProject: DataNode, + resolverCtx: ProjectResolverContext + ) { + // Add mpp-artifacts into map used for dependency substitution + val mppArtifacts = ideProject.getUserData(MPP_CONFIGURATION_ARTIFACTS) + val configArtifacts = ideProject.getUserData(CONFIGURATION_ARTIFACTS) + if (mppArtifacts != null && configArtifacts != null) { + val reverseConfigArtifacts = HashMap(configArtifacts.map { it.value to it.key }.toMap()) + // processing case when one artifact could be produced by several (actualized!)source sets + if (mppArtifacts.isNotEmpty() && resolverCtx.isResolveModulePerSourceSet) { + //Note! Should not use MultiValuesMap as it contains Set of values, but we need comparision === instead of == + val artifactToDependency = HashMap>() + this.forEach { dependency -> + dependency.getDependencyArtifacts().map { toCanonicalPath(it.absolutePath) } + .filter { mppArtifacts.keys.contains(it) }.forEach { filePath -> + (artifactToDependency[filePath] ?: ArrayList().also { newCollection -> + artifactToDependency[filePath] = newCollection + }).add(dependency) + } + } + // create 'fake' dependency artifact files and put them into dependency substitution map + mppArtifacts.forEach { (filePath, moduleIds) -> + moduleIds.firstOrNull()?.also { configArtifacts[filePath] = it } + artifactToDependency[filePath]?.forEach { externalDependency -> + for ((index, module) in moduleIds.withIndex()) { + if (index != 0) { + val fakeArtifact = if (reverseConfigArtifacts.containsKey(module)) { + reverseConfigArtifacts[module] + } else { + val result = "$filePath-MPP-$index" + configArtifacts[result] = module + reverseConfigArtifacts[module] = result + result + } + externalDependency.addDependencyArtifactInternal(File(fakeArtifact)) + } + } + } + } + } + } + } + + private fun ExternalDependency.addDependencyArtifactInternal(file: File) { + when (this) { + is DefaultExternalProjectDependency -> this.projectDependencyArtifacts = + ArrayList(this.projectDependencyArtifacts).also { + it.add(file) + } + is ExternalProjectDependency -> try { + this.projectDependencyArtifacts.add(file) + } catch (_: Exception) { + // ignore + } + is FileCollectionDependency -> this.files.add(file) + } + } + fun initializeModuleData( gradleModule: IdeaModule, mainModuleNode: DataNode, @@ -358,6 +380,10 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp targetData.moduleIds = compilationIds } + sourceSetMap.values.forEach { + it.getSecond().dependencies?.modifyDependenciesOnMppModules(projectDataNode, resolverCtx) + } + val ignoreCommonSourceSets by lazy { externalProject.notImportedCommonSourceSets() } for (sourceSet in mppModel.sourceSets.values) { if (delegateToAndroidPlugin(sourceSet)) continue @@ -532,7 +558,8 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp val processedModuleIds = HashSet() processCompilations(gradleModule, mppModel, ideModule, resolverCtx) { dataNode, compilation -> if (processedModuleIds.add(getKotlinModuleId(gradleModule, compilation, resolverCtx))) { - val substitutedDependencies = substitutor.substituteDependencies(compilation) + val substitutedDependencies = + substitutor.substituteDependencies(compilation.dependencies.mapNotNull { mppModel.dependencyMap[it] }) buildDependencies( resolverCtx, sourceSetMap, @@ -722,8 +749,17 @@ open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtensionComp } private fun addDependency(fromModule: DataNode<*>, toModule: DataNode<*>, dependOnTestModule: Boolean) { + if (fromModule.data == toModule.data) return val fromData = fromModule.data as? ModuleData ?: return val toData = toModule.data as? ModuleData ?: return + val existing = fromModule.children.mapNotNull { it.data as? ModuleDependencyData }.filter { it.target.id == (toModule.data as? ModuleData)?.id } + val nodeToModify = + existing.singleOrNull() ?: existing.firstOrNull { it.scope == DependencyScope.COMPILE } ?: existing.firstOrNull() + if (nodeToModify != null) { + nodeToModify.scope = DependencyScope.COMPILE + nodeToModify.isProductionOnTestDependency = nodeToModify.isProductionOnTestDependency || dependOnTestModule + return + } val moduleDependencyData = ModuleDependencyData(fromData, toData).also { it.scope = DependencyScope.COMPILE it.isExported = false diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/gradle/projectStructureDSL.kt b/idea/idea-gradle/tests/org/jetbrains/kotlin/gradle/projectStructureDSL.kt index 1207592e470..23ebd64e47d 100644 --- a/idea/idea-gradle/tests/org/jetbrains/kotlin/gradle/projectStructureDSL.kt +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/gradle/projectStructureDSL.kt @@ -201,7 +201,15 @@ class ModuleInfo( } fun moduleDependency(moduleName: String, scope: DependencyScope, productionOnTest: Boolean? = null) { - val moduleEntry = rootModel.orderEntries.filterIsInstance().singleOrNull { it.moduleName == moduleName } + val moduleEntries = rootModel.orderEntries.filterIsInstance().filter { it.moduleName == moduleName } + if (moduleEntries.size > 1) { + projectInfo.messageCollector.report( + "Found multiple order entries for module $moduleName: ${rootModel.orderEntries.filterIsInstance()}" + ) + return + } + val moduleEntry = moduleEntries.singleOrNull() + if (moduleEntry == null) { val allModules = rootModel.orderEntries.filterIsInstance().map { it.moduleName }.joinToString(", ") projectInfo.messageCollector.report("Module '${module.name}': No module dependency found: '$moduleName'. All module names: $allModules") diff --git a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt index f7b25e30dde..b33c15740fa 100644 --- a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt +++ b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt @@ -565,6 +565,20 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { ) } + + /** + * Returns only those dependencies with RUNTIME scope which are not present with compile scope + */ + private fun Collection.onlyNewDependencies(compileDependencies: Collection): List { + val compileDependencyArtefacts = compileDependencies.flatMap { (it as? ExternalProjectDependency)?.projectDependencyArtifacts ?: emptyList() } + return this.filter { + if (it is ExternalProjectDependency) + !(compileDependencyArtefacts.containsAll(it.projectDependencyArtifacts)) + else + true + } + } + private fun buildCompilationDependencies( gradleCompilation: Named, classifier: String?, @@ -580,11 +594,14 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { ) this += buildDependencies( gradleCompilation, dependencyResolver, "getRuntimeDependencyConfigurationName", "RUNTIME", project, transformationBuilder - ) + ).onlyNewDependencies(this) + this += sourceSetMap[compilationFullName( gradleCompilation.name, classifier )]?.dependencies?.map { dependencyMapper.getDependency(it) }?.filterNotNull() ?: emptySet() + + makeConfigurationNamesDefault(dependencyMapper) } } @@ -658,7 +675,8 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { ) this += buildDependencies( gradleSourceSet, dependencyResolver, "getRuntimeOnlyMetadataConfigurationName", "RUNTIME", project, transformationBuilder - ) + ).onlyNewDependencies(this) + this += buildAndroidSourceSetDependencies(androidDeps, gradleSourceSet) } } @@ -793,7 +811,6 @@ class KotlinMPPGradleModelBuilder : ModelBuilderService { private val projectDependencyTransformation = transformations.filter { it.projectPath != null }.associateBy { it.projectPath } - //TODO val dependenciesByProjectPath by lazy { configuration diff --git a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilderBunch.kt b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilderBunch.kt new file mode 100644 index 00000000000..74e6af2b2f8 --- /dev/null +++ b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilderBunch.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle + +//BUNCH: 193 +fun makeConfigurationNamesDefault(dependencyMapper: KotlinDependencyMapper) { +} diff --git a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilderBunch.kt.201 b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilderBunch.kt.201 new file mode 100644 index 00000000000..0f3f952252b --- /dev/null +++ b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilderBunch.kt.201 @@ -0,0 +1,15 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle + +import org.jetbrains.plugins.gradle.model.DefaultExternalProjectDependency + +//BUNCH: 193 +fun makeConfigurationNamesDefault(dependencyMapper: KotlinDependencyMapper) { + dependencyMapper.toDependencyMap().values.forEach { + (it as? DefaultExternalProjectDependency)?.configurationName = "default" + } +}