From d9aa1796cdbc5f799dc464b3915d7d29f8fbb996 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Tue, 20 Feb 2018 19:26:24 +0300 Subject: [PATCH] MPP: Support multiple 'expectedBy' dependencies for single module #KT-22865 Fixed --- .../kotlin/analyzer/AnalyzerFacade.kt | 6 +- .../ExpectedActualDeclarationChecker.kt | 13 +++- .../kotlin/descriptors/ModuleDescriptor.kt | 2 +- .../descriptors/impl/ModuleDescriptorImpl.kt | 10 +-- .../jetbrains/kotlin/types/ErrorUtils.java | 6 +- .../idea/caches/project/IdeaModuleInfos.kt | 18 +++-- .../KotlinGradleProjectResolverExtension.kt | 32 ++++----- .../KotlinGradleSourceSetDataService.kt | 2 +- .../MultiplatformGradleProjectTaskRunner.kt | 2 +- .../MultiplatformProjectImportingTest.kt | 29 +++++--- .../ExternalSystemImportingTestCase.java | 15 +++- .../gradle/GradleFacetImportTest.kt | 16 ++--- .../kotlin/config/KotlinFacetSettings.kt | 2 +- .../kotlin/config/facetSerialization.kt | 20 +++++- .../kotlin/idea/maven/KotlinMavenImporter.kt | 6 +- .../idea/maven/KotlinMavenImporterTest.kt | 72 ++++++++++++++++--- .../src/KotlinGradleModelBuilder.kt | 15 ++-- .../jetbrains/kotlin/idea/facet/facetUtils.kt | 14 ++-- .../highlighter/markers/HasExpectedMarker.kt | 11 +-- .../markers/KotlinLineMarkerProvider.kt | 5 +- .../configuration/ConfigureKotlinTest.java | 7 +- .../idea/stubs/AbstractMultiModuleTest.kt | 2 +- 22 files changed, 197 insertions(+), 108 deletions(-) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/analyzer/AnalyzerFacade.kt b/compiler/frontend/src/org/jetbrains/kotlin/analyzer/AnalyzerFacade.kt index d8ffca04a34..99802bc8e97 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/analyzer/AnalyzerFacade.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/analyzer/AnalyzerFacade.kt @@ -239,7 +239,7 @@ interface ModuleInfo { val name: Name val displayedName: String get() = name.asString() fun dependencies(): List - val expectedBy: ModuleInfo? get() = null + val expectedBy: List get() = emptyList() val platform: TargetPlatform? get() = null fun modulesWhoseInternalsAreVisible(): Collection = listOf() val capabilities: Map, Any?> @@ -310,8 +310,8 @@ class LazyModuleDependencies( override val allDependencies: List get() = dependencies() - override val expectedByDependency by storageManager.createNullableLazyValue { - module.expectedBy?.let { resolverForProject.descriptorForModule(it as M) } + override val expectedByDependencies by storageManager.createLazyValue { + module.expectedBy.map { resolverForProject.descriptorForModule(it as M) } } override val modulesWhoseInternalsAreVisible: Set diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExpectedActualDeclarationChecker.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExpectedActualDeclarationChecker.kt index 9e8ab2ca4a0..edb96772dd7 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExpectedActualDeclarationChecker.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/checkers/ExpectedActualDeclarationChecker.kt @@ -37,6 +37,7 @@ import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver.Compati import org.jetbrains.kotlin.resolve.multiplatform.ExpectedActualResolver.Compatibility.Incompatible import org.jetbrains.kotlin.resolve.source.PsiSourceFile import org.jetbrains.kotlin.utils.addToStdlib.safeAs +import org.jetbrains.kotlin.utils.ifEmpty import java.io.File object ExpectedActualDeclarationChecker : DeclarationChecker { @@ -114,8 +115,16 @@ object ExpectedActualDeclarationChecker : DeclarationChecker { // However, in compiler context platform & common modules are joined into one module, // so there is yet no "common module" in this situation. // So yet we are using own module in compiler context and common module in IDE context. - val commonOrOwnModule = descriptor.module.expectedByModule ?: descriptor.module - val compatibility = ExpectedActualResolver.findExpectedForActual(descriptor, commonOrOwnModule) ?: return + val commonOrOwnModules = descriptor.module.expectedByModules.ifEmpty { listOf(descriptor.module) } + val compatibility = commonOrOwnModules + .asSequence() + .fold(LinkedHashMap>()) { resultMap, commonModule -> + val currentMap = ExpectedActualResolver.findExpectedForActual(descriptor, commonModule) + if (currentMap != null) { + resultMap.putAll(currentMap) + } + resultMap + } val hasActualModifier = descriptor.isActual && reportOn.hasActualModifier() if (!hasActualModifier) { diff --git a/core/descriptors/src/org/jetbrains/kotlin/descriptors/ModuleDescriptor.kt b/core/descriptors/src/org/jetbrains/kotlin/descriptors/ModuleDescriptor.kt index 7035dfabb8f..584075ca5aa 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/descriptors/ModuleDescriptor.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/descriptors/ModuleDescriptor.kt @@ -40,7 +40,7 @@ interface ModuleDescriptor : DeclarationDescriptor { */ val allDependencyModules: List - val expectedByModule: ModuleDescriptor? + val expectedByModules: List fun getCapability(capability: Capability): T? diff --git a/core/descriptors/src/org/jetbrains/kotlin/descriptors/impl/ModuleDescriptorImpl.kt b/core/descriptors/src/org/jetbrains/kotlin/descriptors/impl/ModuleDescriptorImpl.kt index b5feed0b8d7..58f5e3428ac 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/descriptors/impl/ModuleDescriptorImpl.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/descriptors/impl/ModuleDescriptorImpl.kt @@ -66,8 +66,8 @@ class ModuleDescriptorImpl @JvmOverloads constructor( override val allDependencyModules: List get() = this.dependencies.sure { "Dependencies of module $id were not set" }.allDependencies.filter { it != this } - override val expectedByModule: ModuleDescriptor? - get() = this.dependencies.sure { "Dependencies of module $id were not set" }.expectedByDependency + override val expectedByModules: List + get() = this.dependencies.sure { "Dependencies of module $id were not set" }.expectedByDependencies override fun getPackage(fqName: FqName): PackageViewDescriptor { assertValid() @@ -111,7 +111,7 @@ class ModuleDescriptorImpl @JvmOverloads constructor( } fun setDependencies(descriptors: List, friends: Set) { - setDependencies(ModuleDependenciesImpl(descriptors, friends, null)) + setDependencies(ModuleDependenciesImpl(descriptors, friends, emptyList())) } override fun shouldSeeInternalsOf(targetModule: ModuleDescriptor): Boolean { @@ -150,11 +150,11 @@ class ModuleDescriptorImpl @JvmOverloads constructor( interface ModuleDependencies { val allDependencies: List val modulesWhoseInternalsAreVisible: Set - val expectedByDependency: ModuleDescriptorImpl? + val expectedByDependencies: List } class ModuleDependenciesImpl( override val allDependencies: List, override val modulesWhoseInternalsAreVisible: Set, - override val expectedByDependency: ModuleDescriptorImpl? + override val expectedByDependencies: List ) : ModuleDependencies diff --git a/core/descriptors/src/org/jetbrains/kotlin/types/ErrorUtils.java b/core/descriptors/src/org/jetbrains/kotlin/types/ErrorUtils.java index 40143f4b9c7..469d0792cf4 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/types/ErrorUtils.java +++ b/core/descriptors/src/org/jetbrains/kotlin/types/ErrorUtils.java @@ -85,10 +85,10 @@ public class ErrorUtils { return emptyList(); } - @Nullable + @NotNull @Override - public ModuleDescriptor getExpectedByModule() { - return null; + public List getExpectedByModules() { + return emptyList(); } @Override diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/IdeaModuleInfos.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/IdeaModuleInfos.kt index 1ea6d0609c6..3781e47968b 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/IdeaModuleInfos.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/caches/project/IdeaModuleInfos.kt @@ -117,24 +117,22 @@ private fun ideaModelDependencies(module: Module, forProduction: Boolean): List< return result.toList() } -fun Module.findImplementedModuleName(modelsProvider: IdeModifiableModelsProvider): String? { +fun Module.findImplementedModuleNames(modelsProvider: IdeModifiableModelsProvider): List { val facetModel = modelsProvider.getModifiableFacetModel(this) val facet = facetModel.findFacet( KotlinFacetType.TYPE_ID, FacetTypeRegistry.getInstance().findFacetType(ID)!!.defaultFacetName ) - return facet?.configuration?.settings?.implementedModuleName + return facet?.configuration?.settings?.implementedModuleNames ?: emptyList() } -fun Module.findImplementedModule(modelsProvider: IdeModifiableModelsProvider): Module? { - val implementedModuleName = findImplementedModuleName(modelsProvider) - return implementedModuleName?.let { modelsProvider.findIdeModule(it) } -} +fun Module.findImplementedModules(modelsProvider: IdeModifiableModelsProvider) = + findImplementedModuleNames(modelsProvider).mapNotNull { modelsProvider.findIdeModule(it) } interface ModuleSourceInfo : IdeaModuleInfo, TrackableModuleInfo { val module: Module - override val expectedBy: ModuleSourceInfo? + override val expectedBy: List override val displayedName get() = module.name @@ -149,11 +147,11 @@ interface ModuleSourceInfo : IdeaModuleInfo, TrackableModuleInfo { } sealed class ModuleSourceInfoWithExpectedBy(private val forProduction: Boolean) : ModuleSourceInfo { - override val expectedBy: ModuleSourceInfo? + override val expectedBy: List get() { val modelsProvider = IdeModifiableModelsProviderImpl(module.project) - val expectedByModule = module.findImplementedModule(modelsProvider) - return if (forProduction) expectedByModule?.productionSourceInfo() else expectedByModule?.testSourceInfo() + val expectedByModules = module.findImplementedModules(modelsProvider) + return expectedByModules.mapNotNull { if (forProduction) it.productionSourceInfo() else it.testSourceInfo() } } override fun dependencies(): List = module.cached(createCachedValueProvider { 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 fc080736e35..3aedd9753a2 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 @@ -49,8 +49,8 @@ var DataNode.coroutines by CopyableDataNodeUserDataProperty(Key.create("KOTLIN_COROUTINES")) var DataNode.platformPluginId by CopyableDataNodeUserDataProperty(Key.create("PLATFORM_PLUGIN_ID")) -var DataNode.implementedModuleName - by CopyableDataNodeUserDataProperty(Key.create("IMPLEMENTED_MODULE_NAME")) +var DataNode.implementedModuleNames + by NotNullableCopyableDataNodeUserDataProperty(Key.create>("IMPLEMENTED_MODULE_NAME"), emptyList()) class KotlinGradleProjectResolverExtension : AbstractProjectResolverExtension() { val isAndroidProjectKey = Key.findKeyByName("IS_ANDROID_PROJECT_KEY") @@ -134,23 +134,23 @@ class KotlinGradleProjectResolverExtension : AbstractProjectResolverExtension() gradleIdeaProject.modules.firstOrNull { it.gradleProject.path == moduleNodeForGradleModel?.data?.id } } else gradleModule - val implementsModuleId = resolverCtx.getExtraProject(ideaModule, KotlinGradleModel::class.java)?.implements + val implementsModuleIds = resolverCtx.getExtraProject(ideaModule, KotlinGradleModel::class.java)?.implements + ?: emptyList() - val targetModule = implementsModuleId?.let { + for (implementsModuleId in implementsModuleIds) { val compositePrefix = if (resolverCtx.models.ideaProject != gradleModule.project - && it.startsWith(":")) { + && implementsModuleId.startsWith(":")) { gradleModule.project.name } else { "" } - findModuleById(ideProject, compositePrefix + it) - } - if (targetModule != null) { + val targetModule = findModuleById(ideProject, compositePrefix + implementsModuleId) ?: continue + if (useModulePerSourceSet()) { val targetSourceSetsByName = ExternalSystemApiUtil - .findAll(targetModule, GradleSourceSetData.KEY) - .associateBy { it.sourceSetName } + .findAll(targetModule, GradleSourceSetData.KEY) + .associateBy { it.sourceSetName } val targetMainSourceSet = targetSourceSetsByName["main"] ?: targetModule val targetSourceSet = targetSourceSetsByName[currentModuleNode.sourceSetName] if (targetSourceSet != null) { @@ -193,17 +193,15 @@ class KotlinGradleProjectResolverExtension : AbstractProjectResolverExtension() ideProject: DataNode, gradleModel: KotlinGradleModel ) { - val implementedModule = gradleModel.implements?.let { findModuleById(ideProject, it) } ?: return + val implementedModules = gradleModel.implements.mapNotNull { findModuleById(ideProject, it) } if (resolverCtx.isResolveModulePerSourceSet) { val dependentSourceSets = dependentModule.getSourceSetsMap() - val implementedSourceSets = implementedModule.getSourceSetsMap() + val implementedSourceSetMaps = implementedModules.map { it.getSourceSetsMap() } for ((sourceSetName, dependentSourceSet) in dependentSourceSets) { - val implementedSourceSet = implementedSourceSets[sourceSetName] ?: continue - dependentSourceSet.implementedModuleName = implementedSourceSet.data.internalName + dependentSourceSet.implementedModuleNames = implementedSourceSetMaps.mapNotNull { it[sourceSetName]?.data?.internalName } } - } - else { - dependentModule.implementedModuleName = implementedModule.data.internalName + } else { + dependentModule.implementedModuleNames = implementedModules.map { it.data.internalName } } } diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleSourceSetDataService.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleSourceSetDataService.kt index 81df602d271..a48d69e1e40 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleSourceSetDataService.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleSourceSetDataService.kt @@ -214,7 +214,7 @@ private fun configureFacetByGradleModule( } with(kotlinFacet.configuration.settings) { - implementedModuleName = (sourceSetNode ?: moduleNode).implementedModuleName + implementedModuleNames = (sourceSetNode ?: moduleNode).implementedModuleNames productionOutputPath = getExplicitOutputPath(moduleNode, platformKind, "main") testOutputPath = getExplicitOutputPath(moduleNode, platformKind, "test") } diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/run/MultiplatformGradleProjectTaskRunner.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/run/MultiplatformGradleProjectTaskRunner.kt index 4daed00bd92..2ae43eb4f6d 100644 --- a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/run/MultiplatformGradleProjectTaskRunner.kt +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/run/MultiplatformGradleProjectTaskRunner.kt @@ -175,5 +175,5 @@ class MultiplatformGradleOrderEnumeratorHandler(val factory: MultiplatformGradle private fun Module.isMultiplatformModule(): Boolean { val settings = KotlinFacetSettingsProvider.getInstance(project).getInitializedSettings(this) - return settings.targetPlatformKind is TargetPlatformKind.Common || settings.implementedModuleName != null + return settings.targetPlatformKind is TargetPlatformKind.Common || settings.implementedModuleNames.isNotEmpty() } diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/gradle/MultiplatformProjectImportingTest.kt b/idea/idea-gradle/tests/org/jetbrains/kotlin/gradle/MultiplatformProjectImportingTest.kt index fc598a2f44a..5c20906f52f 100644 --- a/idea/idea-gradle/tests/org/jetbrains/kotlin/gradle/MultiplatformProjectImportingTest.kt +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/gradle/MultiplatformProjectImportingTest.kt @@ -17,14 +17,12 @@ package org.jetbrains.kotlin.gradle import com.intellij.openapi.roots.DependencyScope -import com.intellij.openapi.util.text.StringUtil import com.intellij.util.PathUtil import junit.framework.TestCase import org.jetbrains.kotlin.idea.codeInsight.gradle.GradleImportingTestCase import org.jetbrains.kotlin.idea.facet.KotlinFacet import org.jetbrains.kotlin.test.KotlinTestUtils import org.junit.Test -import java.io.File class MultiplatformProjectImportingTest : GradleImportingTestCase() { @Test @@ -74,9 +72,9 @@ class MultiplatformProjectImportingTest : GradleImportingTestCase() { @Test fun testPlatformToCommonExpectedByDependency() { - createProjectSubFile("settings.gradle", "include ':common', ':jvm', ':js'") + createProjectSubFile("settings.gradle", "include ':common1', ':common2', ':jvm', ':js'") - val kotlinVersion = "1.2.0-beta-74" + val kotlinVersion = "1.2.40-dev-610" createProjectSubFile("build.gradle", """ buildscript { @@ -90,7 +88,11 @@ class MultiplatformProjectImportingTest : GradleImportingTestCase() { } } - project('common') { + project('common1') { + apply plugin: 'kotlin-platform-common' + } + + project('common2') { apply plugin: 'kotlin-platform-common' } @@ -98,7 +100,8 @@ class MultiplatformProjectImportingTest : GradleImportingTestCase() { apply plugin: 'kotlin-platform-jvm' dependencies { - expectedBy project(':common') + expectedBy project(':common1') + expectedBy project(':common2') } } @@ -106,16 +109,20 @@ class MultiplatformProjectImportingTest : GradleImportingTestCase() { apply plugin: 'kotlin-platform-js' dependencies { - expectedBy project(':common') + expectedBy project(':common1') } } """) importProject() - assertModuleModuleDepScope("jvm_main", "common_main", DependencyScope.COMPILE) - assertModuleModuleDepScope("jvm_test", "common_test", DependencyScope.COMPILE) - assertModuleModuleDepScope("js_main", "common_main", DependencyScope.COMPILE) - assertModuleModuleDepScope("js_test", "common_test", DependencyScope.COMPILE) + assertModuleModuleDepScope("jvm_main", "common1_main", DependencyScope.COMPILE) + assertModuleModuleDepScope("jvm_main", "common2_main", DependencyScope.COMPILE) + assertModuleModuleDepScope("jvm_test", "common1_test", DependencyScope.COMPILE) + assertModuleModuleDepScope("jvm_test", "common2_test", DependencyScope.COMPILE) + assertModuleModuleDepScope("js_main", "common1_main", DependencyScope.COMPILE) + assertModuleModuleDepScope("js_test", "common1_test", DependencyScope.COMPILE) + assertNoDepForModule("js_main", "common2_main") + assertNoDepForModule("js_test", "common2_test") } @Test diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/ExternalSystemImportingTestCase.java b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/ExternalSystemImportingTestCase.java index 6970f4b5aa3..5ca667a8f6f 100644 --- a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/ExternalSystemImportingTestCase.java +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/ExternalSystemImportingTestCase.java @@ -128,6 +128,10 @@ public abstract class ExternalSystemImportingTestCase extends ExternalSystemTest expectedScopes, actualScopes); } + protected void assertNoDepForModule(String moduleName, String depName) { + assertEmpty("No dependency '" + depName + "' was expected", collectModuleDeps(moduleName, depName, ModuleOrderEntry.class)); + } + @NotNull private List getModuleModuleDeps(@NotNull String moduleName, @NotNull String depName) { return getModuleDep(moduleName, depName, ModuleOrderEntry.class); @@ -139,6 +143,14 @@ public abstract class ExternalSystemImportingTestCase extends ExternalSystemTest @NotNull private List getModuleDep(@NotNull String moduleName, @NotNull String depName, @NotNull Class clazz) { + List deps = collectModuleDeps(moduleName, depName, clazz); + assertTrue("Dependency '" + depName + "' for module '" + moduleName + "' not found among: " + collectModuleDepsNames(moduleName, clazz), + !deps.isEmpty()); + return deps; + } + + @NotNull + private List collectModuleDeps(@NotNull String moduleName, @NotNull String depName, @NotNull Class clazz) { List deps = ContainerUtil.newArrayList(); for (OrderEntry e : getRootManager(moduleName).getOrderEntries()) { @@ -146,8 +158,7 @@ public abstract class ExternalSystemImportingTestCase extends ExternalSystemTest deps.add((T)e); } } - assertTrue("Dependency '" + depName + "' for module '" + moduleName + "' not found among: " + collectModuleDepsNames(moduleName, clazz), - !deps.isEmpty()); + return deps; } diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleFacetImportTest.kt b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleFacetImportTest.kt index 6eb61a5d944..046a156435d 100644 --- a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleFacetImportTest.kt +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleFacetImportTest.kt @@ -1481,10 +1481,10 @@ compileTestKotlin { importProject() - Assert.assertEquals("MultiTest_main", facetSettings("MultiTest-jvm_main").implementedModuleName) - Assert.assertEquals("MultiTest_test", facetSettings("MultiTest-jvm_test").implementedModuleName) - Assert.assertEquals("MultiTest_main", facetSettings("MultiTest-js_main").implementedModuleName) - Assert.assertEquals("MultiTest_test", facetSettings("MultiTest-js_test").implementedModuleName) + Assert.assertEquals(listOf("MultiTest_main"), facetSettings("MultiTest-jvm_main").implementedModuleNames) + Assert.assertEquals(listOf("MultiTest_test"), facetSettings("MultiTest-jvm_test").implementedModuleNames) + Assert.assertEquals(listOf("MultiTest_main"), facetSettings("MultiTest-js_main").implementedModuleNames) + Assert.assertEquals(listOf("MultiTest_test"), facetSettings("MultiTest-js_test").implementedModuleNames) } @Test @@ -1630,10 +1630,10 @@ compileTestKotlin { importProject() - Assert.assertEquals("MultiTest_myMain", facetSettings("MultiTest-jvm_myMain").implementedModuleName) - Assert.assertEquals("MultiTest_myTest", facetSettings("MultiTest-jvm_myTest").implementedModuleName) - Assert.assertEquals("MultiTest_myMain", facetSettings("MultiTest-js_myMain").implementedModuleName) - Assert.assertEquals("MultiTest_myTest", facetSettings("MultiTest-js_myTest").implementedModuleName) + Assert.assertEquals(listOf("MultiTest_myMain"), facetSettings("MultiTest-jvm_myMain").implementedModuleNames) + Assert.assertEquals(listOf("MultiTest_myTest"), facetSettings("MultiTest-jvm_myTest").implementedModuleNames) + Assert.assertEquals(listOf("MultiTest_myMain"), facetSettings("MultiTest-js_myMain").implementedModuleNames) + Assert.assertEquals(listOf("MultiTest_myTest"), facetSettings("MultiTest-js_myTest").implementedModuleNames) } @Test diff --git a/idea/idea-jps-common/src/org/jetbrains/kotlin/config/KotlinFacetSettings.kt b/idea/idea-jps-common/src/org/jetbrains/kotlin/config/KotlinFacetSettings.kt index 48c89ae5881..21d5586a4fe 100644 --- a/idea/idea-jps-common/src/org/jetbrains/kotlin/config/KotlinFacetSettings.kt +++ b/idea/idea-jps-common/src/org/jetbrains/kotlin/config/KotlinFacetSettings.kt @@ -192,7 +192,7 @@ class KotlinFacetSettings { } } - var implementedModuleName: String? = null + var implementedModuleNames: List = emptyList() var productionOutputPath: String? = null var testOutputPath: String? = null diff --git a/idea/idea-jps-common/src/org/jetbrains/kotlin/config/facetSerialization.kt b/idea/idea-jps-common/src/org/jetbrains/kotlin/config/facetSerialization.kt index c37eab577f0..82374598345 100644 --- a/idea/idea-jps-common/src/org/jetbrains/kotlin/config/facetSerialization.kt +++ b/idea/idea-jps-common/src/org/jetbrains/kotlin/config/facetSerialization.kt @@ -99,7 +99,12 @@ private fun readV2AndLaterConfig(element: Element): KotlinFacetSettings { val platformName = element.getAttributeValue("platform") val platformKind = TargetPlatformKind.ALL_PLATFORMS.firstOrNull { it.description == platformName } ?: TargetPlatformKind.DEFAULT_PLATFORM element.getChild("implements")?.let { - implementedModuleName = (it.content.firstOrNull() as? Text)?.textTrim + val items = it.getChildren("implement") + implementedModuleNames = if (items.isNotEmpty()) { + items.mapNotNull { (it.content.firstOrNull() as? Text)?.textTrim } + } else { + listOfNotNull((it.content.firstOrNull() as? Text)?.textTrim) + } } element.getChild("compilerSettings")?.let { compilerSettings = CompilerSettings() @@ -237,8 +242,17 @@ private fun KotlinFacetSettings.writeLatestConfig(element: Element) { if (!useProjectSettings) { element.setAttribute("useProjectSettings", useProjectSettings.toString()) } - implementedModuleName?.let { - element.addContent(Element("implements").apply { addContent(it) }) + if (implementedModuleNames.isNotEmpty()) { + element.addContent( + Element("implements").apply { + val singleModule = implementedModuleNames.singleOrNull() + if (singleModule != null) { + addContent(singleModule) + } else { + implementedModuleNames.map { addContent(Element("implement").apply { addContent(it) }) } + } + } + ) } productionOutputPath?.let { if (it != (compilerArguments as? K2JSCompilerArguments)?.outputFile) { diff --git a/idea/idea-maven/src/org/jetbrains/kotlin/idea/maven/KotlinMavenImporter.kt b/idea/idea-maven/src/org/jetbrains/kotlin/idea/maven/KotlinMavenImporter.kt index 7904eec3936..147f18d7358 100644 --- a/idea/idea-maven/src/org/jetbrains/kotlin/idea/maven/KotlinMavenImporter.kt +++ b/idea/idea-maven/src/org/jetbrains/kotlin/idea/maven/KotlinMavenImporter.kt @@ -301,13 +301,13 @@ class KotlinMavenImporter : MavenImporter(KOTLIN_PLUGIN_GROUP_ID, KOTLIN_PLUGIN_ private fun setImplementedModuleName(kotlinFacet: KotlinFacet, mavenProject: MavenProject, module: Module) { if (kotlinFacet.configuration.settings.targetPlatformKind == TargetPlatformKind.Common) { - kotlinFacet.configuration.settings.implementedModuleName = null + kotlinFacet.configuration.settings.implementedModuleNames = emptyList() } else { val manager = MavenProjectsManager.getInstance(module.project) val mavenDependencies = mavenProject.dependencies.mapNotNull { manager?.findProject(it) } - val implemented = mavenDependencies.singleOrNull { detectPlatformByExecutions(it) == TargetPlatformKind.Common } + val implemented = mavenDependencies.filter { detectPlatformByExecutions(it) == TargetPlatformKind.Common } - kotlinFacet.configuration.settings.implementedModuleName = implemented?.let { manager.findModule(it)?.name ?: it.displayName } + kotlinFacet.configuration.settings.implementedModuleNames = implemented.map { manager.findModule(it)?.name ?: it.displayName } } } } diff --git a/idea/idea-maven/test/org/jetbrains/kotlin/idea/maven/KotlinMavenImporterTest.kt b/idea/idea-maven/test/org/jetbrains/kotlin/idea/maven/KotlinMavenImporterTest.kt index ade1a4499ed..89b4dcbece9 100644 --- a/idea/idea-maven/test/org/jetbrains/kotlin/idea/maven/KotlinMavenImporterTest.kt +++ b/idea/idea-maven/test/org/jetbrains/kotlin/idea/maven/KotlinMavenImporterTest.kt @@ -1973,8 +1973,8 @@ class KotlinMavenImporterTest : MavenImportingTestCase() { """ ) - val commonModule = createModulePom( - "my-common-module", + val commonModule1 = createModulePom( + "my-common-module1", """ @@ -1984,7 +1984,50 @@ class KotlinMavenImporterTest : MavenImportingTestCase() { test - my-common-module + my-common-module1 + 1.0.0 + + + + org.jetbrains.kotlin + kotlin-stdlib-common + $kotlinVersion + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + + meta + compile + + metadata + + + + + + + """ + ) + + val commonModule2 = createModulePom( + "my-common-module2", + """ + + + test + project + 1.0.0 + + + test + my-common-module2 1.0.0 @@ -2038,7 +2081,12 @@ class KotlinMavenImporterTest : MavenImportingTestCase() { test - my-common-module + my-common-module1 + 1.0.0 + + + test + my-common-module2 1.0.0 @@ -2086,7 +2134,7 @@ class KotlinMavenImporterTest : MavenImportingTestCase() { test - my-common-module + my-common-module1 1.0.0 @@ -2112,23 +2160,27 @@ class KotlinMavenImporterTest : MavenImportingTestCase() { """ ) - importProjects(mainPom, commonModule, jvmModule, jsModule) + importProjects(mainPom, commonModule1, commonModule2, jvmModule, jsModule) - assertModules("project", "my-common-module", "my-jvm-module", "my-js-module") + assertModules("project", "my-common-module1", "my-common-module2", "my-jvm-module", "my-js-module") assertImporterStatePresent() - with(facetSettings("my-common-module")) { + with(facetSettings("my-common-module1")) { + Assert.assertEquals(TargetPlatformKind.Common.description, targetPlatformKind!!.description) + } + + with(facetSettings("my-common-module2")) { Assert.assertEquals(TargetPlatformKind.Common.description, targetPlatformKind!!.description) } with(facetSettings("my-jvm-module")) { Assert.assertEquals(TargetPlatformKind.Jvm(JvmTarget.JVM_1_6).description, targetPlatformKind!!.description) - Assert.assertEquals("my-common-module", implementedModuleName) + Assert.assertEquals(listOf("my-common-module1", "my-common-module2"), implementedModuleNames) } with(facetSettings("my-js-module")) { Assert.assertEquals(TargetPlatformKind.JavaScript.description, targetPlatformKind!!.description) - Assert.assertEquals("my-common-module", implementedModuleName) + Assert.assertEquals(listOf("my-common-module1"), implementedModuleNames) } } diff --git a/idea/kotlin-gradle-tooling/src/KotlinGradleModelBuilder.kt b/idea/kotlin-gradle-tooling/src/KotlinGradleModelBuilder.kt index f9eda649bbf..d71e80e4562 100644 --- a/idea/kotlin-gradle-tooling/src/KotlinGradleModelBuilder.kt +++ b/idea/kotlin-gradle-tooling/src/KotlinGradleModelBuilder.kt @@ -20,7 +20,6 @@ import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.artifacts.Dependency import org.gradle.api.artifacts.ProjectDependency -import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import org.jetbrains.plugins.gradle.tooling.ErrorMessageBuilder import org.jetbrains.plugins.gradle.tooling.ModelBuilderService import java.io.File @@ -48,7 +47,7 @@ interface KotlinGradleModel : Serializable { val compilerArgumentsBySourceSet: CompilerArgumentsBySourceSet val coroutines: String? val platformPluginId: String? - val implements: String? + val implements: List } class KotlinGradleModelImpl( @@ -56,7 +55,7 @@ class KotlinGradleModelImpl( override val compilerArgumentsBySourceSet: CompilerArgumentsBySourceSet, override val coroutines: String?, override val platformPluginId: String?, - override val implements: String? + override val implements: List ) : KotlinGradleModel abstract class AbstractKotlinGradleModelBuilder : ModelBuilderService { @@ -89,11 +88,11 @@ class KotlinGradleModelBuilder : AbstractKotlinGradleModelBuilder() { override fun canBuild(modelName: String?): Boolean = modelName == KotlinGradleModel::class.java.name - private fun getImplements(project: Project): Project? { + private fun getImplementedProjects(project: Project): List { return listOf("expectedBy", "implement") .flatMap { project.configurations.findByName(it)?.dependencies ?: emptySet() } - .firstIsInstanceOrNull() - ?.dependencyProject + .filterIsInstance() + .mapNotNull { it.dependencyProject } } // see GradleProjectResolverUtil.getModuleId() in IDEA codebase @@ -164,14 +163,14 @@ class KotlinGradleModelBuilder : AbstractKotlinGradleModelBuilder() { } val platform = platformPluginId ?: pluginToPlatform.entries.singleOrNull { project.plugins.findPlugin(it.key) != null }?.value - val implementedProject = getImplements(project) + val implementedProjects = getImplementedProjects(project) return KotlinGradleModelImpl( kotlinPluginId != null || platformPluginId != null, compilerArgumentsBySourceSet, getCoroutines(project), platform, - implementedProject?.pathOrName() + implementedProjects.map { it.pathOrName() } ) } } diff --git a/idea/src/org/jetbrains/kotlin/idea/facet/facetUtils.kt b/idea/src/org/jetbrains/kotlin/idea/facet/facetUtils.kt index 4de7994e3ea..c1be3a780d3 100644 --- a/idea/src/org/jetbrains/kotlin/idea/facet/facetUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/facet/facetUtils.kt @@ -123,11 +123,8 @@ val mavenLibraryIdToPlatform: Map> by lazy { .toMap() } -internal fun Module.findImplementingModules(modelsProvider: IdeModifiableModelsProvider): List { - return modelsProvider.modules.filter { module -> - module.findImplementedModuleName(modelsProvider) == name - } -} +internal fun Module.findImplementingModules(modelsProvider: IdeModifiableModelsProvider) = + modelsProvider.modules.filter { name in it.findImplementedModuleNames(modelsProvider) } val Module.implementingModules: List get() = cached(CachedValueProvider { @@ -167,12 +164,11 @@ val ModuleDescriptor.implementingDescriptors: List }) } -val ModuleDescriptor.implementedDescriptor: ModuleDescriptor? +val ModuleDescriptor.implementedDescriptors: List get() { - val moduleSourceInfo = getCapability(ModuleInfo.Capability) as? ModuleSourceInfo ?: return null + val moduleSourceInfo = getCapability(ModuleInfo.Capability) as? ModuleSourceInfo ?: return emptyList() - val implementedModuleInfo = moduleSourceInfo.expectedBy - return implementedModuleInfo?.let { + return moduleSourceInfo.expectedBy.mapNotNull { KotlinCacheService.getInstance(moduleSourceInfo.module.project) .getResolutionFacadeByModuleInfo(it, it.platform)?.moduleDescriptor } diff --git a/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/HasExpectedMarker.kt b/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/HasExpectedMarker.kt index 73d78e005ee..4d213dbc981 100644 --- a/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/HasExpectedMarker.kt +++ b/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/HasExpectedMarker.kt @@ -20,7 +20,7 @@ import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny import org.jetbrains.kotlin.idea.core.toDescriptor -import org.jetbrains.kotlin.idea.facet.implementedDescriptor +import org.jetbrains.kotlin.idea.facet.implementedDescriptors import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtDeclaration @@ -44,8 +44,8 @@ fun getExpectedDeclarationTooltip(declaration: KtDeclaration?): String? { val descriptor = declaration?.toDescriptor() as? MemberDescriptor ?: return null val platformModuleDescriptor = declaration.containingKtFile.findModuleDescriptor() - val commonModuleDescriptor = platformModuleDescriptor.implementedDescriptor ?: return null - if (!commonModuleDescriptor.hasDeclarationOf(descriptor)) return null + val commonModuleDescriptors = platformModuleDescriptor.implementedDescriptors ?: return null + if (!commonModuleDescriptors.any { it.hasDeclarationOf(descriptor) }) return null return "Has declaration in common module" } @@ -54,7 +54,10 @@ fun navigateToExpectedDeclaration(declaration: KtDeclaration?) { declaration?.expectedDeclarationIfAny()?.navigate(false) } -internal fun MemberDescriptor.expectedDescriptor() = module.implementedDescriptor?.declarationOf(this) +internal fun MemberDescriptor.expectedDescriptors() = module.implementedDescriptors.mapNotNull { it.declarationOf(this) } + +// TODO: Sort out the cases with multiple expected descriptors +internal fun MemberDescriptor.expectedDescriptor() = expectedDescriptors().firstOrNull() internal fun KtDeclaration.expectedDeclarationIfAny(): KtDeclaration? { val expectedDescriptor = (toDescriptor() as? MemberDescriptor)?.expectedDescriptor() ?: return null diff --git a/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/KotlinLineMarkerProvider.kt b/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/KotlinLineMarkerProvider.kt index cb6713c1a6d..db8b709b5a7 100644 --- a/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/KotlinLineMarkerProvider.kt +++ b/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/KotlinLineMarkerProvider.kt @@ -39,7 +39,7 @@ import org.jetbrains.kotlin.idea.caches.lightClasses.KtFakeLightMethod import org.jetbrains.kotlin.idea.core.isInheritable import org.jetbrains.kotlin.idea.core.isOverridable import org.jetbrains.kotlin.idea.core.toDescriptor -import org.jetbrains.kotlin.idea.facet.implementedDescriptor +import org.jetbrains.kotlin.idea.facet.implementedDescriptors import org.jetbrains.kotlin.idea.facet.implementingDescriptors import org.jetbrains.kotlin.idea.search.declarationsSearch.toPossiblyFakeLightMethods import org.jetbrains.kotlin.idea.util.ProjectRootsUtil @@ -379,8 +379,7 @@ private fun collectExpectedMarkers(declaration: KtNamedDeclaration, val descriptor = declaration.toDescriptor() as? MemberDescriptor ?: return val platformModuleDescriptor = declaration.containingKtFile.findModuleDescriptor() - val commonModuleDescriptor = platformModuleDescriptor.implementedDescriptor ?: return - if (!commonModuleDescriptor.hasDeclarationOf(descriptor)) return + if (!platformModuleDescriptor.implementedDescriptors.any { it.hasDeclarationOf(descriptor) }) return val anchor = declaration.expectOrActualAnchor diff --git a/idea/tests/org/jetbrains/kotlin/idea/configuration/ConfigureKotlinTest.java b/idea/tests/org/jetbrains/kotlin/idea/configuration/ConfigureKotlinTest.java index 58cc72d4c43..6813971389f 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/configuration/ConfigureKotlinTest.java +++ b/idea/tests/org/jetbrains/kotlin/idea/configuration/ConfigureKotlinTest.java @@ -48,6 +48,9 @@ import java.util.Arrays; import java.util.Collections; import java.util.stream.StreamSupport; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; + public class ConfigureKotlinTest extends AbstractConfigureKotlinTest { public void testNewLibrary_copyJar() { doTestOneJavaModule(KotlinWithLibraryConfigurator.FileState.COPY); @@ -269,8 +272,8 @@ public class ConfigureKotlinTest extends AbstractConfigureKotlinTest { Module module2 = moduleManager.findModuleByName("module2"); assert module2 != null; - assertEquals(KotlinFacet.Companion.get(module1).getConfiguration().getSettings().getImplementedModuleName(), null); - assertEquals(KotlinFacet.Companion.get(module2).getConfiguration().getSettings().getImplementedModuleName(), "module1"); + assertEquals(KotlinFacet.Companion.get(module1).getConfiguration().getSettings().getImplementedModuleNames(), emptyList()); + assertEquals(KotlinFacet.Companion.get(module2).getConfiguration().getSettings().getImplementedModuleNames(), singletonList("module1")); } public void testJava9WithModuleInfo() { diff --git a/idea/tests/org/jetbrains/kotlin/idea/stubs/AbstractMultiModuleTest.kt b/idea/tests/org/jetbrains/kotlin/idea/stubs/AbstractMultiModuleTest.kt index 409bac99168..a2cb71c2eb0 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/stubs/AbstractMultiModuleTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/stubs/AbstractMultiModuleTest.kt @@ -121,7 +121,7 @@ fun Module.createFacet( platformKind ) if (implementedModuleName != null) { - this.implementedModuleName = implementedModuleName + this.implementedModuleNames = listOf(implementedModuleName) } } modelsProvider.commit()