MPP: Support multiple 'expectedBy' dependencies for single module

#KT-22865 Fixed
This commit is contained in:
Alexey Sedunov
2018-02-20 19:26:24 +03:00
parent e3a69d118f
commit d9aa1796cd
22 changed files with 197 additions and 108 deletions
@@ -239,7 +239,7 @@ interface ModuleInfo {
val name: Name val name: Name
val displayedName: String get() = name.asString() val displayedName: String get() = name.asString()
fun dependencies(): List<ModuleInfo> fun dependencies(): List<ModuleInfo>
val expectedBy: ModuleInfo? get() = null val expectedBy: List<ModuleInfo> get() = emptyList()
val platform: TargetPlatform? get() = null val platform: TargetPlatform? get() = null
fun modulesWhoseInternalsAreVisible(): Collection<ModuleInfo> = listOf() fun modulesWhoseInternalsAreVisible(): Collection<ModuleInfo> = listOf()
val capabilities: Map<ModuleDescriptor.Capability<*>, Any?> val capabilities: Map<ModuleDescriptor.Capability<*>, Any?>
@@ -310,8 +310,8 @@ class LazyModuleDependencies<M : ModuleInfo>(
override val allDependencies: List<ModuleDescriptorImpl> get() = dependencies() override val allDependencies: List<ModuleDescriptorImpl> get() = dependencies()
override val expectedByDependency by storageManager.createNullableLazyValue { override val expectedByDependencies by storageManager.createLazyValue {
module.expectedBy?.let { resolverForProject.descriptorForModule(it as M) } module.expectedBy.map { resolverForProject.descriptorForModule(it as M) }
} }
override val modulesWhoseInternalsAreVisible: Set<ModuleDescriptorImpl> override val modulesWhoseInternalsAreVisible: Set<ModuleDescriptorImpl>
@@ -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.multiplatform.ExpectedActualResolver.Compatibility.Incompatible
import org.jetbrains.kotlin.resolve.source.PsiSourceFile import org.jetbrains.kotlin.resolve.source.PsiSourceFile
import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jetbrains.kotlin.utils.ifEmpty
import java.io.File import java.io.File
object ExpectedActualDeclarationChecker : DeclarationChecker { object ExpectedActualDeclarationChecker : DeclarationChecker {
@@ -114,8 +115,16 @@ object ExpectedActualDeclarationChecker : DeclarationChecker {
// However, in compiler context platform & common modules are joined into one module, // However, in compiler context platform & common modules are joined into one module,
// so there is yet no "common module" in this situation. // 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. // So yet we are using own module in compiler context and common module in IDE context.
val commonOrOwnModule = descriptor.module.expectedByModule ?: descriptor.module val commonOrOwnModules = descriptor.module.expectedByModules.ifEmpty { listOf(descriptor.module) }
val compatibility = ExpectedActualResolver.findExpectedForActual(descriptor, commonOrOwnModule) ?: return val compatibility = commonOrOwnModules
.asSequence()
.fold(LinkedHashMap<Compatibility, List<MemberDescriptor>>()) { resultMap, commonModule ->
val currentMap = ExpectedActualResolver.findExpectedForActual(descriptor, commonModule)
if (currentMap != null) {
resultMap.putAll(currentMap)
}
resultMap
}
val hasActualModifier = descriptor.isActual && reportOn.hasActualModifier() val hasActualModifier = descriptor.isActual && reportOn.hasActualModifier()
if (!hasActualModifier) { if (!hasActualModifier) {
@@ -40,7 +40,7 @@ interface ModuleDescriptor : DeclarationDescriptor {
*/ */
val allDependencyModules: List<ModuleDescriptor> val allDependencyModules: List<ModuleDescriptor>
val expectedByModule: ModuleDescriptor? val expectedByModules: List<ModuleDescriptor>
fun <T> getCapability(capability: Capability<T>): T? fun <T> getCapability(capability: Capability<T>): T?
@@ -66,8 +66,8 @@ class ModuleDescriptorImpl @JvmOverloads constructor(
override val allDependencyModules: List<ModuleDescriptor> override val allDependencyModules: List<ModuleDescriptor>
get() = this.dependencies.sure { "Dependencies of module $id were not set" }.allDependencies.filter { it != this } get() = this.dependencies.sure { "Dependencies of module $id were not set" }.allDependencies.filter { it != this }
override val expectedByModule: ModuleDescriptor? override val expectedByModules: List<ModuleDescriptor>
get() = this.dependencies.sure { "Dependencies of module $id were not set" }.expectedByDependency get() = this.dependencies.sure { "Dependencies of module $id were not set" }.expectedByDependencies
override fun getPackage(fqName: FqName): PackageViewDescriptor { override fun getPackage(fqName: FqName): PackageViewDescriptor {
assertValid() assertValid()
@@ -111,7 +111,7 @@ class ModuleDescriptorImpl @JvmOverloads constructor(
} }
fun setDependencies(descriptors: List<ModuleDescriptorImpl>, friends: Set<ModuleDescriptorImpl>) { fun setDependencies(descriptors: List<ModuleDescriptorImpl>, friends: Set<ModuleDescriptorImpl>) {
setDependencies(ModuleDependenciesImpl(descriptors, friends, null)) setDependencies(ModuleDependenciesImpl(descriptors, friends, emptyList()))
} }
override fun shouldSeeInternalsOf(targetModule: ModuleDescriptor): Boolean { override fun shouldSeeInternalsOf(targetModule: ModuleDescriptor): Boolean {
@@ -150,11 +150,11 @@ class ModuleDescriptorImpl @JvmOverloads constructor(
interface ModuleDependencies { interface ModuleDependencies {
val allDependencies: List<ModuleDescriptorImpl> val allDependencies: List<ModuleDescriptorImpl>
val modulesWhoseInternalsAreVisible: Set<ModuleDescriptorImpl> val modulesWhoseInternalsAreVisible: Set<ModuleDescriptorImpl>
val expectedByDependency: ModuleDescriptorImpl? val expectedByDependencies: List<ModuleDescriptorImpl>
} }
class ModuleDependenciesImpl( class ModuleDependenciesImpl(
override val allDependencies: List<ModuleDescriptorImpl>, override val allDependencies: List<ModuleDescriptorImpl>,
override val modulesWhoseInternalsAreVisible: Set<ModuleDescriptorImpl>, override val modulesWhoseInternalsAreVisible: Set<ModuleDescriptorImpl>,
override val expectedByDependency: ModuleDescriptorImpl? override val expectedByDependencies: List<ModuleDescriptorImpl>
) : ModuleDependencies ) : ModuleDependencies
@@ -85,10 +85,10 @@ public class ErrorUtils {
return emptyList(); return emptyList();
} }
@Nullable @NotNull
@Override @Override
public ModuleDescriptor getExpectedByModule() { public List<ModuleDescriptor> getExpectedByModules() {
return null; return emptyList();
} }
@Override @Override
@@ -117,24 +117,22 @@ private fun ideaModelDependencies(module: Module, forProduction: Boolean): List<
return result.toList() return result.toList()
} }
fun Module.findImplementedModuleName(modelsProvider: IdeModifiableModelsProvider): String? { fun Module.findImplementedModuleNames(modelsProvider: IdeModifiableModelsProvider): List<String> {
val facetModel = modelsProvider.getModifiableFacetModel(this) val facetModel = modelsProvider.getModifiableFacetModel(this)
val facet = facetModel.findFacet( val facet = facetModel.findFacet(
KotlinFacetType.TYPE_ID, KotlinFacetType.TYPE_ID,
FacetTypeRegistry.getInstance().findFacetType(ID)!!.defaultFacetName FacetTypeRegistry.getInstance().findFacetType(ID)!!.defaultFacetName
) )
return facet?.configuration?.settings?.implementedModuleName return facet?.configuration?.settings?.implementedModuleNames ?: emptyList()
} }
fun Module.findImplementedModule(modelsProvider: IdeModifiableModelsProvider): Module? { fun Module.findImplementedModules(modelsProvider: IdeModifiableModelsProvider) =
val implementedModuleName = findImplementedModuleName(modelsProvider) findImplementedModuleNames(modelsProvider).mapNotNull { modelsProvider.findIdeModule(it) }
return implementedModuleName?.let { modelsProvider.findIdeModule(it) }
}
interface ModuleSourceInfo : IdeaModuleInfo, TrackableModuleInfo { interface ModuleSourceInfo : IdeaModuleInfo, TrackableModuleInfo {
val module: Module val module: Module
override val expectedBy: ModuleSourceInfo? override val expectedBy: List<ModuleSourceInfo>
override val displayedName get() = module.name override val displayedName get() = module.name
@@ -149,11 +147,11 @@ interface ModuleSourceInfo : IdeaModuleInfo, TrackableModuleInfo {
} }
sealed class ModuleSourceInfoWithExpectedBy(private val forProduction: Boolean) : ModuleSourceInfo { sealed class ModuleSourceInfoWithExpectedBy(private val forProduction: Boolean) : ModuleSourceInfo {
override val expectedBy: ModuleSourceInfo? override val expectedBy: List<ModuleSourceInfo>
get() { get() {
val modelsProvider = IdeModifiableModelsProviderImpl(module.project) val modelsProvider = IdeModifiableModelsProviderImpl(module.project)
val expectedByModule = module.findImplementedModule(modelsProvider) val expectedByModules = module.findImplementedModules(modelsProvider)
return if (forProduction) expectedByModule?.productionSourceInfo() else expectedByModule?.testSourceInfo() return expectedByModules.mapNotNull { if (forProduction) it.productionSourceInfo() else it.testSourceInfo() }
} }
override fun dependencies(): List<IdeaModuleInfo> = module.cached(createCachedValueProvider { override fun dependencies(): List<IdeaModuleInfo> = module.cached(createCachedValueProvider {
@@ -49,8 +49,8 @@ var DataNode<ModuleData>.coroutines
by CopyableDataNodeUserDataProperty(Key.create<String>("KOTLIN_COROUTINES")) by CopyableDataNodeUserDataProperty(Key.create<String>("KOTLIN_COROUTINES"))
var DataNode<ModuleData>.platformPluginId var DataNode<ModuleData>.platformPluginId
by CopyableDataNodeUserDataProperty(Key.create<String>("PLATFORM_PLUGIN_ID")) by CopyableDataNodeUserDataProperty(Key.create<String>("PLATFORM_PLUGIN_ID"))
var DataNode<out ModuleData>.implementedModuleName var DataNode<out ModuleData>.implementedModuleNames
by CopyableDataNodeUserDataProperty(Key.create<String>("IMPLEMENTED_MODULE_NAME")) by NotNullableCopyableDataNodeUserDataProperty(Key.create<List<String>>("IMPLEMENTED_MODULE_NAME"), emptyList())
class KotlinGradleProjectResolverExtension : AbstractProjectResolverExtension() { class KotlinGradleProjectResolverExtension : AbstractProjectResolverExtension() {
val isAndroidProjectKey = Key.findKeyByName("IS_ANDROID_PROJECT_KEY") val isAndroidProjectKey = Key.findKeyByName("IS_ANDROID_PROJECT_KEY")
@@ -134,23 +134,23 @@ class KotlinGradleProjectResolverExtension : AbstractProjectResolverExtension()
gradleIdeaProject.modules.firstOrNull { it.gradleProject.path == moduleNodeForGradleModel?.data?.id } gradleIdeaProject.modules.firstOrNull { it.gradleProject.path == moduleNodeForGradleModel?.data?.id }
} }
else gradleModule 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 val compositePrefix = if (resolverCtx.models.ideaProject != gradleModule.project
&& it.startsWith(":")) { && implementsModuleId.startsWith(":")) {
gradleModule.project.name gradleModule.project.name
} else { } else {
"" ""
} }
findModuleById(ideProject, compositePrefix + it) val targetModule = findModuleById(ideProject, compositePrefix + implementsModuleId) ?: continue
}
if (targetModule != null) {
if (useModulePerSourceSet()) { if (useModulePerSourceSet()) {
val targetSourceSetsByName = ExternalSystemApiUtil val targetSourceSetsByName = ExternalSystemApiUtil
.findAll(targetModule, GradleSourceSetData.KEY) .findAll(targetModule, GradleSourceSetData.KEY)
.associateBy { it.sourceSetName } .associateBy { it.sourceSetName }
val targetMainSourceSet = targetSourceSetsByName["main"] ?: targetModule val targetMainSourceSet = targetSourceSetsByName["main"] ?: targetModule
val targetSourceSet = targetSourceSetsByName[currentModuleNode.sourceSetName] val targetSourceSet = targetSourceSetsByName[currentModuleNode.sourceSetName]
if (targetSourceSet != null) { if (targetSourceSet != null) {
@@ -193,17 +193,15 @@ class KotlinGradleProjectResolverExtension : AbstractProjectResolverExtension()
ideProject: DataNode<ProjectData>, ideProject: DataNode<ProjectData>,
gradleModel: KotlinGradleModel gradleModel: KotlinGradleModel
) { ) {
val implementedModule = gradleModel.implements?.let { findModuleById(ideProject, it) } ?: return val implementedModules = gradleModel.implements.mapNotNull { findModuleById(ideProject, it) }
if (resolverCtx.isResolveModulePerSourceSet) { if (resolverCtx.isResolveModulePerSourceSet) {
val dependentSourceSets = dependentModule.getSourceSetsMap() val dependentSourceSets = dependentModule.getSourceSetsMap()
val implementedSourceSets = implementedModule.getSourceSetsMap() val implementedSourceSetMaps = implementedModules.map { it.getSourceSetsMap() }
for ((sourceSetName, dependentSourceSet) in dependentSourceSets) { for ((sourceSetName, dependentSourceSet) in dependentSourceSets) {
val implementedSourceSet = implementedSourceSets[sourceSetName] ?: continue dependentSourceSet.implementedModuleNames = implementedSourceSetMaps.mapNotNull { it[sourceSetName]?.data?.internalName }
dependentSourceSet.implementedModuleName = implementedSourceSet.data.internalName
} }
} } else {
else { dependentModule.implementedModuleNames = implementedModules.map { it.data.internalName }
dependentModule.implementedModuleName = implementedModule.data.internalName
} }
} }
@@ -214,7 +214,7 @@ private fun configureFacetByGradleModule(
} }
with(kotlinFacet.configuration.settings) { with(kotlinFacet.configuration.settings) {
implementedModuleName = (sourceSetNode ?: moduleNode).implementedModuleName implementedModuleNames = (sourceSetNode ?: moduleNode).implementedModuleNames
productionOutputPath = getExplicitOutputPath(moduleNode, platformKind, "main") productionOutputPath = getExplicitOutputPath(moduleNode, platformKind, "main")
testOutputPath = getExplicitOutputPath(moduleNode, platformKind, "test") testOutputPath = getExplicitOutputPath(moduleNode, platformKind, "test")
} }
@@ -175,5 +175,5 @@ class MultiplatformGradleOrderEnumeratorHandler(val factory: MultiplatformGradle
private fun Module.isMultiplatformModule(): Boolean { private fun Module.isMultiplatformModule(): Boolean {
val settings = KotlinFacetSettingsProvider.getInstance(project).getInitializedSettings(this) 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()
} }
@@ -17,14 +17,12 @@
package org.jetbrains.kotlin.gradle package org.jetbrains.kotlin.gradle
import com.intellij.openapi.roots.DependencyScope import com.intellij.openapi.roots.DependencyScope
import com.intellij.openapi.util.text.StringUtil
import com.intellij.util.PathUtil import com.intellij.util.PathUtil
import junit.framework.TestCase import junit.framework.TestCase
import org.jetbrains.kotlin.idea.codeInsight.gradle.GradleImportingTestCase import org.jetbrains.kotlin.idea.codeInsight.gradle.GradleImportingTestCase
import org.jetbrains.kotlin.idea.facet.KotlinFacet import org.jetbrains.kotlin.idea.facet.KotlinFacet
import org.jetbrains.kotlin.test.KotlinTestUtils import org.jetbrains.kotlin.test.KotlinTestUtils
import org.junit.Test import org.junit.Test
import java.io.File
class MultiplatformProjectImportingTest : GradleImportingTestCase() { class MultiplatformProjectImportingTest : GradleImportingTestCase() {
@Test @Test
@@ -74,9 +72,9 @@ class MultiplatformProjectImportingTest : GradleImportingTestCase() {
@Test @Test
fun testPlatformToCommonExpectedByDependency() { 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", """ createProjectSubFile("build.gradle", """
buildscript { buildscript {
@@ -90,7 +88,11 @@ class MultiplatformProjectImportingTest : GradleImportingTestCase() {
} }
} }
project('common') { project('common1') {
apply plugin: 'kotlin-platform-common'
}
project('common2') {
apply plugin: 'kotlin-platform-common' apply plugin: 'kotlin-platform-common'
} }
@@ -98,7 +100,8 @@ class MultiplatformProjectImportingTest : GradleImportingTestCase() {
apply plugin: 'kotlin-platform-jvm' apply plugin: 'kotlin-platform-jvm'
dependencies { dependencies {
expectedBy project(':common') expectedBy project(':common1')
expectedBy project(':common2')
} }
} }
@@ -106,16 +109,20 @@ class MultiplatformProjectImportingTest : GradleImportingTestCase() {
apply plugin: 'kotlin-platform-js' apply plugin: 'kotlin-platform-js'
dependencies { dependencies {
expectedBy project(':common') expectedBy project(':common1')
} }
} }
""") """)
importProject() importProject()
assertModuleModuleDepScope("jvm_main", "common_main", DependencyScope.COMPILE) assertModuleModuleDepScope("jvm_main", "common1_main", DependencyScope.COMPILE)
assertModuleModuleDepScope("jvm_test", "common_test", DependencyScope.COMPILE) assertModuleModuleDepScope("jvm_main", "common2_main", DependencyScope.COMPILE)
assertModuleModuleDepScope("js_main", "common_main", DependencyScope.COMPILE) assertModuleModuleDepScope("jvm_test", "common1_test", DependencyScope.COMPILE)
assertModuleModuleDepScope("js_test", "common_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 @Test
@@ -128,6 +128,10 @@ public abstract class ExternalSystemImportingTestCase extends ExternalSystemTest
expectedScopes, actualScopes); expectedScopes, actualScopes);
} }
protected void assertNoDepForModule(String moduleName, String depName) {
assertEmpty("No dependency '" + depName + "' was expected", collectModuleDeps(moduleName, depName, ModuleOrderEntry.class));
}
@NotNull @NotNull
private List<ModuleOrderEntry> getModuleModuleDeps(@NotNull String moduleName, @NotNull String depName) { private List<ModuleOrderEntry> getModuleModuleDeps(@NotNull String moduleName, @NotNull String depName) {
return getModuleDep(moduleName, depName, ModuleOrderEntry.class); return getModuleDep(moduleName, depName, ModuleOrderEntry.class);
@@ -139,6 +143,14 @@ public abstract class ExternalSystemImportingTestCase extends ExternalSystemTest
@NotNull @NotNull
private <T> List<T> getModuleDep(@NotNull String moduleName, @NotNull String depName, @NotNull Class<T> clazz) { private <T> List<T> getModuleDep(@NotNull String moduleName, @NotNull String depName, @NotNull Class<T> clazz) {
List<T> deps = collectModuleDeps(moduleName, depName, clazz);
assertTrue("Dependency '" + depName + "' for module '" + moduleName + "' not found among: " + collectModuleDepsNames(moduleName, clazz),
!deps.isEmpty());
return deps;
}
@NotNull
private <T> List<T> collectModuleDeps(@NotNull String moduleName, @NotNull String depName, @NotNull Class<T> clazz) {
List<T> deps = ContainerUtil.newArrayList(); List<T> deps = ContainerUtil.newArrayList();
for (OrderEntry e : getRootManager(moduleName).getOrderEntries()) { for (OrderEntry e : getRootManager(moduleName).getOrderEntries()) {
@@ -146,8 +158,7 @@ public abstract class ExternalSystemImportingTestCase extends ExternalSystemTest
deps.add((T)e); deps.add((T)e);
} }
} }
assertTrue("Dependency '" + depName + "' for module '" + moduleName + "' not found among: " + collectModuleDepsNames(moduleName, clazz),
!deps.isEmpty());
return deps; return deps;
} }
@@ -1481,10 +1481,10 @@ compileTestKotlin {
importProject() importProject()
Assert.assertEquals("MultiTest_main", facetSettings("MultiTest-jvm_main").implementedModuleName) Assert.assertEquals(listOf("MultiTest_main"), facetSettings("MultiTest-jvm_main").implementedModuleNames)
Assert.assertEquals("MultiTest_test", facetSettings("MultiTest-jvm_test").implementedModuleName) Assert.assertEquals(listOf("MultiTest_test"), facetSettings("MultiTest-jvm_test").implementedModuleNames)
Assert.assertEquals("MultiTest_main", facetSettings("MultiTest-js_main").implementedModuleName) Assert.assertEquals(listOf("MultiTest_main"), facetSettings("MultiTest-js_main").implementedModuleNames)
Assert.assertEquals("MultiTest_test", facetSettings("MultiTest-js_test").implementedModuleName) Assert.assertEquals(listOf("MultiTest_test"), facetSettings("MultiTest-js_test").implementedModuleNames)
} }
@Test @Test
@@ -1630,10 +1630,10 @@ compileTestKotlin {
importProject() importProject()
Assert.assertEquals("MultiTest_myMain", facetSettings("MultiTest-jvm_myMain").implementedModuleName) Assert.assertEquals(listOf("MultiTest_myMain"), facetSettings("MultiTest-jvm_myMain").implementedModuleNames)
Assert.assertEquals("MultiTest_myTest", facetSettings("MultiTest-jvm_myTest").implementedModuleName) Assert.assertEquals(listOf("MultiTest_myTest"), facetSettings("MultiTest-jvm_myTest").implementedModuleNames)
Assert.assertEquals("MultiTest_myMain", facetSettings("MultiTest-js_myMain").implementedModuleName) Assert.assertEquals(listOf("MultiTest_myMain"), facetSettings("MultiTest-js_myMain").implementedModuleNames)
Assert.assertEquals("MultiTest_myTest", facetSettings("MultiTest-js_myTest").implementedModuleName) Assert.assertEquals(listOf("MultiTest_myTest"), facetSettings("MultiTest-js_myTest").implementedModuleNames)
} }
@Test @Test
@@ -192,7 +192,7 @@ class KotlinFacetSettings {
} }
} }
var implementedModuleName: String? = null var implementedModuleNames: List<String> = emptyList()
var productionOutputPath: String? = null var productionOutputPath: String? = null
var testOutputPath: String? = null var testOutputPath: String? = null
@@ -99,7 +99,12 @@ private fun readV2AndLaterConfig(element: Element): KotlinFacetSettings {
val platformName = element.getAttributeValue("platform") val platformName = element.getAttributeValue("platform")
val platformKind = TargetPlatformKind.ALL_PLATFORMS.firstOrNull { it.description == platformName } ?: TargetPlatformKind.DEFAULT_PLATFORM val platformKind = TargetPlatformKind.ALL_PLATFORMS.firstOrNull { it.description == platformName } ?: TargetPlatformKind.DEFAULT_PLATFORM
element.getChild("implements")?.let { 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 { element.getChild("compilerSettings")?.let {
compilerSettings = CompilerSettings() compilerSettings = CompilerSettings()
@@ -237,8 +242,17 @@ private fun KotlinFacetSettings.writeLatestConfig(element: Element) {
if (!useProjectSettings) { if (!useProjectSettings) {
element.setAttribute("useProjectSettings", useProjectSettings.toString()) element.setAttribute("useProjectSettings", useProjectSettings.toString())
} }
implementedModuleName?.let { if (implementedModuleNames.isNotEmpty()) {
element.addContent(Element("implements").apply { addContent(it) }) 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 { productionOutputPath?.let {
if (it != (compilerArguments as? K2JSCompilerArguments)?.outputFile) { if (it != (compilerArguments as? K2JSCompilerArguments)?.outputFile) {
@@ -301,13 +301,13 @@ class KotlinMavenImporter : MavenImporter(KOTLIN_PLUGIN_GROUP_ID, KOTLIN_PLUGIN_
private fun setImplementedModuleName(kotlinFacet: KotlinFacet, mavenProject: MavenProject, module: Module) { private fun setImplementedModuleName(kotlinFacet: KotlinFacet, mavenProject: MavenProject, module: Module) {
if (kotlinFacet.configuration.settings.targetPlatformKind == TargetPlatformKind.Common) { if (kotlinFacet.configuration.settings.targetPlatformKind == TargetPlatformKind.Common) {
kotlinFacet.configuration.settings.implementedModuleName = null kotlinFacet.configuration.settings.implementedModuleNames = emptyList()
} else { } else {
val manager = MavenProjectsManager.getInstance(module.project) val manager = MavenProjectsManager.getInstance(module.project)
val mavenDependencies = mavenProject.dependencies.mapNotNull { manager?.findProject(it) } 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 }
} }
} }
} }
@@ -1973,8 +1973,8 @@ class KotlinMavenImporterTest : MavenImportingTestCase() {
""" """
) )
val commonModule = createModulePom( val commonModule1 = createModulePom(
"my-common-module", "my-common-module1",
""" """
<parent> <parent>
@@ -1984,7 +1984,50 @@ class KotlinMavenImporterTest : MavenImportingTestCase() {
</parent> </parent>
<groupId>test</groupId> <groupId>test</groupId>
<artifactId>my-common-module</artifactId> <artifactId>my-common-module1</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-common</artifactId>
<version>$kotlinVersion</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<executions>
<execution>
<id>meta</id>
<phase>compile</phase>
<goals>
<goal>metadata</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
"""
)
val commonModule2 = createModulePom(
"my-common-module2",
"""
<parent>
<groupId>test</groupId>
<artifactId>project</artifactId>
<version>1.0.0</version>
</parent>
<groupId>test</groupId>
<artifactId>my-common-module2</artifactId>
<version>1.0.0</version> <version>1.0.0</version>
<dependencies> <dependencies>
@@ -2038,7 +2081,12 @@ class KotlinMavenImporterTest : MavenImportingTestCase() {
</dependency> </dependency>
<dependency> <dependency>
<groupId>test</groupId> <groupId>test</groupId>
<artifactId>my-common-module</artifactId> <artifactId>my-common-module1</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>test</groupId>
<artifactId>my-common-module2</artifactId>
<version>1.0.0</version> <version>1.0.0</version>
</dependency> </dependency>
</dependencies> </dependencies>
@@ -2086,7 +2134,7 @@ class KotlinMavenImporterTest : MavenImportingTestCase() {
</dependency> </dependency>
<dependency> <dependency>
<groupId>test</groupId> <groupId>test</groupId>
<artifactId>my-common-module</artifactId> <artifactId>my-common-module1</artifactId>
<version>1.0.0</version> <version>1.0.0</version>
</dependency> </dependency>
</dependencies> </dependencies>
@@ -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() 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) Assert.assertEquals(TargetPlatformKind.Common.description, targetPlatformKind!!.description)
} }
with(facetSettings("my-jvm-module")) { with(facetSettings("my-jvm-module")) {
Assert.assertEquals(TargetPlatformKind.Jvm(JvmTarget.JVM_1_6).description, targetPlatformKind!!.description) 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")) { with(facetSettings("my-js-module")) {
Assert.assertEquals(TargetPlatformKind.JavaScript.description, targetPlatformKind!!.description) Assert.assertEquals(TargetPlatformKind.JavaScript.description, targetPlatformKind!!.description)
Assert.assertEquals("my-common-module", implementedModuleName) Assert.assertEquals(listOf("my-common-module1"), implementedModuleNames)
} }
} }
@@ -20,7 +20,6 @@ import org.gradle.api.Project
import org.gradle.api.Task import org.gradle.api.Task
import org.gradle.api.artifacts.Dependency import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.ProjectDependency 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.ErrorMessageBuilder
import org.jetbrains.plugins.gradle.tooling.ModelBuilderService import org.jetbrains.plugins.gradle.tooling.ModelBuilderService
import java.io.File import java.io.File
@@ -48,7 +47,7 @@ interface KotlinGradleModel : Serializable {
val compilerArgumentsBySourceSet: CompilerArgumentsBySourceSet val compilerArgumentsBySourceSet: CompilerArgumentsBySourceSet
val coroutines: String? val coroutines: String?
val platformPluginId: String? val platformPluginId: String?
val implements: String? val implements: List<String>
} }
class KotlinGradleModelImpl( class KotlinGradleModelImpl(
@@ -56,7 +55,7 @@ class KotlinGradleModelImpl(
override val compilerArgumentsBySourceSet: CompilerArgumentsBySourceSet, override val compilerArgumentsBySourceSet: CompilerArgumentsBySourceSet,
override val coroutines: String?, override val coroutines: String?,
override val platformPluginId: String?, override val platformPluginId: String?,
override val implements: String? override val implements: List<String>
) : KotlinGradleModel ) : KotlinGradleModel
abstract class AbstractKotlinGradleModelBuilder : ModelBuilderService { abstract class AbstractKotlinGradleModelBuilder : ModelBuilderService {
@@ -89,11 +88,11 @@ class KotlinGradleModelBuilder : AbstractKotlinGradleModelBuilder() {
override fun canBuild(modelName: String?): Boolean = modelName == KotlinGradleModel::class.java.name override fun canBuild(modelName: String?): Boolean = modelName == KotlinGradleModel::class.java.name
private fun getImplements(project: Project): Project? { private fun getImplementedProjects(project: Project): List<Project> {
return listOf("expectedBy", "implement") return listOf("expectedBy", "implement")
.flatMap { project.configurations.findByName(it)?.dependencies ?: emptySet<Dependency>() } .flatMap { project.configurations.findByName(it)?.dependencies ?: emptySet<Dependency>() }
.firstIsInstanceOrNull<ProjectDependency>() .filterIsInstance<ProjectDependency>()
?.dependencyProject .mapNotNull { it.dependencyProject }
} }
// see GradleProjectResolverUtil.getModuleId() in IDEA codebase // 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 platform = platformPluginId ?: pluginToPlatform.entries.singleOrNull { project.plugins.findPlugin(it.key) != null }?.value
val implementedProject = getImplements(project) val implementedProjects = getImplementedProjects(project)
return KotlinGradleModelImpl( return KotlinGradleModelImpl(
kotlinPluginId != null || platformPluginId != null, kotlinPluginId != null || platformPluginId != null,
compilerArgumentsBySourceSet, compilerArgumentsBySourceSet,
getCoroutines(project), getCoroutines(project),
platform, platform,
implementedProject?.pathOrName() implementedProjects.map { it.pathOrName() }
) )
} }
} }
@@ -123,11 +123,8 @@ val mavenLibraryIdToPlatform: Map<String, TargetPlatformKind<*>> by lazy {
.toMap() .toMap()
} }
internal fun Module.findImplementingModules(modelsProvider: IdeModifiableModelsProvider): List<Module> { internal fun Module.findImplementingModules(modelsProvider: IdeModifiableModelsProvider) =
return modelsProvider.modules.filter { module -> modelsProvider.modules.filter { name in it.findImplementedModuleNames(modelsProvider) }
module.findImplementedModuleName(modelsProvider) == name
}
}
val Module.implementingModules: List<Module> val Module.implementingModules: List<Module>
get() = cached(CachedValueProvider { get() = cached(CachedValueProvider {
@@ -167,12 +164,11 @@ val ModuleDescriptor.implementingDescriptors: List<ModuleDescriptor>
}) })
} }
val ModuleDescriptor.implementedDescriptor: ModuleDescriptor? val ModuleDescriptor.implementedDescriptors: List<ModuleDescriptor>
get() { get() {
val moduleSourceInfo = getCapability(ModuleInfo.Capability) as? ModuleSourceInfo ?: return null val moduleSourceInfo = getCapability(ModuleInfo.Capability) as? ModuleSourceInfo ?: return emptyList()
val implementedModuleInfo = moduleSourceInfo.expectedBy return moduleSourceInfo.expectedBy.mapNotNull {
return implementedModuleInfo?.let {
KotlinCacheService.getInstance(moduleSourceInfo.module.project) KotlinCacheService.getInstance(moduleSourceInfo.module.project)
.getResolutionFacadeByModuleInfo(it, it.platform)?.moduleDescriptor .getResolutionFacadeByModuleInfo(it, it.platform)?.moduleDescriptor
} }
@@ -20,7 +20,7 @@ import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.core.toDescriptor 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.idea.search.usagesSearch.descriptor
import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtDeclaration
@@ -44,8 +44,8 @@ fun getExpectedDeclarationTooltip(declaration: KtDeclaration?): String? {
val descriptor = declaration?.toDescriptor() as? MemberDescriptor ?: return null val descriptor = declaration?.toDescriptor() as? MemberDescriptor ?: return null
val platformModuleDescriptor = declaration.containingKtFile.findModuleDescriptor() val platformModuleDescriptor = declaration.containingKtFile.findModuleDescriptor()
val commonModuleDescriptor = platformModuleDescriptor.implementedDescriptor ?: return null val commonModuleDescriptors = platformModuleDescriptor.implementedDescriptors ?: return null
if (!commonModuleDescriptor.hasDeclarationOf(descriptor)) return null if (!commonModuleDescriptors.any { it.hasDeclarationOf(descriptor) }) return null
return "Has declaration in common module" return "Has declaration in common module"
} }
@@ -54,7 +54,10 @@ fun navigateToExpectedDeclaration(declaration: KtDeclaration?) {
declaration?.expectedDeclarationIfAny()?.navigate(false) 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? { internal fun KtDeclaration.expectedDeclarationIfAny(): KtDeclaration? {
val expectedDescriptor = (toDescriptor() as? MemberDescriptor)?.expectedDescriptor() ?: return null val expectedDescriptor = (toDescriptor() as? MemberDescriptor)?.expectedDescriptor() ?: return null
@@ -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.isInheritable
import org.jetbrains.kotlin.idea.core.isOverridable import org.jetbrains.kotlin.idea.core.isOverridable
import org.jetbrains.kotlin.idea.core.toDescriptor 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.facet.implementingDescriptors
import org.jetbrains.kotlin.idea.search.declarationsSearch.toPossiblyFakeLightMethods import org.jetbrains.kotlin.idea.search.declarationsSearch.toPossiblyFakeLightMethods
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
@@ -379,8 +379,7 @@ private fun collectExpectedMarkers(declaration: KtNamedDeclaration,
val descriptor = declaration.toDescriptor() as? MemberDescriptor ?: return val descriptor = declaration.toDescriptor() as? MemberDescriptor ?: return
val platformModuleDescriptor = declaration.containingKtFile.findModuleDescriptor() val platformModuleDescriptor = declaration.containingKtFile.findModuleDescriptor()
val commonModuleDescriptor = platformModuleDescriptor.implementedDescriptor ?: return if (!platformModuleDescriptor.implementedDescriptors.any { it.hasDeclarationOf(descriptor) }) return
if (!commonModuleDescriptor.hasDeclarationOf(descriptor)) return
val anchor = declaration.expectOrActualAnchor val anchor = declaration.expectOrActualAnchor
@@ -48,6 +48,9 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
public class ConfigureKotlinTest extends AbstractConfigureKotlinTest { public class ConfigureKotlinTest extends AbstractConfigureKotlinTest {
public void testNewLibrary_copyJar() { public void testNewLibrary_copyJar() {
doTestOneJavaModule(KotlinWithLibraryConfigurator.FileState.COPY); doTestOneJavaModule(KotlinWithLibraryConfigurator.FileState.COPY);
@@ -269,8 +272,8 @@ public class ConfigureKotlinTest extends AbstractConfigureKotlinTest {
Module module2 = moduleManager.findModuleByName("module2"); Module module2 = moduleManager.findModuleByName("module2");
assert module2 != null; assert module2 != null;
assertEquals(KotlinFacet.Companion.get(module1).getConfiguration().getSettings().getImplementedModuleName(), null); assertEquals(KotlinFacet.Companion.get(module1).getConfiguration().getSettings().getImplementedModuleNames(), emptyList());
assertEquals(KotlinFacet.Companion.get(module2).getConfiguration().getSettings().getImplementedModuleName(), "module1"); assertEquals(KotlinFacet.Companion.get(module2).getConfiguration().getSettings().getImplementedModuleNames(), singletonList("module1"));
} }
public void testJava9WithModuleInfo() { public void testJava9WithModuleInfo() {
@@ -121,7 +121,7 @@ fun Module.createFacet(
platformKind platformKind
) )
if (implementedModuleName != null) { if (implementedModuleName != null) {
this.implementedModuleName = implementedModuleName this.implementedModuleNames = listOf(implementedModuleName)
} }
} }
modelsProvider.commit() modelsProvider.commit()