From ecf607d4faecbee202844ee83f8ad3715c228458 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Fri, 3 Aug 2018 18:54:40 +0300 Subject: [PATCH] Gradle: Implement new MPP model import --- .../cli/common/arguments/argumentUtils.kt | 12 +- .../kotlin/idea/util/ProjectRootsUtil.kt | 4 + ...KotlinAndroidGradleMPPModuleDataService.kt | 72 +++ ...inAndroidGradleMPPModuleDataService.kt.173 | 70 ++ ...inAndroidGradleMPPModuleDataService.kt.181 | 72 +++ ...nAndroidGradleMPPModuleDataService.kt.as31 | 72 +++ ...nAndroidGradleMPPModuleDataService.kt.as32 | 72 +++ .../KotlinAndroidMPPGradleProjectResolver.kt | 81 +++ .../KotlinGradleProjectResolverExtension.kt | 5 + .../KotlinGradleSourceSetDataService.kt | 5 +- .../idea/configuration/KotlinMPPDataNodes.kt | 45 ++ .../KotlinMPPGradleProjectResolver.kt | 610 ++++++++++++++++++ .../KotlinSourceSetDataService.kt | 163 +++++ .../configuration/KotlinTargetDataService.kt | 46 ++ .../idea/configuration/dataNodeUtils.kt | 17 + .../kotlin/config/KotlinFacetSettings.kt | 9 + .../kotlin/config/facetSerialization.kt | 24 + .../src/KotlinMPPGradleModel.kt | 83 +++ .../src/KotlinMPPGradleModelBuilder.kt | 264 ++++++++ .../src/KotlinMPPGradleModelBuilder.kt.181 | 264 ++++++++ .../src/KotlinMPPGradleModelImpl.kt | 86 +++ ...plugins.gradle.tooling.ModelBuilderService | 1 + idea/kotlin-gradle-tooling/src/utils.kt | 20 + idea/src/META-INF/android.xml | 2 + idea/src/META-INF/android.xml.173 | 2 + idea/src/META-INF/android.xml.as31 | 2 + idea/src/META-INF/android.xml.as32 | 2 + idea/src/META-INF/gradle-java.xml | 3 + idea/src/META-INF/gradle-java.xml.as33 | 3 + idea/src/META-INF/gradle.xml.181 | 3 + idea/src/META-INF/gradle.xml.as33 | 3 + .../jetbrains/kotlin/idea/facet/facetUtils.kt | 21 +- 32 files changed, 2128 insertions(+), 10 deletions(-) create mode 100644 idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinAndroidGradleMPPModuleDataService.kt create mode 100644 idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinAndroidGradleMPPModuleDataService.kt.173 create mode 100644 idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinAndroidGradleMPPModuleDataService.kt.181 create mode 100644 idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinAndroidGradleMPPModuleDataService.kt.as31 create mode 100644 idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinAndroidGradleMPPModuleDataService.kt.as32 create mode 100644 idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinAndroidMPPGradleProjectResolver.kt create mode 100644 idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPDataNodes.kt create mode 100644 idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPGradleProjectResolver.kt create mode 100644 idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinSourceSetDataService.kt create mode 100644 idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinTargetDataService.kt create mode 100644 idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/dataNodeUtils.kt create mode 100644 idea/kotlin-gradle-tooling/src/KotlinMPPGradleModel.kt create mode 100644 idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt create mode 100644 idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt.181 create mode 100644 idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelImpl.kt create mode 100644 idea/kotlin-gradle-tooling/src/utils.kt diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/argumentUtils.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/argumentUtils.kt index 1f3871e0b9a..d74c9f21a79 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/argumentUtils.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/argumentUtils.kt @@ -23,12 +23,14 @@ import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KProperty1 import kotlin.reflect.KVisibility import kotlin.reflect.full.declaredMemberProperties -import kotlin.reflect.full.findAnnotation import kotlin.reflect.full.memberProperties @Suppress("UNCHECKED_CAST") -fun copyBean(bean: T) = - copyProperties(bean, bean::class.java.newInstance()!!, true, collectProperties(bean::class as KClass, false)) +fun copyBean(bean: T) = copyBeanTo(bean, bean::class.java.newInstance()!!) + +@Suppress("UNCHECKED_CAST") +fun copyBeanTo(from: T, to: T, filter: ((KProperty1, Any?) -> Boolean)? = null) = + copyProperties(from, to, true, collectProperties(from::class as KClass, false), filter) fun mergeBeans(from: From, to: To): To { // TODO: rewrite when updated version of com.intellij.util.xmlb is available on TeamCity @@ -48,7 +50,8 @@ private fun copyProperties( from: From, to: To, deepCopyWhenNeeded: Boolean, - propertiesToCopy: List> + propertiesToCopy: List>, + filter: ((KProperty1, Any?) -> Boolean)? = null ): To { if (from == to) return to @@ -57,6 +60,7 @@ private fun copyProperties( val toProperty = to::class.memberProperties.firstOrNull { it.name == fromProperty.name } as? KMutableProperty1 ?: continue val fromValue = fromProperty.get(from) + if (filter != null && !filter(fromProperty, fromValue)) continue toProperty.set(to, if (deepCopyWhenNeeded) fromValue?.copyValueIfNeeded() else fromValue) } return to diff --git a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ProjectRootsUtil.kt b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ProjectRootsUtil.kt index da551dcc5f5..94d5728b918 100644 --- a/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ProjectRootsUtil.kt +++ b/idea/idea-analysis/src/org/jetbrains/kotlin/idea/util/ProjectRootsUtil.kt @@ -12,6 +12,7 @@ import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.fileTypes.FileType import com.intellij.openapi.fileTypes.FileTypeManager import com.intellij.openapi.module.Module +import com.intellij.openapi.module.ModulePointerManager import com.intellij.openapi.module.ModuleUtilCore import com.intellij.openapi.project.Project import com.intellij.openapi.roots.FileIndex @@ -145,3 +146,6 @@ val PsiElement.module: Module? get() = ModuleUtilCore.findModuleForPsiElement(this) fun VirtualFile.findModule(project: Project) = ModuleUtilCore.findModuleForFile(this, project) + +fun Module.createPointer() = + ModulePointerManager.getInstance(project).create(this) \ No newline at end of file diff --git a/idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinAndroidGradleMPPModuleDataService.kt b/idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinAndroidGradleMPPModuleDataService.kt new file mode 100644 index 00000000000..cc8cb73c64c --- /dev/null +++ b/idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinAndroidGradleMPPModuleDataService.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.android.configure + +import com.android.tools.idea.gradle.project.model.AndroidModuleModel +import com.android.tools.idea.gradle.util.ContentEntries.findParentContentEntry +import com.android.tools.idea.io.FilePaths +import com.intellij.openapi.externalSystem.model.DataNode +import com.intellij.openapi.externalSystem.model.ProjectKeys +import com.intellij.openapi.externalSystem.model.project.ModuleData +import com.intellij.openapi.externalSystem.model.project.ProjectData +import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProvider +import com.intellij.openapi.externalSystem.service.project.manage.AbstractProjectDataService +import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ModifiableRootModel +import com.intellij.util.containers.stream +import org.jetbrains.jps.model.java.JavaResourceRootType +import org.jetbrains.jps.model.java.JavaSourceRootType +import org.jetbrains.jps.model.module.JpsModuleSourceRootType +import org.jetbrains.kotlin.gradle.KotlinCompilation +import java.io.File + +class KotlinAndroidGradleMPPModuleDataService : AbstractProjectDataService() { + override fun getTargetDataKey() = ProjectKeys.MODULE + + override fun postProcess( + toImport: MutableCollection>, + projectData: ProjectData?, + project: Project, + modelsProvider: IdeModifiableModelsProvider + ) { + for (nodeToImport in toImport) { + val projectNode = ExternalSystemApiUtil.findParent(nodeToImport, ProjectKeys.PROJECT) ?: continue + val moduleData = nodeToImport.data + val module = modelsProvider.findIdeModule(moduleData) ?: continue + val androidModel = AndroidModuleModel.get(module) ?: continue + val variantName = androidModel.selectedVariant.name + val sourceSetInfo = nodeToImport.kotlinAndroidSourceSets?.firstOrNull { it.kotlinModule.name == variantName } ?: continue + val compilation = sourceSetInfo.kotlinModule as? KotlinCompilation ?: continue + val rootModel = modelsProvider.getModifiableRootModel(module) + for (sourceSet in compilation.sourceSets) { + if (sourceSet.isAndroid) { + val sourceType = if (sourceSet.isTestModule) JavaSourceRootType.TEST_SOURCE else JavaSourceRootType.SOURCE + val resourceType = if (sourceSet.isTestModule) JavaResourceRootType.TEST_RESOURCE else JavaResourceRootType.RESOURCE + sourceSet.sourceDirs.forEach { addSourceRoot(it, sourceType, rootModel) } + sourceSet.resourceDirs.forEach { addSourceRoot(it, resourceType, rootModel) } + } else { + val sourceSetId = sourceSetInfo.sourceSetIdsByName[sourceSet.name] ?: continue + val sourceSetData = ExternalSystemApiUtil.findFirstRecursively(projectNode) { + (it.data as? ModuleData)?.id == sourceSetId + }?.data as? ModuleData ?: continue + val sourceSetModule = modelsProvider.findIdeModule(sourceSetData) ?: continue + rootModel.addModuleOrderEntry(sourceSetModule) + } + } + } + } + + private fun addSourceRoot( + sourceRoot: File, + type: JpsModuleSourceRootType<*>, + rootModel: ModifiableRootModel + ) { + val parent = findParentContentEntry(sourceRoot, rootModel.contentEntries.stream()) ?: return + val url = FilePaths.pathToIdeaUrl(sourceRoot) + parent.addSourceFolder(url, type) + } +} \ No newline at end of file diff --git a/idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinAndroidGradleMPPModuleDataService.kt.173 b/idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinAndroidGradleMPPModuleDataService.kt.173 new file mode 100644 index 00000000000..078a9716f93 --- /dev/null +++ b/idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinAndroidGradleMPPModuleDataService.kt.173 @@ -0,0 +1,70 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.android.configure + +import com.android.tools.idea.gradle.project.model.AndroidModuleModel +import com.android.tools.idea.gradle.util.FilePaths +import com.intellij.openapi.externalSystem.model.DataNode +import com.intellij.openapi.externalSystem.model.ProjectKeys +import com.intellij.openapi.externalSystem.model.project.ModuleData +import com.intellij.openapi.externalSystem.model.project.ProjectData +import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProvider +import com.intellij.openapi.externalSystem.service.project.manage.AbstractProjectDataService +import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ModifiableRootModel +import org.jetbrains.jps.model.java.JavaResourceRootType +import org.jetbrains.jps.model.java.JavaSourceRootType +import org.jetbrains.jps.model.module.JpsModuleSourceRootType +import org.jetbrains.kotlin.gradle.KotlinCompilation +import java.io.File + +class KotlinAndroidGradleMPPModuleDataService : AbstractProjectDataService() { + override fun getTargetDataKey() = ProjectKeys.MODULE + + override fun postProcess( + toImport: MutableCollection>, + projectData: ProjectData?, + project: Project, + modelsProvider: IdeModifiableModelsProvider + ) { + for (nodeToImport in toImport) { + val projectNode = ExternalSystemApiUtil.findParent(nodeToImport, ProjectKeys.PROJECT) ?: continue + val moduleData = nodeToImport.data + val module = modelsProvider.findIdeModule(moduleData) ?: continue + val androidModel = AndroidModuleModel.get(module) ?: continue + val variantName = androidModel.selectedVariant.name + val sourceSetInfo = nodeToImport.kotlinAndroidSourceSets?.firstOrNull { it.kotlinModule.name == variantName } ?: continue + val compilation = sourceSetInfo.kotlinModule as? KotlinCompilation ?: continue + val rootModel = modelsProvider.getModifiableRootModel(module) + for (sourceSet in compilation.sourceSets) { + if (sourceSet.isAndroid) { + val sourceType = if (sourceSet.isTestModule) JavaSourceRootType.TEST_SOURCE else JavaSourceRootType.SOURCE + val resourceType = if (sourceSet.isTestModule) JavaResourceRootType.TEST_RESOURCE else JavaResourceRootType.RESOURCE + sourceSet.sourceDirs.forEach { addSourceRoot(it, sourceType, rootModel) } + sourceSet.resourceDirs.forEach { addSourceRoot(it, resourceType, rootModel) } + } else { + val sourceSetId = sourceSetInfo.sourceSetIdsByName[sourceSet.name] ?: continue + val sourceSetData = ExternalSystemApiUtil.findFirstRecursively(projectNode) { + (it.data as? ModuleData)?.id == sourceSetId + }?.data as? ModuleData ?: continue + val sourceSetModule = modelsProvider.findIdeModule(sourceSetData) ?: continue + rootModel.addModuleOrderEntry(sourceSetModule) + } + } + } + } + + private fun addSourceRoot( + sourceRoot: File, + type: JpsModuleSourceRootType<*>, + rootModel: ModifiableRootModel + ) { + val parent = FilePaths.findParentContentEntry(sourceRoot, rootModel.contentEntries) ?: return + val url = FilePaths.pathToIdeaUrl(sourceRoot) + parent.addSourceFolder(url, type) + } +} \ No newline at end of file diff --git a/idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinAndroidGradleMPPModuleDataService.kt.181 b/idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinAndroidGradleMPPModuleDataService.kt.181 new file mode 100644 index 00000000000..4a2d50a10a1 --- /dev/null +++ b/idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinAndroidGradleMPPModuleDataService.kt.181 @@ -0,0 +1,72 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.android.configure + +import com.android.tools.idea.gradle.project.model.AndroidModuleModel +import com.android.tools.idea.gradle.util.ContentEntries.findParentContentEntry +import com.android.tools.idea.gradle.util.FilePaths +import com.intellij.openapi.externalSystem.model.DataNode +import com.intellij.openapi.externalSystem.model.ProjectKeys +import com.intellij.openapi.externalSystem.model.project.ModuleData +import com.intellij.openapi.externalSystem.model.project.ProjectData +import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProvider +import com.intellij.openapi.externalSystem.service.project.manage.AbstractProjectDataService +import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ModifiableRootModel +import com.intellij.util.containers.stream +import org.jetbrains.jps.model.java.JavaResourceRootType +import org.jetbrains.jps.model.java.JavaSourceRootType +import org.jetbrains.jps.model.module.JpsModuleSourceRootType +import org.jetbrains.kotlin.gradle.KotlinCompilation +import java.io.File + +class KotlinAndroidGradleMPPModuleDataService : AbstractProjectDataService() { + override fun getTargetDataKey() = ProjectKeys.MODULE + + override fun postProcess( + toImport: MutableCollection>, + projectData: ProjectData?, + project: Project, + modelsProvider: IdeModifiableModelsProvider + ) { + for (nodeToImport in toImport) { + val projectNode = ExternalSystemApiUtil.findParent(nodeToImport, ProjectKeys.PROJECT) ?: continue + val moduleData = nodeToImport.data + val module = modelsProvider.findIdeModule(moduleData) ?: continue + val androidModel = AndroidModuleModel.get(module) ?: continue + val variantName = androidModel.selectedVariant.name + val sourceSetInfo = nodeToImport.kotlinAndroidSourceSets?.firstOrNull { it.kotlinModule.name == variantName } ?: continue + val compilation = sourceSetInfo.kotlinModule as? KotlinCompilation ?: continue + val rootModel = modelsProvider.getModifiableRootModel(module) + for (sourceSet in compilation.sourceSets) { + if (sourceSet.isAndroid) { + val sourceType = if (sourceSet.isTestModule) JavaSourceRootType.TEST_SOURCE else JavaSourceRootType.SOURCE + val resourceType = if (sourceSet.isTestModule) JavaResourceRootType.TEST_RESOURCE else JavaResourceRootType.RESOURCE + sourceSet.sourceDirs.forEach { addSourceRoot(it, sourceType, rootModel) } + sourceSet.resourceDirs.forEach { addSourceRoot(it, resourceType, rootModel) } + } else { + val sourceSetId = sourceSetInfo.sourceSetIdsByName[sourceSet.name] ?: continue + val sourceSetData = ExternalSystemApiUtil.findFirstRecursively(projectNode) { + (it.data as? ModuleData)?.id == sourceSetId + }?.data as? ModuleData ?: continue + val sourceSetModule = modelsProvider.findIdeModule(sourceSetData) ?: continue + rootModel.addModuleOrderEntry(sourceSetModule) + } + } + } + } + + private fun addSourceRoot( + sourceRoot: File, + type: JpsModuleSourceRootType<*>, + rootModel: ModifiableRootModel + ) { + val parent = findParentContentEntry(sourceRoot, rootModel.contentEntries.stream()) ?: return + val url = FilePaths.pathToIdeaUrl(sourceRoot) + parent.addSourceFolder(url, type) + } +} \ No newline at end of file diff --git a/idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinAndroidGradleMPPModuleDataService.kt.as31 b/idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinAndroidGradleMPPModuleDataService.kt.as31 new file mode 100644 index 00000000000..cc8cb73c64c --- /dev/null +++ b/idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinAndroidGradleMPPModuleDataService.kt.as31 @@ -0,0 +1,72 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.android.configure + +import com.android.tools.idea.gradle.project.model.AndroidModuleModel +import com.android.tools.idea.gradle.util.ContentEntries.findParentContentEntry +import com.android.tools.idea.io.FilePaths +import com.intellij.openapi.externalSystem.model.DataNode +import com.intellij.openapi.externalSystem.model.ProjectKeys +import com.intellij.openapi.externalSystem.model.project.ModuleData +import com.intellij.openapi.externalSystem.model.project.ProjectData +import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProvider +import com.intellij.openapi.externalSystem.service.project.manage.AbstractProjectDataService +import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ModifiableRootModel +import com.intellij.util.containers.stream +import org.jetbrains.jps.model.java.JavaResourceRootType +import org.jetbrains.jps.model.java.JavaSourceRootType +import org.jetbrains.jps.model.module.JpsModuleSourceRootType +import org.jetbrains.kotlin.gradle.KotlinCompilation +import java.io.File + +class KotlinAndroidGradleMPPModuleDataService : AbstractProjectDataService() { + override fun getTargetDataKey() = ProjectKeys.MODULE + + override fun postProcess( + toImport: MutableCollection>, + projectData: ProjectData?, + project: Project, + modelsProvider: IdeModifiableModelsProvider + ) { + for (nodeToImport in toImport) { + val projectNode = ExternalSystemApiUtil.findParent(nodeToImport, ProjectKeys.PROJECT) ?: continue + val moduleData = nodeToImport.data + val module = modelsProvider.findIdeModule(moduleData) ?: continue + val androidModel = AndroidModuleModel.get(module) ?: continue + val variantName = androidModel.selectedVariant.name + val sourceSetInfo = nodeToImport.kotlinAndroidSourceSets?.firstOrNull { it.kotlinModule.name == variantName } ?: continue + val compilation = sourceSetInfo.kotlinModule as? KotlinCompilation ?: continue + val rootModel = modelsProvider.getModifiableRootModel(module) + for (sourceSet in compilation.sourceSets) { + if (sourceSet.isAndroid) { + val sourceType = if (sourceSet.isTestModule) JavaSourceRootType.TEST_SOURCE else JavaSourceRootType.SOURCE + val resourceType = if (sourceSet.isTestModule) JavaResourceRootType.TEST_RESOURCE else JavaResourceRootType.RESOURCE + sourceSet.sourceDirs.forEach { addSourceRoot(it, sourceType, rootModel) } + sourceSet.resourceDirs.forEach { addSourceRoot(it, resourceType, rootModel) } + } else { + val sourceSetId = sourceSetInfo.sourceSetIdsByName[sourceSet.name] ?: continue + val sourceSetData = ExternalSystemApiUtil.findFirstRecursively(projectNode) { + (it.data as? ModuleData)?.id == sourceSetId + }?.data as? ModuleData ?: continue + val sourceSetModule = modelsProvider.findIdeModule(sourceSetData) ?: continue + rootModel.addModuleOrderEntry(sourceSetModule) + } + } + } + } + + private fun addSourceRoot( + sourceRoot: File, + type: JpsModuleSourceRootType<*>, + rootModel: ModifiableRootModel + ) { + val parent = findParentContentEntry(sourceRoot, rootModel.contentEntries.stream()) ?: return + val url = FilePaths.pathToIdeaUrl(sourceRoot) + parent.addSourceFolder(url, type) + } +} \ No newline at end of file diff --git a/idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinAndroidGradleMPPModuleDataService.kt.as32 b/idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinAndroidGradleMPPModuleDataService.kt.as32 new file mode 100644 index 00000000000..cc8cb73c64c --- /dev/null +++ b/idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinAndroidGradleMPPModuleDataService.kt.as32 @@ -0,0 +1,72 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.android.configure + +import com.android.tools.idea.gradle.project.model.AndroidModuleModel +import com.android.tools.idea.gradle.util.ContentEntries.findParentContentEntry +import com.android.tools.idea.io.FilePaths +import com.intellij.openapi.externalSystem.model.DataNode +import com.intellij.openapi.externalSystem.model.ProjectKeys +import com.intellij.openapi.externalSystem.model.project.ModuleData +import com.intellij.openapi.externalSystem.model.project.ProjectData +import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProvider +import com.intellij.openapi.externalSystem.service.project.manage.AbstractProjectDataService +import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.ModifiableRootModel +import com.intellij.util.containers.stream +import org.jetbrains.jps.model.java.JavaResourceRootType +import org.jetbrains.jps.model.java.JavaSourceRootType +import org.jetbrains.jps.model.module.JpsModuleSourceRootType +import org.jetbrains.kotlin.gradle.KotlinCompilation +import java.io.File + +class KotlinAndroidGradleMPPModuleDataService : AbstractProjectDataService() { + override fun getTargetDataKey() = ProjectKeys.MODULE + + override fun postProcess( + toImport: MutableCollection>, + projectData: ProjectData?, + project: Project, + modelsProvider: IdeModifiableModelsProvider + ) { + for (nodeToImport in toImport) { + val projectNode = ExternalSystemApiUtil.findParent(nodeToImport, ProjectKeys.PROJECT) ?: continue + val moduleData = nodeToImport.data + val module = modelsProvider.findIdeModule(moduleData) ?: continue + val androidModel = AndroidModuleModel.get(module) ?: continue + val variantName = androidModel.selectedVariant.name + val sourceSetInfo = nodeToImport.kotlinAndroidSourceSets?.firstOrNull { it.kotlinModule.name == variantName } ?: continue + val compilation = sourceSetInfo.kotlinModule as? KotlinCompilation ?: continue + val rootModel = modelsProvider.getModifiableRootModel(module) + for (sourceSet in compilation.sourceSets) { + if (sourceSet.isAndroid) { + val sourceType = if (sourceSet.isTestModule) JavaSourceRootType.TEST_SOURCE else JavaSourceRootType.SOURCE + val resourceType = if (sourceSet.isTestModule) JavaResourceRootType.TEST_RESOURCE else JavaResourceRootType.RESOURCE + sourceSet.sourceDirs.forEach { addSourceRoot(it, sourceType, rootModel) } + sourceSet.resourceDirs.forEach { addSourceRoot(it, resourceType, rootModel) } + } else { + val sourceSetId = sourceSetInfo.sourceSetIdsByName[sourceSet.name] ?: continue + val sourceSetData = ExternalSystemApiUtil.findFirstRecursively(projectNode) { + (it.data as? ModuleData)?.id == sourceSetId + }?.data as? ModuleData ?: continue + val sourceSetModule = modelsProvider.findIdeModule(sourceSetData) ?: continue + rootModel.addModuleOrderEntry(sourceSetModule) + } + } + } + } + + private fun addSourceRoot( + sourceRoot: File, + type: JpsModuleSourceRootType<*>, + rootModel: ModifiableRootModel + ) { + val parent = findParentContentEntry(sourceRoot, rootModel.contentEntries.stream()) ?: return + val url = FilePaths.pathToIdeaUrl(sourceRoot) + parent.addSourceFolder(url, type) + } +} \ No newline at end of file diff --git a/idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinAndroidMPPGradleProjectResolver.kt b/idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinAndroidMPPGradleProjectResolver.kt new file mode 100644 index 00000000000..ca570f91ad8 --- /dev/null +++ b/idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinAndroidMPPGradleProjectResolver.kt @@ -0,0 +1,81 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.android.configure + +import com.android.builder.model.AndroidProject +import com.android.builder.model.NativeAndroidProject +import com.intellij.openapi.externalSystem.model.DataNode +import com.intellij.openapi.externalSystem.model.project.ModuleData +import com.intellij.openapi.externalSystem.model.project.ProjectData +import com.intellij.openapi.externalSystem.util.ExternalSystemConstants +import com.intellij.openapi.externalSystem.util.Order +import com.intellij.openapi.util.Key +import org.gradle.tooling.model.idea.IdeaModule +import org.jetbrains.kotlin.gradle.KotlinMPPGradleModel +import org.jetbrains.kotlin.gradle.KotlinMPPGradleModelBuilder +import org.jetbrains.kotlin.idea.configuration.KotlinMPPGradleProjectResolver +import org.jetbrains.kotlin.idea.configuration.KotlinSourceSetInfo +import org.jetbrains.kotlin.idea.util.CopyableDataNodeUserDataProperty +import org.jetbrains.plugins.gradle.service.project.AbstractProjectResolverExtension + +var DataNode.kotlinAndroidSourceSets: List? + by CopyableDataNodeUserDataProperty(Key.create("ANDROID_COMPILATIONS")) + +@Order(ExternalSystemConstants.UNORDERED - 1) +class KotlinAndroidMPPGradleProjectResolver : AbstractProjectResolverExtension() { + private val isAndroidProject by lazy { + resolverCtx.hasModulesWithModel(AndroidProject::class.java) + || resolverCtx.hasModulesWithModel(NativeAndroidProject::class.java) + } + + override fun getToolingExtensionsClasses(): Set> { + return setOf(KotlinMPPGradleModelBuilder::class.java, Unit::class.java) + } + + override fun getExtraProjectModelClasses(): Set> { + return setOf(KotlinMPPGradleModel::class.java) + } + + override fun createModule(gradleModule: IdeaModule, projectDataNode: DataNode): DataNode { + return super.createModule(gradleModule, projectDataNode).also { + initializeModuleData(gradleModule, it, projectDataNode) + } + } + + override fun populateModuleContentRoots(gradleModule: IdeaModule, ideModule: DataNode) { + super.populateModuleContentRoots(gradleModule, ideModule) + if (isAndroidProject) { + KotlinMPPGradleProjectResolver.populateContentRoots(gradleModule, ideModule, resolverCtx) + } + } + + override fun populateModuleDependencies(gradleModule: IdeaModule, ideModule: DataNode, ideProject: DataNode) { + super.populateModuleDependencies(gradleModule, ideModule, ideProject) + if (isAndroidProject) { + KotlinMPPGradleProjectResolver.populateModuleDependencies(gradleModule, ideProject, ideModule, resolverCtx) + } + } + + private fun initializeModuleData( + gradleModule: IdeaModule, + mainModuleData: DataNode, + projectDataNode: DataNode + ) { + if (!isAndroidProject) return + + KotlinMPPGradleProjectResolver.initializeModuleData(gradleModule, mainModuleData, projectDataNode, resolverCtx) + + val mppModel = resolverCtx.getExtraProject(gradleModule, KotlinMPPGradleModel::class.java) ?: return + + mainModuleData.kotlinAndroidSourceSets = mppModel + .targets + .asSequence() + .flatMap { it.compilations.asSequence() } + .filter { it.isAndroid } + .map { KotlinMPPGradleProjectResolver.createSourceSetInfo(it, gradleModule, resolverCtx) } + .toList() + } +} \ No newline at end of file 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 e242d125e1e..261cc38ec17 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 @@ -30,6 +30,7 @@ import org.gradle.tooling.model.idea.IdeaModule import org.jetbrains.kotlin.gradle.CompilerArgumentsBySourceSet import org.jetbrains.kotlin.gradle.KotlinGradleModel import org.jetbrains.kotlin.gradle.KotlinGradleModelBuilder +import org.jetbrains.kotlin.gradle.KotlinMPPGradleModel import org.jetbrains.kotlin.idea.inspections.gradle.getDependencyModules import org.jetbrains.kotlin.idea.util.CopyableDataNodeUserDataProperty import org.jetbrains.kotlin.idea.util.NotNullableCopyableDataNodeUserDataProperty @@ -78,6 +79,10 @@ class KotlinGradleProjectResolverExtension : AbstractProjectResolverExtension() ideModule: DataNode, ideProject: DataNode ) { + if (resolverCtx.getExtraProject(gradleModule, KotlinMPPGradleModel::class.java) != null) { + return super.populateModuleDependencies(gradleModule, ideModule, ideProject) + } + val outputToSourceSet = ideProject.getUserData(GradleProjectResolver.MODULES_OUTPUTS) val sourceSetByName = ideProject.getUserData(GradleProjectResolver.RESOLVED_SOURCE_SETS) 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 eca178f67de..6bc422f1304 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 @@ -200,6 +200,7 @@ fun configureFacetByGradleModule( sourceSetNode: DataNode?, sourceSetName: String? = sourceSetNode?.data?.id?.let { it.substring(it.lastIndexOf(':') + 1) } ): KotlinFacet? { + if (moduleNode.kotlinSourceSet != null) return null // Suppress in the presence of new MPP model if (!moduleNode.isResolved) return null if (!moduleNode.hasKotlinPlugin) { @@ -263,7 +264,7 @@ private fun getExplicitOutputPath(moduleNode: DataNode, platformKind return K2JSCompilerArguments().apply { parseCommandLineArguments(k2jsArgumentList, this) }.outputFile } -private fun adjustClasspath(kotlinFacet: KotlinFacet, dependencyClasspath: List) { +internal fun adjustClasspath(kotlinFacet: KotlinFacet, dependencyClasspath: List) { if (dependencyClasspath.isEmpty()) return val arguments = kotlinFacet.configuration.settings.compilerArguments as? K2JVMCompilerArguments ?: return val fullClasspath = arguments.classpath?.split(File.pathSeparator) ?: emptyList() @@ -274,7 +275,7 @@ private fun adjustClasspath(kotlinFacet: KotlinFacet, dependencyClasspath: List< private val gradlePropertyFiles = listOf("local.properties", "gradle.properties") -private fun findKotlinCoroutinesProperty(project: Project): String { +internal fun findKotlinCoroutinesProperty(project: Project): String { for (propertyFileName in gradlePropertyFiles) { val propertyFile = project.baseDir.findChild(propertyFileName) ?: continue val properties = Properties() diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPDataNodes.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPDataNodes.kt new file mode 100644 index 00000000000..b414ec1a4ba --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPDataNodes.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.idea.configuration + +import com.intellij.openapi.externalSystem.model.DataNode +import com.intellij.openapi.externalSystem.model.ProjectKeys +import com.intellij.openapi.externalSystem.model.project.AbstractNamedData +import com.intellij.openapi.externalSystem.model.project.ModuleData +import com.intellij.openapi.util.Key +import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments +import org.jetbrains.kotlin.gradle.KotlinModule +import org.jetbrains.kotlin.gradle.KotlinPlatform +import org.jetbrains.kotlin.gradle.KotlinSourceSet +import org.jetbrains.kotlin.idea.util.CopyableDataNodeUserDataProperty +import org.jetbrains.plugins.gradle.util.GradleConstants +import java.io.File +import com.intellij.openapi.externalSystem.model.Key as ExternalKey + +var DataNode.kotlinSourceSet: KotlinSourceSetInfo? + by CopyableDataNodeUserDataProperty(Key.create("KOTLIN_SOURCE_SET")) + +var DataNode.kotlinTargetDataNode: DataNode? + by CopyableDataNodeUserDataProperty(Key.create("KOTLIN_TARGET_DATA_NODE")) + +class KotlinSourceSetInfo(val kotlinModule: KotlinModule) { + var moduleId: String? = null + var platform: KotlinPlatform = KotlinPlatform.COMMON + var defaultCompilerArguments: CommonCompilerArguments? = null + var compilerArguments: CommonCompilerArguments? = null + var dependencyClasspath: List = emptyList() + var isTestModule: Boolean = false + var sourceSetIdsByName: Map = emptyMap() +} + +class KotlinTargetData(name: String) : AbstractNamedData(GradleConstants.SYSTEM_ID, name) { + var moduleIds: Set = emptySet() + var archiveFile: File? = null + + companion object { + val KEY = ExternalKey.create(KotlinTargetData::class.java, ProjectKeys.MODULE.processingWeight + 1) + } +} \ No newline at end of file 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 new file mode 100644 index 00000000000..6dd722238dc --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinMPPGradleProjectResolver.kt @@ -0,0 +1,610 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.idea.configuration + +import com.intellij.openapi.externalSystem.model.DataNode +import com.intellij.openapi.externalSystem.model.ProjectKeys +import com.intellij.openapi.externalSystem.model.project.* +import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil +import com.intellij.openapi.externalSystem.util.ExternalSystemConstants +import com.intellij.openapi.externalSystem.util.Order +import com.intellij.openapi.roots.DependencyScope +import com.intellij.openapi.util.Pair +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.util.text.StringUtil +import com.intellij.util.PathUtilRt +import com.intellij.util.SmartList +import com.intellij.util.containers.MultiMap +import com.intellij.util.text.VersionComparatorUtil +import org.gradle.tooling.model.UnsupportedMethodException +import org.gradle.tooling.model.idea.IdeaContentRoot +import org.gradle.tooling.model.idea.IdeaModule +import org.jetbrains.kotlin.cli.common.arguments.* +import org.jetbrains.kotlin.gradle.* +import org.jetbrains.plugins.gradle.model.* +import org.jetbrains.plugins.gradle.model.data.GradleSourceSetData +import org.jetbrains.plugins.gradle.service.project.AbstractProjectResolverExtension +import org.jetbrains.plugins.gradle.service.project.GradleProjectResolver +import org.jetbrains.plugins.gradle.service.project.GradleProjectResolver.CONFIGURATION_ARTIFACTS +import org.jetbrains.plugins.gradle.service.project.GradleProjectResolver.MODULES_OUTPUTS +import org.jetbrains.plugins.gradle.service.project.GradleProjectResolverUtil.buildDependencies +import org.jetbrains.plugins.gradle.service.project.GradleProjectResolverUtil.getModuleId +import org.jetbrains.plugins.gradle.service.project.ProjectResolverContext +import org.jetbrains.plugins.gradle.util.GradleConstants +import java.io.File + +@Order(ExternalSystemConstants.UNORDERED + 1) +open class KotlinMPPGradleProjectResolver : AbstractProjectResolverExtension() { + override fun getToolingExtensionsClasses(): Set> { + return setOf(KotlinMPPGradleModelBuilder::class.java, Unit::class.java) + } + + override fun getExtraProjectModelClasses(): Set> { + return setOf(KotlinMPPGradleModel::class.java) + } + + override fun createModule(gradleModule: IdeaModule, projectDataNode: DataNode): DataNode { + return super.createModule(gradleModule, projectDataNode).also { + initializeModuleData(gradleModule, it, projectDataNode, resolverCtx) + } + } + + override fun populateModuleContentRoots(gradleModule: IdeaModule, ideModule: DataNode) { + if (resolverCtx.getExtraProject(gradleModule, KotlinMPPGradleModel::class.java) == null) { + return super.populateModuleContentRoots(gradleModule, ideModule) + } + populateContentRoots(gradleModule, ideModule, resolverCtx) + } + + override fun populateModuleCompileOutputSettings(gradleModule: IdeaModule, ideModule: DataNode) { + if (resolverCtx.getExtraProject(gradleModule, KotlinMPPGradleModel::class.java) == null) { + super.populateModuleCompileOutputSettings(gradleModule, ideModule) + } + + val mppModel = resolverCtx.getExtraProject(gradleModule, KotlinMPPGradleModel::class.java) ?: return + val ideaOutDir = File(ideModule.data.linkedExternalProjectPath, "out") + val projectDataNode = ideModule.getDataNode(ProjectKeys.PROJECT)!! + val moduleOutputsMap = projectDataNode.getUserData(MODULES_OUTPUTS)!! + val outputDirs = HashSet() + processCompilations(gradleModule, mppModel, ideModule, resolverCtx) { dataNode, compilation -> + var gradleOutputMap = dataNode.getUserData(GradleProjectResolver.GRADLE_OUTPUTS) + if (gradleOutputMap == null) { + gradleOutputMap = MultiMap.create() + dataNode.putUserData(GradleProjectResolver.GRADLE_OUTPUTS, gradleOutputMap) + } + + val moduleData = dataNode.data + + with(compilation.output) { + effectiveClassesDir?.let { + moduleData.isInheritProjectCompileOutputPath = false + moduleData.setCompileOutputPath(compilation.sourceType, it.absolutePath) + for (gradleOutputDir in classesDirs) { + recordOutputDir(gradleOutputDir, it, compilation.sourceType, moduleData, moduleOutputsMap, gradleOutputMap) + } + } + resourcesDir?.let { + moduleData.setCompileOutputPath(compilation.resourceType, it.absolutePath) + recordOutputDir(it, it, compilation.resourceType, moduleData, moduleOutputsMap, gradleOutputMap) + } + } + } + if (outputDirs.any { FileUtil.isAncestor(ideaOutDir, File(it), false) }) { + excludeOutDir(ideModule, ideaOutDir) + } + } + + override fun populateModuleDependencies(gradleModule: IdeaModule, ideModule: DataNode, ideProject: DataNode) { + if (resolverCtx.getExtraProject(gradleModule, KotlinMPPGradleModel::class.java) == null) { + super.populateModuleDependencies(gradleModule, ideModule, ideProject) + } + populateModuleDependencies(gradleModule, ideProject, ideModule, resolverCtx) + } + + private fun recordOutputDir( + gradleOutputDir: File, + effectiveOutputDir: File, + sourceType: ExternalSystemSourceType, + moduleData: GradleSourceSetData, + moduleOutputsMap: MutableMap>, + gradleOutputMap: MultiMap + ) { + val gradleOutputPath = ExternalSystemApiUtil.toCanonicalPath(gradleOutputDir.absolutePath) + gradleOutputMap.putValue(sourceType, gradleOutputPath) + if (gradleOutputDir.path != effectiveOutputDir.path) { + moduleOutputsMap[gradleOutputPath] = Pair(moduleData.id, sourceType) + } + } + + private fun excludeOutDir(ideModule: DataNode, ideaOutDir: File) { + val contentRootDataDataNode = ExternalSystemApiUtil.find(ideModule, ProjectKeys.CONTENT_ROOT) + + val excludedContentRootData: ContentRootData + if (contentRootDataDataNode == null || !FileUtil.isAncestor(File(contentRootDataDataNode.data.rootPath), ideaOutDir, false)) { + excludedContentRootData = ContentRootData(GradleConstants.SYSTEM_ID, ideaOutDir.absolutePath) + ideModule.createChild(ProjectKeys.CONTENT_ROOT, excludedContentRootData) + } else { + excludedContentRootData = contentRootDataDataNode.data + } + + excludedContentRootData.storePath(ExternalSystemSourceType.EXCLUDED, ideaOutDir.absolutePath) + } + + companion object { + fun initializeModuleData( + gradleModule: IdeaModule, + mainModuleNode: DataNode, + projectDataNode: DataNode, + resolverCtx: ProjectResolverContext + ) { + val mainModuleData = mainModuleNode.data + val mainModuleConfigPath = mainModuleData.linkedExternalProjectPath + val mainModuleFileDirectoryPath = mainModuleData.moduleFileDirectoryPath + + val externalProject = resolverCtx.getExtraProject(gradleModule, ExternalProject::class.java) + val mppModel = resolverCtx.getExtraProject(gradleModule, KotlinMPPGradleModel::class.java) + if (mppModel == null || externalProject == null) return + + val jdkName = gradleModule.jdkNameIfAny + + val moduleGroup: Array? = if (!resolverCtx.isUseQualifiedModuleNames) { + val gradlePath = gradleModule.gradleProject.path + val isRootModule = gradlePath.isEmpty() || gradlePath == ":" + if (isRootModule) { + arrayOf(mainModuleData.internalName) + } else { + gradlePath.split(":").drop(1).toTypedArray() + } + } else null + + val sourceSetMap = projectDataNode.getUserData(GradleProjectResolver.RESOLVED_SOURCE_SETS)!! + + val sourceSetToCompilationData = LinkedHashMap>() + for (target in mppModel.targets) { + if (target.isAndroid) continue + val targetData = KotlinTargetData(target.name).also { + it.archiveFile = target.jar?.archiveFile + } + val targetDataNode = mainModuleNode.createChild(KotlinTargetData.KEY, targetData) + + val compilationIds = LinkedHashSet() + for (compilation in target.compilations) { + val moduleId = getKotlinModuleId(gradleModule, compilation, resolverCtx) + val existingSourceSetDataNode = sourceSetMap[moduleId]?.first + if (existingSourceSetDataNode?.kotlinSourceSet != null) continue + + compilationIds += moduleId + + val moduleExternalName = getExternalModuleName(gradleModule, compilation) + val moduleInternalName = getInternalModuleName(gradleModule, externalProject, compilation, resolverCtx) + + val compilationData = existingSourceSetDataNode?.data ?: GradleSourceSetData( + moduleId, moduleExternalName, moduleInternalName, mainModuleFileDirectoryPath, mainModuleConfigPath + ).also { + it.group = externalProject.group + it.version = externalProject.version + + when (compilation.name) { + KotlinCompilation.MAIN_COMPILATION_NAME -> { + it.publication = ProjectId(externalProject.group, externalProject.name, externalProject.version) + } + KotlinCompilation.TEST_COMPILATION_NAME -> { + it.productionModuleId = getInternalModuleName( + gradleModule, + externalProject, + compilation, + resolverCtx, + KotlinCompilation.MAIN_COMPILATION_NAME + ) + } + } + + it.ideModuleGroup = moduleGroup + it.sdkName = jdkName + } + + val kotlinSourceSet = createSourceSetInfo(compilation, gradleModule, resolverCtx) + + if (compilation.platform == KotlinPlatform.JVM) { + compilationData.targetCompatibility = (kotlinSourceSet.compilerArguments as? K2JVMCompilerArguments)?.jvmTarget + } + + for (sourceSet in compilation.sourceSets) { + sourceSetToCompilationData.getOrPut(sourceSet) { LinkedHashSet() } += compilationData + } + + val compilationDataNode = + (existingSourceSetDataNode ?: mainModuleNode.createChild(GradleSourceSetData.KEY, compilationData)).also { + it.kotlinSourceSet = kotlinSourceSet + it.kotlinTargetDataNode = targetDataNode + } + if (existingSourceSetDataNode == null) { + sourceSetMap[moduleId] = Pair(compilationDataNode, createExternalSourceSet(compilation, compilationData)) + } + } + + targetData.moduleIds = compilationIds + } + + for (sourceSet in mppModel.sourceSets.values) { + if (sourceSet.isAndroid) continue + val moduleId = getKotlinModuleId(gradleModule, sourceSet, resolverCtx) + val existingSourceSetDataNode = sourceSetMap[moduleId]?.first + if (existingSourceSetDataNode?.kotlinSourceSet != null) continue + + val moduleExternalName = getExternalModuleName(gradleModule, sourceSet) + val moduleInternalName = getInternalModuleName(gradleModule, externalProject, sourceSet, resolverCtx) + + val sourceSetData = existingSourceSetDataNode?.data ?: GradleSourceSetData( + moduleId, moduleExternalName, moduleInternalName, mainModuleFileDirectoryPath, mainModuleConfigPath + ).also { + it.group = externalProject.group + it.version = externalProject.version + + val name = sourceSet.name + val baseName = name.removeSuffix("Test") + if (baseName != name) { + it.productionModuleId = getInternalModuleName( + gradleModule, + externalProject, + sourceSet, + resolverCtx, + baseName + "Main" + ) + } + + it.ideModuleGroup = moduleGroup + it.sdkName = jdkName + it.targetCompatibility = sourceSetToCompilationData[sourceSet] + ?.mapNotNull { it.targetCompatibility } + ?.minWith(VersionComparatorUtil.COMPARATOR) + } + + val kotlinSourceSet = createSourceSetInfo(sourceSet, gradleModule, resolverCtx) + + val sourceSetDataNode = + (existingSourceSetDataNode ?: mainModuleNode.createChild(GradleSourceSetData.KEY, sourceSetData)).also { + it.kotlinSourceSet = kotlinSourceSet + } + if (existingSourceSetDataNode == null) { + sourceSetMap[moduleId] = Pair(sourceSetDataNode, createExternalSourceSet(sourceSet, sourceSetData)) + } + } + + with(projectDataNode.data) { + if (mainModuleData.linkedExternalProjectPath == linkedExternalProjectPath) { + group = mainModuleData.group + version = mainModuleData.version + } + } + + mainModuleNode.coroutines = mppModel.extraFeatures.coroutinesState + } + + fun populateContentRoots( + gradleModule: IdeaModule, + ideModule: DataNode, + resolverCtx: ProjectResolverContext + ) { + val mppModel = resolverCtx.getExtraProject(gradleModule, KotlinMPPGradleModel::class.java) ?: return + processSourceSets(gradleModule, mppModel, ideModule, resolverCtx) { dataNode, sourceSet -> + createContentRootData(sourceSet.sourceDirs, sourceSet.sourceType, dataNode) + createContentRootData(sourceSet.resourceDirs, sourceSet.resourceType, dataNode) + } + + for (gradleContentRoot in gradleModule.contentRoots ?: emptySet()) { + if (gradleContentRoot == null) continue + + val rootDirectory = gradleContentRoot.rootDirectory ?: continue + val ideContentRoot = ContentRootData(GradleConstants.SYSTEM_ID, rootDirectory.absolutePath).also { ideContentRoot -> + (gradleContentRoot.excludeDirectories ?: emptySet()).forEach { file -> + ideContentRoot.storePath(ExternalSystemSourceType.EXCLUDED, file.absolutePath) + } + } + ideModule.createChild(ProjectKeys.CONTENT_ROOT, ideContentRoot) + } + } + + fun populateModuleDependencies( + gradleModule: IdeaModule, + ideProject: DataNode, + ideModule: DataNode, + resolverCtx: ProjectResolverContext + ) { + val mppModel = resolverCtx.getExtraProject(gradleModule, KotlinMPPGradleModel::class.java) ?: return + val sourceSetMap = ideProject.getUserData(GradleProjectResolver.RESOLVED_SOURCE_SETS) ?: return + val artifactsMap = ideProject.getUserData(CONFIGURATION_ARTIFACTS) ?: return + val processedModuleIds = HashSet() + processCompilations(gradleModule, mppModel, ideModule, resolverCtx) { dataNode, compilation -> + if (processedModuleIds.add(getKotlinModuleId(gradleModule, compilation, resolverCtx))) { + buildDependencies( + resolverCtx, + sourceSetMap, + artifactsMap, + dataNode, + preprocessDependencies(compilation, ideProject), + ideProject + ) + for (sourceSet in compilation.sourceSets) { + if (sourceSet.fullName() == compilation.fullName()) continue + addDependency(dataNode, sourceSet, gradleModule, ideModule, resolverCtx) + } + } + } + processSourceSets(gradleModule, mppModel, ideModule, resolverCtx) { dataNode, sourceSet -> + dataNode.data.productionModuleId?.let { + val productionModuleDataNode = ideModule.findChildModuleByInternalName(it) + if (productionModuleDataNode != null) { + addDependency(dataNode, productionModuleDataNode) + } + } + if (processedModuleIds.add(getKotlinModuleId(gradleModule, sourceSet, resolverCtx))) { + buildDependencies( + resolverCtx, + sourceSetMap, + artifactsMap, + dataNode, + preprocessDependencies(sourceSet, ideProject), + ideProject + ) + } + for (targetSourceSetName in sourceSet.dependsOnSourceSets) { + val targetSourceSet = mppModel.sourceSets[targetSourceSetName] ?: continue + addDependency(dataNode, targetSourceSet, gradleModule, ideModule, resolverCtx) + } + } + } + + private fun preprocessDependencies( + kotlinModule: KotlinModule, + ideProject: DataNode + ): List { + return kotlinModule.dependencies + .groupBy { it.id } + .mapValues { it.value.firstOrNull { it.scope == "COMPILE" } ?: it.value.lastOrNull() } + .values + .filterNotNull() + .map { adjustDependency(it, ideProject) } + } + + private fun adjustDependency( + dependency: ExternalDependency, + ideProject: DataNode + ): ExternalDependency { + if (dependency !is ExternalProjectDependency) return dependency + val projectPath = dependency.projectPath + val classifier = dependency.classifier ?: return dependency + val targetModuleNode = ExternalSystemApiUtil.findFirstRecursively(ideProject) { + (it.data as? ModuleData)?.id == projectPath + } ?: return dependency + val targetCompilation = ExternalSystemApiUtil + .getChildren(targetModuleNode, GradleSourceSetData.KEY) + .mapNotNull { sourceSetNode -> + sourceSetNode.kotlinSourceSet?.kotlinModule as? KotlinCompilation + } + .firstOrNull { + it.name == KotlinCompilation.MAIN_COMPILATION_NAME && it.target.disambiguationClassifier == classifier + } ?: return dependency + return DefaultExternalProjectDependency(dependency).also { + it.configurationName = targetCompilation.fullName() + } + } + + private fun addDependency(fromModule: DataNode<*>, toModule: DataNode<*>) { + val fromData = fromModule.data as? ModuleData ?: return + val toData = toModule.data as? ModuleData ?: return + val moduleDependencyData = ModuleDependencyData(fromData, toData).also { + it.scope = DependencyScope.COMPILE + it.isExported = false + } + fromModule.createChild(ProjectKeys.MODULE_DEPENDENCY, moduleDependencyData) + } + + private fun addDependency( + fromModule: DataNode<*>, + toModule: KotlinModule, + gradleModule: IdeaModule, + ideModule: DataNode, + resolverCtx: ProjectResolverContext + ) { + val usedModuleId = getKotlinModuleId(gradleModule, toModule, resolverCtx) + val usedModuleDataNode = ideModule.findChildModuleById(usedModuleId) ?: return + addDependency(fromModule, usedModuleDataNode) + } + + private fun createContentRootData(sourceDirs: Set, sourceType: ExternalSystemSourceType, parentNode: DataNode<*>) { + for (sourceDir in sourceDirs) { + val contentRootData = ContentRootData(GradleConstants.SYSTEM_ID, sourceDir.absolutePath) + contentRootData.storePath(sourceType, sourceDir.absolutePath) + parentNode.createChild(ProjectKeys.CONTENT_ROOT, contentRootData) + } + } + + private fun processSourceSets( + gradleModule: IdeaModule, + mppModel: KotlinMPPGradleModel, + ideModule: DataNode, + resolverCtx: ProjectResolverContext, + processor: (DataNode, KotlinSourceSet) -> Unit + ) { + val sourceSetsMap = HashMap>() + for (dataNode in ExternalSystemApiUtil.findAll(ideModule, GradleSourceSetData.KEY)) { + if (dataNode.kotlinSourceSet != null) { + sourceSetsMap[dataNode.data.id] = dataNode + } + } + for (sourceSet in mppModel.sourceSets.values) { + if (sourceSet.isAndroid) continue + val moduleId = getKotlinModuleId(gradleModule, sourceSet, resolverCtx) + val moduleDataNode = sourceSetsMap[moduleId] ?: continue + processor(moduleDataNode, sourceSet) + } + } + + private fun processCompilations( + gradleModule: IdeaModule, + mppModel: KotlinMPPGradleModel, + ideModule: DataNode, + resolverCtx: ProjectResolverContext, + processor: (DataNode, KotlinCompilation) -> Unit + ) { + val sourceSetsMap = HashMap>() + for (dataNode in ExternalSystemApiUtil.findAll(ideModule, GradleSourceSetData.KEY)) { + if (dataNode.kotlinSourceSet != null) { + sourceSetsMap[dataNode.data.id] = dataNode + } + } + for (target in mppModel.targets) { + if (target.isAndroid) continue + for (compilation in target.compilations) { + val moduleId = getKotlinModuleId(gradleModule, compilation, resolverCtx) + val moduleDataNode = sourceSetsMap[moduleId] ?: continue + processor(moduleDataNode, compilation) + } + } + } + + private val IdeaModule.jdkNameIfAny + get() = try { + jdkName + } catch (e: UnsupportedMethodException) { + null + } + + private fun getExternalModuleName(gradleModule: IdeaModule, kotlinModule: KotlinModule) = + gradleModule.name + ":" + kotlinModule.fullName() + + private fun getInternalModuleName( + gradleModule: IdeaModule, + externalProject: ExternalProject, + kotlinModule: KotlinModule, + resolverCtx: ProjectResolverContext, + actualName: String = kotlinModule.name + ): String { + val delimiter: String + val moduleName = StringBuilder() + if (resolverCtx.isUseQualifiedModuleNames) { + delimiter = "." + if (StringUtil.isNotEmpty(externalProject.group)) { + moduleName.append(externalProject.group).append(delimiter) + } + moduleName.append(externalProject.name) + } else { + delimiter = "_" + moduleName.append(gradleModule.name) + } + moduleName.append(delimiter) + moduleName.append(kotlinModule.fullName(actualName)) + return PathUtilRt.suggestFileName(moduleName.toString(), true, false) + } + + private fun createExternalSourceSet(compilation: KotlinCompilation, compilationData: GradleSourceSetData): ExternalSourceSet { + return DefaultExternalSourceSet().also { sourceSet -> + val effectiveClassesDir = compilation.output.effectiveClassesDir + val resourcesDir = compilation.output.resourcesDir + + sourceSet.name = compilation.fullName() + sourceSet.targetCompatibility = compilationData.targetCompatibility + sourceSet.dependencies += compilation.dependencies + val sourcesWithTypes = SmartList>() + if (effectiveClassesDir != null) { + sourcesWithTypes += compilation.sourceType to DefaultExternalSourceDirectorySet().also { dirSet -> + dirSet.outputDir = effectiveClassesDir + dirSet.srcDirs = compilation.sourceSets.flatMapTo(LinkedHashSet()) { it.sourceDirs } + dirSet.gradleOutputDirs += compilation.output.classesDirs + dirSet.setInheritedCompilerOutput(false) + } + } + if (resourcesDir != null) { + sourcesWithTypes += compilation.resourceType to DefaultExternalSourceDirectorySet().also { dirSet -> + dirSet.outputDir = resourcesDir + dirSet.srcDirs = compilation.sourceSets.flatMapTo(LinkedHashSet()) { it.resourceDirs } + dirSet.gradleOutputDirs += resourcesDir + dirSet.setInheritedCompilerOutput(false) + } + } + sourceSet.sources = sourcesWithTypes.toMap() + } + } + + private fun createExternalSourceSet(ktSourceSet: KotlinSourceSet, ktSourceSetData: GradleSourceSetData): ExternalSourceSet { + return DefaultExternalSourceSet().also { sourceSet -> + sourceSet.name = ktSourceSet.name + sourceSet.targetCompatibility = ktSourceSetData.targetCompatibility + sourceSet.dependencies += ktSourceSet.dependencies + sourceSet.sources = linkedMapOf( + ktSourceSet.sourceType to DefaultExternalSourceDirectorySet().also { dirSet -> + dirSet.srcDirs = ktSourceSet.sourceDirs + }, + ktSourceSet.resourceType to DefaultExternalSourceDirectorySet().also { dirSet -> + dirSet.srcDirs = ktSourceSet.resourceDirs + } + ) + } + } + + private val KotlinModule.sourceType + get() = if (isTestModule) ExternalSystemSourceType.TEST else ExternalSystemSourceType.SOURCE + + private val KotlinModule.resourceType + get() = if (isTestModule) ExternalSystemSourceType.TEST_RESOURCE else ExternalSystemSourceType.RESOURCE + + private fun createSourceSetInfo( + sourceSet: KotlinSourceSet, + gradleModule: IdeaModule, + resolverCtx: ProjectResolverContext + ): KotlinSourceSetInfo { + return KotlinSourceSetInfo(sourceSet).also { + it.moduleId = getKotlinModuleId(gradleModule, sourceSet, resolverCtx) + it.platform = sourceSet.platform + it.isTestModule = sourceSet.isTestModule + it.compilerArguments = createCompilerArguments(emptyList(), sourceSet.platform).also { + it.multiPlatform = true + } + } + } + + fun createSourceSetInfo( + compilation: KotlinCompilation, + gradleModule: IdeaModule, + resolverCtx: ProjectResolverContext + ): KotlinSourceSetInfo { + return KotlinSourceSetInfo(compilation).also { + it.moduleId = getKotlinModuleId(gradleModule, compilation, resolverCtx) + it.platform = compilation.platform + it.isTestModule = compilation.isTestModule + it.compilerArguments = createCompilerArguments(compilation.arguments.currentArguments, compilation.platform).also { + it.multiPlatform = true + } + it.dependencyClasspath = compilation.dependencyClasspath + it.defaultCompilerArguments = createCompilerArguments(compilation.arguments.defaultArguments, compilation.platform) + it.sourceSetIdsByName = compilation.sourceSets + .asSequence() + .filter { it.fullName() != compilation.fullName() } + .map { it.name to getKotlinModuleId(gradleModule, it, resolverCtx) } + .toMap() + } + } + + private fun createCompilerArguments(args: List, platform: KotlinPlatform): CommonCompilerArguments { + return when (platform) { + KotlinPlatform.COMMON -> K2MetadataCompilerArguments() + KotlinPlatform.JVM -> K2JVMCompilerArguments() + KotlinPlatform.JS -> K2JSCompilerArguments() + }.also { + parseCommandLineArguments(args, it) + } + } + + private fun KotlinModule.fullName(simpleName: String = name) = when (this) { + is KotlinCompilation -> target.disambiguationClassifier?.let { it + simpleName.capitalize() } ?: simpleName + else -> simpleName + } + + private fun getKotlinModuleId(gradleModule: IdeaModule, kotlinModule: KotlinModule, resolverCtx: ProjectResolverContext) = + getModuleId(resolverCtx, gradleModule) + ":" + kotlinModule.fullName() + } +} \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinSourceSetDataService.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinSourceSetDataService.kt new file mode 100644 index 00000000000..958d5e7d577 --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinSourceSetDataService.kt @@ -0,0 +1,163 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.idea.configuration + +import com.intellij.openapi.externalSystem.model.DataNode +import com.intellij.openapi.externalSystem.model.ProjectKeys +import com.intellij.openapi.externalSystem.model.project.ExternalSystemSourceType +import com.intellij.openapi.externalSystem.model.project.ModuleData +import com.intellij.openapi.externalSystem.model.project.ProjectData +import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProvider +import com.intellij.openapi.externalSystem.service.project.manage.AbstractProjectDataService +import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil +import com.intellij.openapi.module.Module +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.LibraryOrderEntry +import com.intellij.openapi.roots.ModifiableRootModel +import com.intellij.openapi.roots.impl.libraries.LibraryEx +import com.intellij.openapi.roots.libraries.Library +import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments +import org.jetbrains.kotlin.config.CoroutineSupport +import org.jetbrains.kotlin.config.JvmTarget +import org.jetbrains.kotlin.config.KotlinModuleKind +import org.jetbrains.kotlin.config.TargetPlatformKind +import org.jetbrains.kotlin.gradle.KotlinCompilation +import org.jetbrains.kotlin.gradle.KotlinModule +import org.jetbrains.kotlin.gradle.KotlinPlatform +import org.jetbrains.kotlin.gradle.KotlinSourceSet +import org.jetbrains.kotlin.idea.facet.applyCompilerArgumentsToFacet +import org.jetbrains.kotlin.idea.facet.configureFacet +import org.jetbrains.kotlin.idea.facet.getOrCreateFacet +import org.jetbrains.kotlin.idea.facet.noVersionAutoAdvance +import org.jetbrains.kotlin.idea.framework.CommonLibraryKind +import org.jetbrains.kotlin.idea.framework.JSLibraryKind +import org.jetbrains.kotlin.idea.framework.effectiveKind +import org.jetbrains.kotlin.idea.inspections.gradle.findAll +import org.jetbrains.kotlin.idea.inspections.gradle.findKotlinPluginVersion +import org.jetbrains.kotlin.idea.roots.migrateNonJvmSourceFolders +import org.jetbrains.plugins.gradle.model.data.BuildScriptClasspathData +import org.jetbrains.plugins.gradle.model.data.GradleSourceSetData + +class KotlinSourceSetDataService : AbstractProjectDataService() { + override fun getTargetDataKey() = GradleSourceSetData.KEY + + override fun postProcess( + toImport: MutableCollection>, + projectData: ProjectData?, + project: Project, + modelsProvider: IdeModifiableModelsProvider + ) { + for (nodeToImport in toImport) { + val mainModuleData = ExternalSystemApiUtil.findParent( + nodeToImport, + ProjectKeys.MODULE + ) ?: continue + val sourceSetData = nodeToImport.data + val kotlinSourceSet = nodeToImport.kotlinSourceSet ?: continue + val ideModule = modelsProvider.findIdeModule(sourceSetData) ?: continue + val platform = kotlinSourceSet.platform + val rootModel = modelsProvider.getModifiableRootModel(ideModule) + + dropWrongLibraries(rootModel, platform, project) + + if (platform != KotlinPlatform.JVM) { + migrateNonJvmSourceFolders(rootModel) + } + + configureFacet(sourceSetData, kotlinSourceSet, mainModuleData, ideModule, modelsProvider) + } + } + + private val KotlinModule.kind + get() = when (this) { + is KotlinCompilation -> KotlinModuleKind.COMPILATION_AND_SOURCE_SET_HOLDER + is KotlinSourceSet -> KotlinModuleKind.SOURCE_SET_HOLDER + else -> KotlinModuleKind.DEFAULT + } + + private fun configureFacet( + sourceSetData: GradleSourceSetData, + kotlinSourceSet: KotlinSourceSetInfo, + mainModuleNode: DataNode, + ideModule: Module, + modelsProvider: IdeModifiableModelsProvider + ) { + val compilerVersion = mainModuleNode + .findAll(BuildScriptClasspathData.KEY) + .firstOrNull() + ?.data + ?.let { findKotlinPluginVersion(it) } ?: return + val platformKind = when (kotlinSourceSet.platform) { + KotlinPlatform.JVM -> TargetPlatformKind.Jvm[JvmTarget.fromString( + sourceSetData.targetCompatibility ?: "" + ) ?: JvmTarget.DEFAULT] + KotlinPlatform.JS -> TargetPlatformKind.JavaScript + KotlinPlatform.COMMON -> TargetPlatformKind.Common + } + val coroutinesProperty = CoroutineSupport.byCompilerArgument( + mainModuleNode.coroutines ?: findKotlinCoroutinesProperty(ideModule.project) + ) + + val kotlinFacet = ideModule.getOrCreateFacet(modelsProvider, false) + kotlinFacet.configureFacet(compilerVersion, coroutinesProperty, platformKind, modelsProvider) + + val compilerArguments = kotlinSourceSet.compilerArguments + val defaultCompilerArguments = kotlinSourceSet.defaultCompilerArguments + if (compilerArguments != null) { + applyCompilerArgumentsToFacet( + compilerArguments, + defaultCompilerArguments, + kotlinFacet, + modelsProvider + ) + } + + adjustClasspath(kotlinFacet, kotlinSourceSet.dependencyClasspath) + + kotlinFacet.noVersionAutoAdvance() + + with(kotlinFacet.configuration.settings) { + kind = kotlinSourceSet.kotlinModule.kind + + sourceSetNames = kotlinSourceSet.sourceSetIdsByName.values.mapNotNull { sourceSetId -> + val node = mainModuleNode.findChildModuleById(sourceSetId) ?: return@mapNotNull null + val data = node.data as? ModuleData ?: return@mapNotNull null + modelsProvider.findIdeModule(data)?.name + } + + if (kotlinSourceSet.isTestModule) { + testOutputPath = (kotlinSourceSet.compilerArguments as? K2JSCompilerArguments)?.outputFile + productionOutputPath = null + } else { + productionOutputPath = (kotlinSourceSet.compilerArguments as? K2JSCompilerArguments)?.outputFile + testOutputPath = null + } + } + } + + private fun dropWrongLibraries( + rootModel: ModifiableRootModel, + platform: KotlinPlatform, + project: Project + ) { + rootModel.orderEntries().librariesOnly().forEach { orderEntry -> + val library = (orderEntry as? LibraryOrderEntry)?.library + if (library != null && !library.matchesPlatform(platform, project)) { + rootModel.removeOrderEntry(orderEntry) + } + true + } + } + + private fun Library.matchesPlatform(platform: KotlinPlatform, project: Project): Boolean { + val kind = (this as? LibraryEx)?.effectiveKind(project) + return when (kind) { + CommonLibraryKind -> true + JSLibraryKind -> platform == KotlinPlatform.JS + else -> platform == KotlinPlatform.JVM + } + } +} \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinTargetDataService.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinTargetDataService.kt new file mode 100644 index 00000000000..f4be0722d0f --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinTargetDataService.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.idea.configuration + +import com.intellij.openapi.externalSystem.model.DataNode +import com.intellij.openapi.externalSystem.model.project.ProjectData +import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProvider +import com.intellij.openapi.externalSystem.service.project.manage.AbstractProjectDataService +import com.intellij.openapi.project.Project +import com.intellij.openapi.util.io.FileUtil +import com.intellij.packaging.impl.artifacts.JarArtifactType +import com.intellij.packaging.impl.elements.ProductionModuleOutputPackagingElement +import org.jetbrains.kotlin.idea.util.createPointer + +class KotlinTargetDataService : AbstractProjectDataService() { + override fun getTargetDataKey() = KotlinTargetData.KEY + + override fun importData( + toImport: MutableCollection>, + projectData: ProjectData?, + project: Project, + modelsProvider: IdeModifiableModelsProvider + ) { + for (nodeToImport in toImport) { + val targetData = nodeToImport.data + val archiveFile = targetData.archiveFile ?: continue + val artifactModel = modelsProvider.modifiableArtifactModel + val artifactName = FileUtil.getNameWithoutExtension(archiveFile) + artifactModel.findArtifact(artifactName)?.let { artifactModel.removeArtifact(it) } + artifactModel.addArtifact(artifactName, JarArtifactType.getInstance()).also { + it.outputPath = archiveFile.parent + for (moduleId in targetData.moduleIds) { + val compilationModuleDataNode = nodeToImport.parent?.findChildModuleById(moduleId) ?: continue + val compilationData = compilationModuleDataNode.data ?: continue + val kotlinSourceSet = compilationModuleDataNode.kotlinSourceSet ?: continue + if (kotlinSourceSet.isTestModule) continue + val moduleToPackage = modelsProvider.findIdeModule(compilationData) ?: continue + it.rootElement.addOrFindChild(ProductionModuleOutputPackagingElement(project, moduleToPackage.createPointer())) + } + } + } + } +} \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/dataNodeUtils.kt b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/dataNodeUtils.kt new file mode 100644 index 00000000000..a6abf5fbd10 --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/dataNodeUtils.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.idea.configuration + +import com.intellij.openapi.externalSystem.model.DataNode +import com.intellij.openapi.externalSystem.model.project.ModuleData + +@Suppress("UNCHECKED_CAST") +fun DataNode<*>.findChildModuleById(id: String) = + children.firstOrNull { (it.data as? ModuleData)?.id == id } as? DataNode + +@Suppress("UNCHECKED_CAST") +fun DataNode<*>.findChildModuleByInternalName(name: String) = + children.firstOrNull { (it.data as? ModuleData)?.internalName == name } \ No newline at end of file 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 21d5586a4fe..44dd61b6607 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 @@ -112,6 +112,12 @@ var CommonCompilerArguments.apiVersionView: VersionView autoAdvanceApiVersion = value == VersionView.LatestStable } +enum class KotlinModuleKind { + DEFAULT, + SOURCE_SET_HOLDER, + COMPILATION_AND_SOURCE_SET_HOLDER +} + class KotlinFacetSettings { companion object { // Increment this when making serialization-incompatible changes to configuration data @@ -196,6 +202,9 @@ class KotlinFacetSettings { var productionOutputPath: String? = null var testOutputPath: String? = null + + var kind: KotlinModuleKind = KotlinModuleKind.DEFAULT + var sourceSetNames: List = emptyList() } fun TargetPlatformKind<*>.createCompilerArguments(init: CommonCompilerArguments.() -> Unit = {}): CommonCompilerArguments { 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 21ecb8fd1c3..c4a7e07407a 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 @@ -111,6 +111,20 @@ private fun readV2AndLaterConfig(element: Element): KotlinFacetSettings { listOfNotNull((it.content.firstOrNull() as? Text)?.textTrim) } } + element.getChild("sourceSets")?.let { + val items = it.getChildren("sourceSet") + sourceSetNames = items.mapNotNull { (it.content.firstOrNull() as? Text)?.textTrim } + } + kind = element.getChild("newMppModelJpsModuleKind")?.let { + val kindName = (it.content.firstOrNull() as? Text)?.textTrim + if (kindName != null) { + try { + KotlinModuleKind.valueOf(kindName) + } catch (e: Exception) { + null + } + } else null + } ?: KotlinModuleKind.DEFAULT element.getChild("compilerSettings")?.let { compilerSettings = CompilerSettings() XmlSerializer.deserializeInto(compilerSettings!!, it) @@ -259,6 +273,16 @@ private fun KotlinFacetSettings.writeLatestConfig(element: Element) { } ) } + if (sourceSetNames.isNotEmpty()) { + element.addContent( + Element("sourceSets").apply { + sourceSetNames.map { addContent(Element("sourceSet").apply { addContent(it) }) } + } + ) + } + if (kind != KotlinModuleKind.DEFAULT) { + element.addContent(Element("newMppModelJpsModuleKind").apply { addContent(kind.name) }) + } productionOutputPath?.let { if (it != (compilerArguments as? K2JSCompilerArguments)?.outputFile) { element.addContent(Element("productionOutputPath").apply { addContent(PathUtil.toSystemIndependentName(it)) }) diff --git a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModel.kt b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModel.kt new file mode 100644 index 00000000000..bb735e4a24a --- /dev/null +++ b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModel.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.ExternalDependency +import java.io.File +import java.io.Serializable + +typealias KotlinDependency = ExternalDependency + +interface KotlinModule : Serializable { + val isAndroid: Boolean + val name: String + val platform: KotlinPlatform + val dependencies: Set + val isTestModule: Boolean +} + +interface KotlinSourceSet : KotlinModule { + val sourceDirs: Set + val resourceDirs: Set + val dependsOnSourceSets: Set +} + +interface KotlinCompilationOutput : Serializable { + val classesDirs: Set + val effectiveClassesDir: File? + val resourcesDir: File? +} + +interface KotlinCompilationArguments : Serializable { + val defaultArguments: List + val currentArguments: List +} + +interface KotlinCompilation : KotlinModule { + val sourceSets: Collection + val target: KotlinTarget + val output: KotlinCompilationOutput + val arguments: KotlinCompilationArguments + val dependencyClasspath: List + + companion object { + const val MAIN_COMPILATION_NAME = "main" + const val TEST_COMPILATION_NAME = "test" + } +} + +enum class KotlinPlatform(val id: String) { + COMMON("common"), + JVM("jvm"), + JS("js"); + + companion object { + fun byId(id: String) = values().firstOrNull { it.id == id } + } +} + +interface KotlinTargetJar : Serializable { + val archiveFile: File? +} + +interface KotlinTarget : Serializable { + val isAndroid: Boolean + val name: String + val disambiguationClassifier: String? + val platform: KotlinPlatform + val compilations: Collection + val jar: KotlinTargetJar? +} + +interface ExtraFeatures : Serializable { + val coroutinesState: String? +} + +interface KotlinMPPGradleModel : Serializable { + val sourceSets: Map + val targets: Collection + val extraFeatures: ExtraFeatures +} \ No newline at end of file diff --git a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt new file mode 100644 index 00000000000..5cef8ae3002 --- /dev/null +++ b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt @@ -0,0 +1,264 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.gradle.api.Named +import org.gradle.api.NamedDomainObjectContainer +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.artifacts.Dependency +import org.gradle.api.artifacts.component.ProjectComponentIdentifier +import org.gradle.api.file.FileCollection +import org.gradle.api.file.SourceDirectorySet +import org.jetbrains.plugins.gradle.model.AbstractExternalDependency +import org.jetbrains.plugins.gradle.model.DefaultExternalProjectDependency +import org.jetbrains.plugins.gradle.model.ExternalDependency +import org.jetbrains.plugins.gradle.model.ExternalProjectDependency +import org.jetbrains.plugins.gradle.tooling.ErrorMessageBuilder +import org.jetbrains.plugins.gradle.tooling.ModelBuilderService +import org.jetbrains.plugins.gradle.tooling.util.DependencyResolver +import org.jetbrains.plugins.gradle.tooling.util.resolve.DependencyResolverImpl +import org.jetbrains.plugins.gradle.tooling.util.SourceSetCachedFinder +import java.io.File +import java.lang.Exception + +class KotlinMPPGradleModelBuilder : ModelBuilderService { + override fun getErrorMessageBuilder(project: Project, e: Exception): ErrorMessageBuilder { + return ErrorMessageBuilder + .create(project, e, "Gradle import errors") + .withDescription("Unable to build Kotlin project configuration") + } + + override fun canBuild(modelName: String?): Boolean { + return modelName == KotlinMPPGradleModel::class.java.name + } + + override fun buildAll(modelName: String, project: Project): Any? { + val dependencyResolver = DependencyResolverImpl( + project, + false, + false, + true, + SourceSetCachedFinder(project) + ) + val sourceSets = buildSourceSets(project) ?: return null + val sourceSetMap = sourceSets.map { it.name to it }.toMap() + val targets = buildTargets(sourceSetMap, dependencyResolver, project) ?: return null + computeSourceSetsDeferredInfo(sourceSets, targets) + val coroutinesState = getCoroutinesState(project) + return KotlinMPPGradleModelImpl(sourceSetMap, targets, ExtraFeaturesImpl(coroutinesState)) + } + + private fun getCoroutinesState(project: Project): String? { + val kotlinExt = project.extensions.findByName("kotlin") ?: return null + val getExperimental = kotlinExt.javaClass.getMethodOrNull("getExperimental") ?: return null + val experimentalExt = getExperimental(kotlinExt) ?: return null + val getCoroutines = experimentalExt.javaClass.getMethodOrNull("getCoroutines") ?: return null + return getCoroutines(experimentalExt) as? String + } + + private fun buildSourceSets(project: Project): Collection? { + val kotlinExt = project.extensions.findByName("kotlin") ?: return null + val getSourceSets = kotlinExt.javaClass.getMethodOrNull("getSourceSets") ?: return null + @Suppress("UNCHECKED_CAST") + val sourceSets = + (getSourceSets(kotlinExt) as? NamedDomainObjectContainer)?.asMap?.values ?: emptyList() + return sourceSets.mapNotNull { buildSourceSet(it) } + } + + private fun buildSourceSet(gradleSourceSet: Named): KotlinSourceSetImpl? { + val sourceSetClass = gradleSourceSet.javaClass + val getSourceDirSet = sourceSetClass.getMethodOrNull("getKotlin") ?: return null + val getResourceDirSet = sourceSetClass.getMethodOrNull("getResources") ?: return null + val getDependsOn = sourceSetClass.getMethodOrNull("getDependsOn") ?: return null + val sourceDirs = (getSourceDirSet(gradleSourceSet) as? SourceDirectorySet)?.srcDirs ?: emptySet() + val resourceDirs = (getResourceDirSet(gradleSourceSet) as? SourceDirectorySet)?.srcDirs ?: emptySet() + @Suppress("UNCHECKED_CAST") + val dependsOnSourceSets = (getDependsOn(gradleSourceSet) as? Set)?.mapTo(LinkedHashSet()) { it.name } ?: emptySet() + return KotlinSourceSetImpl(gradleSourceSet.name, sourceDirs, resourceDirs, dependsOnSourceSets) + } + + private fun buildDependencies( + gradleCompilation: Any, + dependencyResolver: DependencyResolver, + configurationNameAccessor: String, + scope: String, + project: Project + ): Collection { + val gradleCompilationClass = gradleCompilation.javaClass + val getConfigurationName = gradleCompilationClass.getMethodOrNull(configurationNameAccessor) ?: return emptyList() + val configurationName = getConfigurationName(gradleCompilation) as? String ?: return emptyList() + val configuration = project.configurations.findByName(configurationName) ?: return emptyList() + if (!configuration.isCanBeResolved) return emptyList() + val artifactsByProjectPath = configuration + .resolvedConfiguration + .lenientConfiguration + .firstLevelModuleDependencies + .flatMap { it.moduleArtifacts } + .filter { it.id.componentIdentifier is ProjectComponentIdentifier } + .groupBy { (it.id.componentIdentifier as ProjectComponentIdentifier).projectPath } + return dependencyResolver.resolveDependencies(configuration) + .apply { + forEach { (it as? AbstractExternalDependency)?.scope = scope } + } + .map { + if (it !is ExternalProjectDependency || it.configurationName != Dependency.DEFAULT_CONFIGURATION) return@map it + val artifacts = artifactsByProjectPath[it.projectPath] ?: return@map it + val classifier = artifacts.mapTo(LinkedHashSet()) { it.classifier }.singleOrNull() ?: return@map it + DefaultExternalProjectDependency(it).apply { + this.classifier = classifier + } + } + } + + private fun buildTargets( + sourceSetMap: Map, + dependencyResolver: DependencyResolver, + project: Project + ): Collection? { + val kotlinExt = project.extensions.findByName("kotlin") ?: return null + val getTargets = kotlinExt.javaClass.getMethodOrNull("getTargets") ?: return null + @Suppress("UNCHECKED_CAST") + val targets = (getTargets.invoke(kotlinExt) as? NamedDomainObjectContainer)?.asMap?.values ?: emptyList() + return targets.mapNotNull { buildTarget(it, sourceSetMap, dependencyResolver, project) } + } + + private fun buildTarget( + gradleTarget: Named, + sourceSetMap: Map, + dependencyResolver: DependencyResolver, + project: Project + ): KotlinTarget? { + val targetClass = gradleTarget.javaClass + val getPlatformType = targetClass.getMethodOrNull("getPlatformType") ?: return null + val getCompilations = targetClass.getMethodOrNull("getCompilations") ?: return null + val getDisambiguationClassifier = targetClass.getMethodOrNull("getDisambiguationClassifier") ?: return null + val platformId = (getPlatformType.invoke(gradleTarget) as? Named)?.name ?: return null + val platform = KotlinPlatform.byId(platformId) ?: return null + val disambiguationClassifier = getDisambiguationClassifier(gradleTarget) as? String + val isAndroid = targetClass.name == KOTLIN_ANDROID_TARGET_CLASS_NAME + @Suppress("UNCHECKED_CAST") + val gradleCompilations = + (getCompilations.invoke(gradleTarget) as? NamedDomainObjectContainer)?.asMap?.values ?: emptyList() + val compilations = gradleCompilations.mapNotNull { buildCompilation(it, sourceSetMap, dependencyResolver, isAndroid, project) } + val jar = buildTargetJar(gradleTarget, project) + val target = KotlinTargetImpl(isAndroid, gradleTarget.name, disambiguationClassifier, platform, compilations, jar) + compilations.forEach { it.target = target } + return target + } + + private fun buildTargetJar(gradleTarget: Named, project: Project): KotlinTargetJar? { + val targetClass = gradleTarget.javaClass + val getArtifactsTaskName = targetClass.getMethodOrNull("getArtifactsTaskName") ?: return null + val artifactsTaskName = getArtifactsTaskName(gradleTarget) as? String ?: return null + val jarTask = project.tasks.findByName(artifactsTaskName) ?: return null + val jarTaskClass = jarTask.javaClass + val getArchivePath = jarTaskClass.getMethodOrNull("getArchivePath") + val archiveFile = getArchivePath?.invoke(jarTask) as? File? + return KotlinTargetJarImpl(archiveFile) + } + + private fun buildCompilation( + gradleCompilation: Named, + sourceSetMap: Map, + dependencyResolver: DependencyResolver, + isAndroid: Boolean, + project: Project + ): KotlinCompilationImpl? { + val compilationClass = gradleCompilation.javaClass + val getKotlinSourceSets = compilationClass.getMethodOrNull("getKotlinSourceSets") ?: return null + @Suppress("UNCHECKED_CAST") + val kotlinGradleSourceSets = (getKotlinSourceSets(gradleCompilation) as? Collection) ?: return null + val kotlinSourceSets = kotlinGradleSourceSets.mapNotNull { sourceSetMap[it.name] } + val getCompileKotlinTaskName = compilationClass.getMethodOrNull("getCompileKotlinTaskName") ?: return null + @Suppress("UNCHECKED_CAST") + val compileKotlinTaskName = (getCompileKotlinTaskName(gradleCompilation) as? String) ?: return null + val compileKotlinTask = project.tasks.findByName(compileKotlinTaskName) ?: return null + val output = buildCompilationOutput(gradleCompilation, compileKotlinTask) ?: return null + val arguments = buildCompilationArguments(compileKotlinTask) ?: return null + val dependencyClasspath = buildDependencyClasspath(compileKotlinTask) + val dependencies = buildDependencies(gradleCompilation, dependencyResolver, project) + return KotlinCompilationImpl(isAndroid, gradleCompilation.name, kotlinSourceSets, dependencies, output, arguments, dependencyClasspath) + } + + private fun buildDependencies( + gradleCompilation: Named, + dependencyResolver: DependencyResolver, + project: Project + ): Set { + return LinkedHashSet().apply { + this += buildDependencies( + gradleCompilation, dependencyResolver, "getCompileDependencyConfigurationName", "COMPILE", project + ) + this += buildDependencies( + gradleCompilation, dependencyResolver, "getRuntimeDependencyConfigurationName", "RUNTIME", project + ) + } + } + + private fun buildCompilationArguments(compileKotlinTask: Task): KotlinCompilationArguments? { + val compileTaskClass = compileKotlinTask.javaClass + val getCurrentArguments = compileTaskClass.getMethodOrNull("getSerializedCompilerArguments") ?: return null + val getDefaultArguments = compileTaskClass.getMethodOrNull("getDefaultSerializedCompilerArguments") ?: return null + @Suppress("UNCHECKED_CAST") + val currentArguments = getCurrentArguments(compileKotlinTask) as? List ?: return null + @Suppress("UNCHECKED_CAST") + val defaultArguments = getDefaultArguments(compileKotlinTask) as? List ?: return null + return KotlinCompilationArgumentsImpl(defaultArguments, currentArguments) + } + + private fun buildDependencyClasspath(compileKotlinTask: Task): List { + val abstractKotlinCompileClass = + compileKotlinTask.javaClass.classLoader.loadClass(AbstractKotlinGradleModelBuilder.ABSTRACT_KOTLIN_COMPILE_CLASS) + val getCompileClasspath = + abstractKotlinCompileClass.getDeclaredMethodOrNull("getCompileClasspath") ?: return emptyList() + @Suppress("UNCHECKED_CAST") + return (getCompileClasspath(compileKotlinTask) as? Collection)?.map { it.path } ?: emptyList() + } + + private fun buildCompilationOutput( + gradleCompilation: Named, + compileKotlinTask: Task + ): KotlinCompilationOutput? { + val compilationClass = gradleCompilation.javaClass + val getOutput = compilationClass.getMethodOrNull("getOutput") ?: return null + val gradleOutput = getOutput(gradleCompilation) ?: return null + val gradleOutputClass = gradleOutput.javaClass + val getClassesDirs = gradleOutputClass.getMethodOrNull("getClassesDirs") ?: return null + val getResourcesDir = gradleOutputClass.getMethodOrNull("getResourcesDir") ?: return null + val compileKotlinTaskClass = compileKotlinTask.javaClass + val getDestinationDir = compileKotlinTaskClass.getMethodOrNull("getDestinationDir") ?: return null + val classesDirs = getClassesDirs(gradleOutput) as? FileCollection ?: return null + val resourcesDir = getResourcesDir(gradleOutput) as? File ?: return null + val destinationDir = getDestinationDir(compileKotlinTask) as? File + return KotlinCompilationOutputImpl(classesDirs.files, destinationDir, resourcesDir) + } + + private fun computeSourceSetsDeferredInfo( + sourceSets: Collection, + targets: Collection + ) { + val sourceSetToCompilations = LinkedHashMap>() + for (target in targets) { + for (compilation in target.compilations) { + for (sourceSet in compilation.sourceSets) { + sourceSetToCompilations.getOrPut(sourceSet) { LinkedHashSet() } += compilation + } + } + } + for (sourceSet in sourceSets) { + val compilations = sourceSetToCompilations[sourceSet] ?: continue + sourceSet.platform = compilations.map { it.platform }.distinct().singleOrNull() ?: KotlinPlatform.COMMON + sourceSet.dependencies = compilations.flatMapTo(LinkedHashSet()) { it.dependencies } + sourceSet.isTestModule = compilations.all { it.isTestModule } + sourceSet.isAndroid = compilations.all { it.isAndroid } + } + } + + companion object { + private val KOTLIN_ANDROID_TARGET_CLASS_NAME = "org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget" + } +} \ No newline at end of file diff --git a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt.181 b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt.181 new file mode 100644 index 00000000000..e74c8ac1a5f --- /dev/null +++ b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelBuilder.kt.181 @@ -0,0 +1,264 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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.gradle.api.Named +import org.gradle.api.NamedDomainObjectContainer +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.artifacts.Dependency +import org.gradle.api.artifacts.component.ProjectComponentIdentifier +import org.gradle.api.file.FileCollection +import org.gradle.api.file.SourceDirectorySet +import org.jetbrains.plugins.gradle.model.AbstractExternalDependency +import org.jetbrains.plugins.gradle.model.DefaultExternalProjectDependency +import org.jetbrains.plugins.gradle.model.ExternalDependency +import org.jetbrains.plugins.gradle.model.ExternalProjectDependency +import org.jetbrains.plugins.gradle.tooling.ErrorMessageBuilder +import org.jetbrains.plugins.gradle.tooling.ModelBuilderService +import org.jetbrains.plugins.gradle.tooling.util.DependencyResolver +import org.jetbrains.plugins.gradle.tooling.util.DependencyResolverImpl +import org.jetbrains.plugins.gradle.tooling.util.SourceSetCachedFinder +import java.io.File +import java.lang.Exception + +class KotlinMPPGradleModelBuilder : ModelBuilderService { + override fun getErrorMessageBuilder(project: Project, e: Exception): ErrorMessageBuilder { + return ErrorMessageBuilder + .create(project, e, "Gradle import errors") + .withDescription("Unable to build Kotlin project configuration") + } + + override fun canBuild(modelName: String?): Boolean { + return modelName == KotlinMPPGradleModel::class.java.name + } + + override fun buildAll(modelName: String, project: Project): Any? { + val dependencyResolver = DependencyResolverImpl( + project, + false, + false, + true, + SourceSetCachedFinder(project) + ) + val sourceSets = buildSourceSets(project) ?: return null + val sourceSetMap = sourceSets.map { it.name to it }.toMap() + val targets = buildTargets(sourceSetMap, dependencyResolver, project) ?: return null + computeSourceSetsDeferredInfo(sourceSets, targets) + val coroutinesState = getCoroutinesState(project) + return KotlinMPPGradleModelImpl(sourceSetMap, targets, ExtraFeaturesImpl(coroutinesState)) + } + + private fun getCoroutinesState(project: Project): String? { + val kotlinExt = project.extensions.findByName("kotlin") ?: return null + val getExperimental = kotlinExt.javaClass.getMethodOrNull("getExperimental") ?: return null + val experimentalExt = getExperimental(kotlinExt) ?: return null + val getCoroutines = experimentalExt.javaClass.getMethodOrNull("getCoroutines") ?: return null + return getCoroutines(experimentalExt) as? String + } + + private fun buildSourceSets(project: Project): Collection? { + val kotlinExt = project.extensions.findByName("kotlin") ?: return null + val getSourceSets = kotlinExt.javaClass.getMethodOrNull("getSourceSets") ?: return null + @Suppress("UNCHECKED_CAST") + val sourceSets = + (getSourceSets(kotlinExt) as? NamedDomainObjectContainer)?.asMap?.values ?: emptyList() + return sourceSets.mapNotNull { buildSourceSet(it) } + } + + private fun buildSourceSet(gradleSourceSet: Named): KotlinSourceSetImpl? { + val sourceSetClass = gradleSourceSet.javaClass + val getSourceDirSet = sourceSetClass.getMethodOrNull("getKotlin") ?: return null + val getResourceDirSet = sourceSetClass.getMethodOrNull("getResources") ?: return null + val getDependsOn = sourceSetClass.getMethodOrNull("getDependsOn") ?: return null + val sourceDirs = (getSourceDirSet(gradleSourceSet) as? SourceDirectorySet)?.srcDirs ?: emptySet() + val resourceDirs = (getResourceDirSet(gradleSourceSet) as? SourceDirectorySet)?.srcDirs ?: emptySet() + @Suppress("UNCHECKED_CAST") + val dependsOnSourceSets = (getDependsOn(gradleSourceSet) as? Set)?.mapTo(LinkedHashSet()) { it.name } ?: emptySet() + return KotlinSourceSetImpl(gradleSourceSet.name, sourceDirs, resourceDirs, dependsOnSourceSets) + } + + private fun buildDependencies( + gradleCompilation: Any, + dependencyResolver: DependencyResolver, + configurationNameAccessor: String, + scope: String, + project: Project + ): Collection { + val gradleCompilationClass = gradleCompilation.javaClass + val getConfigurationName = gradleCompilationClass.getMethodOrNull(configurationNameAccessor) ?: return emptyList() + val configurationName = getConfigurationName(gradleCompilation) as? String ?: return emptyList() + val configuration = project.configurations.findByName(configurationName) ?: return emptyList() + if (!configuration.isCanBeResolved) return emptyList() + val artifactsByProjectPath = configuration + .resolvedConfiguration + .lenientConfiguration + .firstLevelModuleDependencies + .flatMap { it.moduleArtifacts } + .filter { it.id.componentIdentifier is ProjectComponentIdentifier } + .groupBy { (it.id.componentIdentifier as ProjectComponentIdentifier).projectPath } + return dependencyResolver.resolveDependencies(configuration) + .apply { + forEach { (it as? AbstractExternalDependency)?.scope = scope } + } + .map { + if (it !is ExternalProjectDependency || it.configurationName != Dependency.DEFAULT_CONFIGURATION) return@map it + val artifacts = artifactsByProjectPath[it.projectPath] ?: return@map it + val classifier = artifacts.mapTo(LinkedHashSet()) { it.classifier }.singleOrNull() ?: return@map it + DefaultExternalProjectDependency(it).apply { + this.classifier = classifier + } + } + } + + private fun buildTargets( + sourceSetMap: Map, + dependencyResolver: DependencyResolver, + project: Project + ): Collection? { + val kotlinExt = project.extensions.findByName("kotlin") ?: return null + val getTargets = kotlinExt.javaClass.getMethodOrNull("getTargets") ?: return null + @Suppress("UNCHECKED_CAST") + val targets = (getTargets.invoke(kotlinExt) as? NamedDomainObjectContainer)?.asMap?.values ?: emptyList() + return targets.mapNotNull { buildTarget(it, sourceSetMap, dependencyResolver, project) } + } + + private fun buildTarget( + gradleTarget: Named, + sourceSetMap: Map, + dependencyResolver: DependencyResolver, + project: Project + ): KotlinTarget? { + val targetClass = gradleTarget.javaClass + val getPlatformType = targetClass.getMethodOrNull("getPlatformType") ?: return null + val getCompilations = targetClass.getMethodOrNull("getCompilations") ?: return null + val getDisambiguationClassifier = targetClass.getMethodOrNull("getDisambiguationClassifier") ?: return null + val platformId = (getPlatformType.invoke(gradleTarget) as? Named)?.name ?: return null + val platform = KotlinPlatform.byId(platformId) ?: return null + val disambiguationClassifier = getDisambiguationClassifier(gradleTarget) as? String + val isAndroid = targetClass.name == KOTLIN_ANDROID_TARGET_CLASS_NAME + @Suppress("UNCHECKED_CAST") + val gradleCompilations = + (getCompilations.invoke(gradleTarget) as? NamedDomainObjectContainer)?.asMap?.values ?: emptyList() + val compilations = gradleCompilations.mapNotNull { buildCompilation(it, sourceSetMap, dependencyResolver, isAndroid, project) } + val jar = buildTargetJar(gradleTarget, project) + val target = KotlinTargetImpl(isAndroid, gradleTarget.name, disambiguationClassifier, platform, compilations, jar) + compilations.forEach { it.target = target } + return target + } + + private fun buildTargetJar(gradleTarget: Named, project: Project): KotlinTargetJar? { + val targetClass = gradleTarget.javaClass + val getArtifactsTaskName = targetClass.getMethodOrNull("getArtifactsTaskName") ?: return null + val artifactsTaskName = getArtifactsTaskName(gradleTarget) as? String ?: return null + val jarTask = project.tasks.findByName(artifactsTaskName) ?: return null + val jarTaskClass = jarTask.javaClass + val getArchivePath = jarTaskClass.getMethodOrNull("getArchivePath") + val archiveFile = getArchivePath?.invoke(jarTask) as? File? + return KotlinTargetJarImpl(archiveFile) + } + + private fun buildCompilation( + gradleCompilation: Named, + sourceSetMap: Map, + dependencyResolver: DependencyResolver, + isAndroid: Boolean, + project: Project + ): KotlinCompilationImpl? { + val compilationClass = gradleCompilation.javaClass + val getKotlinSourceSets = compilationClass.getMethodOrNull("getKotlinSourceSets") ?: return null + @Suppress("UNCHECKED_CAST") + val kotlinGradleSourceSets = (getKotlinSourceSets(gradleCompilation) as? Collection) ?: return null + val kotlinSourceSets = kotlinGradleSourceSets.mapNotNull { sourceSetMap[it.name] } + val getCompileKotlinTaskName = compilationClass.getMethodOrNull("getCompileKotlinTaskName") ?: return null + @Suppress("UNCHECKED_CAST") + val compileKotlinTaskName = (getCompileKotlinTaskName(gradleCompilation) as? String) ?: return null + val compileKotlinTask = project.tasks.findByName(compileKotlinTaskName) ?: return null + val output = buildCompilationOutput(gradleCompilation, compileKotlinTask) ?: return null + val arguments = buildCompilationArguments(compileKotlinTask) ?: return null + val dependencyClasspath = buildDependencyClasspath(compileKotlinTask) + val dependencies = buildDependencies(gradleCompilation, dependencyResolver, project) + return KotlinCompilationImpl(isAndroid, gradleCompilation.name, kotlinSourceSets, dependencies, output, arguments, dependencyClasspath) + } + + private fun buildDependencies( + gradleCompilation: Named, + dependencyResolver: DependencyResolver, + project: Project + ): Set { + return LinkedHashSet().apply { + this += buildDependencies( + gradleCompilation, dependencyResolver, "getCompileDependencyConfigurationName", "COMPILE", project + ) + this += buildDependencies( + gradleCompilation, dependencyResolver, "getRuntimeDependencyConfigurationName", "RUNTIME", project + ) + } + } + + private fun buildCompilationArguments(compileKotlinTask: Task): KotlinCompilationArguments? { + val compileTaskClass = compileKotlinTask.javaClass + val getCurrentArguments = compileTaskClass.getMethodOrNull("getSerializedCompilerArguments") ?: return null + val getDefaultArguments = compileTaskClass.getMethodOrNull("getDefaultSerializedCompilerArguments") ?: return null + @Suppress("UNCHECKED_CAST") + val currentArguments = getCurrentArguments(compileKotlinTask) as? List ?: return null + @Suppress("UNCHECKED_CAST") + val defaultArguments = getDefaultArguments(compileKotlinTask) as? List ?: return null + return KotlinCompilationArgumentsImpl(defaultArguments, currentArguments) + } + + private fun buildDependencyClasspath(compileKotlinTask: Task): List { + val abstractKotlinCompileClass = + compileKotlinTask.javaClass.classLoader.loadClass(AbstractKotlinGradleModelBuilder.ABSTRACT_KOTLIN_COMPILE_CLASS) + val getCompileClasspath = + abstractKotlinCompileClass.getDeclaredMethodOrNull("getCompileClasspath") ?: return emptyList() + @Suppress("UNCHECKED_CAST") + return (getCompileClasspath(compileKotlinTask) as? Collection)?.map { it.path } ?: emptyList() + } + + private fun buildCompilationOutput( + gradleCompilation: Named, + compileKotlinTask: Task + ): KotlinCompilationOutput? { + val compilationClass = gradleCompilation.javaClass + val getOutput = compilationClass.getMethodOrNull("getOutput") ?: return null + val gradleOutput = getOutput(gradleCompilation) ?: return null + val gradleOutputClass = gradleOutput.javaClass + val getClassesDirs = gradleOutputClass.getMethodOrNull("getClassesDirs") ?: return null + val getResourcesDir = gradleOutputClass.getMethodOrNull("getResourcesDir") ?: return null + val compileKotlinTaskClass = compileKotlinTask.javaClass + val getDestinationDir = compileKotlinTaskClass.getMethodOrNull("getDestinationDir") ?: return null + val classesDirs = getClassesDirs(gradleOutput) as? FileCollection ?: return null + val resourcesDir = getResourcesDir(gradleOutput) as? File ?: return null + val destinationDir = getDestinationDir(compileKotlinTask) as? File + return KotlinCompilationOutputImpl(classesDirs.files, destinationDir, resourcesDir) + } + + private fun computeSourceSetsDeferredInfo( + sourceSets: Collection, + targets: Collection + ) { + val sourceSetToCompilations = LinkedHashMap>() + for (target in targets) { + for (compilation in target.compilations) { + for (sourceSet in compilation.sourceSets) { + sourceSetToCompilations.getOrPut(sourceSet) { LinkedHashSet() } += compilation + } + } + } + for (sourceSet in sourceSets) { + val compilations = sourceSetToCompilations[sourceSet] ?: continue + sourceSet.platform = compilations.map { it.platform }.distinct().singleOrNull() ?: KotlinPlatform.COMMON + sourceSet.dependencies = compilations.flatMapTo(LinkedHashSet()) { it.dependencies } + sourceSet.isTestModule = compilations.all { it.isTestModule } + sourceSet.isAndroid = compilations.all { it.isAndroid } + } + } + + companion object { + private val KOTLIN_ANDROID_TARGET_CLASS_NAME = "org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget" + } +} \ No newline at end of file diff --git a/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelImpl.kt b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelImpl.kt new file mode 100644 index 00000000000..5c6c8c3518c --- /dev/null +++ b/idea/kotlin-gradle-tooling/src/KotlinMPPGradleModelImpl.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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 java.io.File + +class KotlinSourceSetImpl( + override val name: String, + override val sourceDirs: Set, + override val resourceDirs: Set, + override val dependsOnSourceSets: Set +) : KotlinSourceSet { + override var isAndroid: Boolean = false + internal set + + override var platform: KotlinPlatform = KotlinPlatform.COMMON + internal set + + override var dependencies: Set = emptySet() + internal set + + override var isTestModule: Boolean = false + internal set + + override fun toString() = name +} + +class KotlinCompilationOutputImpl( + override val classesDirs: Set, + override val effectiveClassesDir: File?, + override val resourcesDir: File? +) : KotlinCompilationOutput + +class KotlinCompilationArgumentsImpl( + override val defaultArguments: List, + override val currentArguments: List +) : KotlinCompilationArguments + +class KotlinCompilationImpl( + override val isAndroid: Boolean, + override val name: String, + override val sourceSets: Collection, + override val dependencies: Set, + override val output: KotlinCompilationOutput, + override val arguments: KotlinCompilationArguments, + override val dependencyClasspath: List +) : KotlinCompilation { + override lateinit var target: KotlinTarget + internal set + + override val platform: KotlinPlatform + get() = target.platform + + override val isTestModule: Boolean + get() = name == KotlinCompilation.TEST_COMPILATION_NAME + + override fun toString() = name +} + +class KotlinTargetJarImpl( + override val archiveFile: File? +) : KotlinTargetJar + +class KotlinTargetImpl( + override val isAndroid: Boolean, + override val name: String, + override val disambiguationClassifier: String?, + override val platform: KotlinPlatform, + override val compilations: Collection, + override val jar: KotlinTargetJar? +) : KotlinTarget { + override fun toString() = name +} + +class ExtraFeaturesImpl( + override val coroutinesState: String? +) : ExtraFeatures + +class KotlinMPPGradleModelImpl( + override val sourceSets: Map, + override val targets: Collection, + override val extraFeatures: ExtraFeatures +) : KotlinMPPGradleModel \ No newline at end of file diff --git a/idea/kotlin-gradle-tooling/src/META-INF/services/org.jetbrains.plugins.gradle.tooling.ModelBuilderService b/idea/kotlin-gradle-tooling/src/META-INF/services/org.jetbrains.plugins.gradle.tooling.ModelBuilderService index b8452a8e834..b918a8c27ab 100644 --- a/idea/kotlin-gradle-tooling/src/META-INF/services/org.jetbrains.plugins.gradle.tooling.ModelBuilderService +++ b/idea/kotlin-gradle-tooling/src/META-INF/services/org.jetbrains.plugins.gradle.tooling.ModelBuilderService @@ -1 +1,2 @@ org.jetbrains.kotlin.gradle.KotlinGradleModelBuilder +org.jetbrains.kotlin.gradle.KotlinMPPGradleModelBuilder diff --git a/idea/kotlin-gradle-tooling/src/utils.kt b/idea/kotlin-gradle-tooling/src/utils.kt new file mode 100644 index 00000000000..7a04f48a1a0 --- /dev/null +++ b/idea/kotlin-gradle-tooling/src/utils.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. 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 + +fun Class<*>.getMethodOrNull(name: String, vararg parameterTypes: Class<*>) = + try { + getMethod(name, *parameterTypes) + } catch (e: Exception) { + null + } + +fun Class<*>.getDeclaredMethodOrNull(name: String, vararg parameterTypes: Class<*>) = + try { + getDeclaredMethod(name, *parameterTypes)?.also { it.isAccessible = true } + } catch (e: Exception) { + null + } \ No newline at end of file diff --git a/idea/src/META-INF/android.xml b/idea/src/META-INF/android.xml index c2ac8d2e142..272eac57ce5 100644 --- a/idea/src/META-INF/android.xml +++ b/idea/src/META-INF/android.xml @@ -72,10 +72,12 @@ + + diff --git a/idea/src/META-INF/android.xml.173 b/idea/src/META-INF/android.xml.173 index 34f5f836571..6eebd823d99 100644 --- a/idea/src/META-INF/android.xml.173 +++ b/idea/src/META-INF/android.xml.173 @@ -70,10 +70,12 @@ + + diff --git a/idea/src/META-INF/android.xml.as31 b/idea/src/META-INF/android.xml.as31 index babdbcbcc4b..db057088f3e 100644 --- a/idea/src/META-INF/android.xml.as31 +++ b/idea/src/META-INF/android.xml.as31 @@ -72,10 +72,12 @@ + + diff --git a/idea/src/META-INF/android.xml.as32 b/idea/src/META-INF/android.xml.as32 index babdbcbcc4b..db057088f3e 100644 --- a/idea/src/META-INF/android.xml.as32 +++ b/idea/src/META-INF/android.xml.as32 @@ -72,10 +72,12 @@ + + diff --git a/idea/src/META-INF/gradle-java.xml b/idea/src/META-INF/gradle-java.xml index 1a8d0a6a72c..b406ff1da2c 100644 --- a/idea/src/META-INF/gradle-java.xml +++ b/idea/src/META-INF/gradle-java.xml @@ -13,12 +13,15 @@ + + + + + + + @@ -27,6 +28,8 @@ + + + @@ -27,6 +28,8 @@ + + , kotlinFacet: KotlinFacet, modelsProvider: IdeModifiableModelsProvider? +) { + val compilerArgumentsClass = kotlinFacet.configuration.settings.compilerArguments?.javaClass ?: return + val currentArgumentsBean = compilerArgumentsClass.newInstance() + val defaultArgumentsBean = compilerArgumentsClass.newInstance() + parseCommandLineArguments(defaultArguments, defaultArgumentsBean) + parseCommandLineArguments(arguments, currentArgumentsBean) + applyCompilerArgumentsToFacet(currentArgumentsBean, defaultArgumentsBean, kotlinFacet, modelsProvider) +} + +fun applyCompilerArgumentsToFacet( + arguments: CommonCompilerArguments, + defaultArguments: CommonCompilerArguments?, + kotlinFacet: KotlinFacet, + modelsProvider: IdeModifiableModelsProvider ) { with(kotlinFacet.configuration.settings) { val compilerArguments = this.compilerArguments ?: return - val defaultCompilerArguments = compilerArguments::class.java.newInstance() - parseCommandLineArguments(defaultArguments, defaultCompilerArguments) + val defaultCompilerArguments = defaultArguments?.let { copyBean(it) } ?: compilerArguments::class.java.newInstance() defaultCompilerArguments.convertPathsToSystemIndependent() - parseCommandLineArguments(arguments, compilerArguments) - + val emptyArgs = compilerArguments::class.java.newInstance() + copyBeanTo(arguments, compilerArguments) { property, value -> value != property.get(emptyArgs) } compilerArguments.convertPathsToSystemIndependent() // Retain only fields exposed (and not explicitly ignored) in facet configuration editor.