Kotlin Facet: Support "implements" relation between modules

#KT-17593 Fixed
This commit is contained in:
Alexey Sedunov
2017-08-15 19:00:47 +03:00
parent 8ebc766b5b
commit bdad58cec6
6 changed files with 288 additions and 4 deletions
@@ -45,6 +45,8 @@ var DataNode<ModuleData>.coroutines
by UserDataProperty(Key.create<String>("KOTLIN_COROUTINES")) by UserDataProperty(Key.create<String>("KOTLIN_COROUTINES"))
var DataNode<ModuleData>.platformPluginId var DataNode<ModuleData>.platformPluginId
by UserDataProperty(Key.create<String>("PLATFORM_PLUGIN_ID")) by UserDataProperty(Key.create<String>("PLATFORM_PLUGIN_ID"))
var DataNode<ModuleData>.implementedModule
by UserDataProperty(Key.create<DataNode<ModuleData>>("IMPLEMENTS"))
class KotlinGradleProjectResolverExtension : AbstractProjectResolverExtension() { class KotlinGradleProjectResolverExtension : AbstractProjectResolverExtension() {
override fun getToolingExtensionsClasses(): Set<Class<out Any>> { override fun getToolingExtensionsClasses(): Set<Class<out Any>> {
@@ -68,6 +70,7 @@ class KotlinGradleProjectResolverExtension : AbstractProjectResolverExtension()
ideModule.compilerArgumentsBySourceSet = gradleModel.compilerArgumentsBySourceSet ideModule.compilerArgumentsBySourceSet = gradleModel.compilerArgumentsBySourceSet
ideModule.coroutines = gradleModel.coroutines ideModule.coroutines = gradleModel.coroutines
ideModule.platformPluginId = gradleModel.platformPluginId ideModule.platformPluginId = gradleModel.platformPluginId
ideModule.implementedModule = gradleModel.implements?.let { findModule(ideProject, it) }
super.populateModuleDependencies(gradleModule, ideModule, ideProject) super.populateModuleDependencies(gradleModule, ideModule, ideProject)
} }
@@ -20,13 +20,13 @@ import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.externalSystem.model.DataNode import com.intellij.openapi.externalSystem.model.DataNode
import com.intellij.openapi.externalSystem.model.ProjectKeys import com.intellij.openapi.externalSystem.model.ProjectKeys
import com.intellij.openapi.externalSystem.model.project.LibraryData import com.intellij.openapi.externalSystem.model.project.LibraryData
import com.intellij.openapi.externalSystem.model.project.LibraryDependencyData
import com.intellij.openapi.externalSystem.model.project.ModuleData import com.intellij.openapi.externalSystem.model.project.ModuleData
import com.intellij.openapi.externalSystem.model.project.ProjectData import com.intellij.openapi.externalSystem.model.project.ProjectData
import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProvider import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProvider
import com.intellij.openapi.externalSystem.service.project.manage.AbstractProjectDataService import com.intellij.openapi.externalSystem.service.project.manage.AbstractProjectDataService
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import com.intellij.openapi.module.Module import com.intellij.openapi.module.Module
import com.intellij.openapi.module.isQualifiedModuleNamesEnabled
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.OrderRootType import com.intellij.openapi.roots.OrderRootType
import com.intellij.openapi.roots.impl.libraries.LibraryEx import com.intellij.openapi.roots.impl.libraries.LibraryEx
@@ -192,9 +192,9 @@ private fun configureFacetByGradleModule(
val kotlinFacet = ideModule.getOrCreateFacet(modelsProvider, false) val kotlinFacet = ideModule.getOrCreateFacet(modelsProvider, false)
kotlinFacet.configureFacet(compilerVersion, coroutinesProperty, platformKind, modelsProvider) kotlinFacet.configureFacet(compilerVersion, coroutinesProperty, platformKind, modelsProvider)
val sourceSetName = sourceSetNode?.data?.id?.let { it.substring(it.lastIndexOf(':') + 1) } ?: "main" val sourceSetName = sourceSetNode?.data?.id?.let { it.substring(it.lastIndexOf(':') + 1) }
val argsInfo = moduleNode.compilerArgumentsBySourceSet?.get(sourceSetName) val argsInfo = moduleNode.compilerArgumentsBySourceSet?.get(sourceSetName ?: "main")
if (argsInfo != null) { if (argsInfo != null) {
val currentCompilerArguments = argsInfo.currentArguments val currentCompilerArguments = argsInfo.currentArguments
val defaultCompilerArguments = argsInfo.defaultArguments val defaultCompilerArguments = argsInfo.defaultArguments
@@ -205,9 +205,18 @@ private fun configureFacetByGradleModule(
adjustClasspath(kotlinFacet, dependencyClasspath) adjustClasspath(kotlinFacet, dependencyClasspath)
} }
kotlinFacet.configuration.settings.implementedModuleName = getImplementedModuleName(moduleNode, sourceSetName)
return kotlinFacet return kotlinFacet
} }
private fun getImplementedModuleName(moduleNode: DataNode<ModuleData>, sourceSetName: String?): String? {
val baseModuleName = moduleNode.implementedModule?.data?.internalName
if (baseModuleName == null || sourceSetName == null) return baseModuleName
val delimiter = if(isQualifiedModuleNamesEnabled()) "." else "_"
return "$baseModuleName$delimiter$sourceSetName"
}
private fun adjustClasspath(kotlinFacet: KotlinFacet, dependencyClasspath: List<String>) { private fun adjustClasspath(kotlinFacet: KotlinFacet, dependencyClasspath: List<String>) {
if (dependencyClasspath.isEmpty()) return if (dependencyClasspath.isEmpty()) return
val arguments = kotlinFacet.configuration.settings.compilerArguments as? K2JVMCompilerArguments ?: return val arguments = kotlinFacet.configuration.settings.compilerArguments as? K2JVMCompilerArguments ?: return
@@ -1319,6 +1319,265 @@ compileTestKotlin {
} }
} }
@Test
fun testImplementsDependency() {
createProjectSubFile(
"build.gradle",
"""
buildscript {
repositories {
mavenCentral()
maven {
url 'http://dl.bintray.com/kotlin/kotlin-eap-1.1'
}
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.0")
}
}
apply plugin: 'kotlin-platform-common'
repositories {
mavenCentral()
maven {
url 'http://dl.bintray.com/kotlin/kotlin-eap-1.1'
}
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-common:1.1.0"
}
""".trimIndent()
)
createProjectSubFile(
"settings.gradle",
"""
rootProject.name = 'MultiTest'
include 'MultiTest-jvm', 'MultiTest-js'
""".trimIndent()
)
createProjectSubFile(
"MultiTest-js/build.gradle",
"""
buildscript {
repositories {
mavenCentral()
maven {
url 'http://dl.bintray.com/kotlin/kotlin-eap-1.1'
}
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.0")
}
}
apply plugin: 'kotlin-platform-js'
repositories {
mavenCentral()
maven {
url 'http://dl.bintray.com/kotlin/kotlin-eap-1.1'
}
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-js:1.1.0"
implement project(":")
}
""".trimIndent()
)
createProjectSubFile(
"MultiTest-jvm/build.gradle",
"""
buildscript {
repositories {
mavenCentral()
maven {
url 'http://dl.bintray.com/kotlin/kotlin-eap-1.1'
}
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.0")
}
}
apply plugin: 'kotlin-platform-jvm'
repositories {
mavenCentral()
maven {
url 'http://dl.bintray.com/kotlin/kotlin-eap-1.1'
}
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:1.1.0"
implement project(":")
}
""".trimIndent()
)
importProject()
Assert.assertEquals("MultiTest_main", facetSettings("MultiTest-jvm_main").implementedModuleName)
Assert.assertEquals("MultiTest_test", facetSettings("MultiTest-jvm_test").implementedModuleName)
Assert.assertEquals("MultiTest_main", facetSettings("MultiTest-js_main").implementedModuleName)
Assert.assertEquals("MultiTest_test", facetSettings("MultiTest-js_test").implementedModuleName)
}
@Test
fun testImplementsDependencyWithCustomSourceSets() {
createProjectSubFile(
"build.gradle",
"""
buildscript {
repositories {
mavenCentral()
maven {
url 'http://dl.bintray.com/kotlin/kotlin-eap-1.1'
}
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.0")
}
}
apply plugin: 'kotlin-platform-common'
sourceSets {
myMain {
kotlin {
srcDir 'src'
}
}
myTest {
kotlin {
srcDir 'test'
}
}
}
repositories {
mavenCentral()
maven {
url 'http://dl.bintray.com/kotlin/kotlin-eap-1.1'
}
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-common:1.1.0"
}
""".trimIndent()
)
createProjectSubFile(
"settings.gradle",
"""
rootProject.name = 'MultiTest'
include 'MultiTest-jvm', 'MultiTest-js'
""".trimIndent()
)
createProjectSubFile(
"MultiTest-js/build.gradle",
"""
buildscript {
repositories {
mavenCentral()
maven {
url 'http://dl.bintray.com/kotlin/kotlin-eap-1.1'
}
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.0")
}
}
apply plugin: 'kotlin-platform-js'
sourceSets {
myMain {
kotlin {
srcDir 'src'
}
}
myTest {
kotlin {
srcDir 'test'
}
}
}
repositories {
mavenCentral()
maven {
url 'http://dl.bintray.com/kotlin/kotlin-eap-1.1'
}
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-js:1.1.0"
implement project(":")
}
""".trimIndent()
)
createProjectSubFile(
"MultiTest-jvm/build.gradle",
"""
buildscript {
repositories {
mavenCentral()
maven {
url 'http://dl.bintray.com/kotlin/kotlin-eap-1.1'
}
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.1.0")
}
}
apply plugin: 'kotlin-platform-jvm'
sourceSets {
myMain {
kotlin {
srcDir 'src'
}
}
myTest {
kotlin {
srcDir 'test'
}
}
}
repositories {
mavenCentral()
maven {
url 'http://dl.bintray.com/kotlin/kotlin-eap-1.1'
}
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:1.1.0"
implement project(":")
}
""".trimIndent()
)
importProject()
Assert.assertEquals("MultiTest_myMain", facetSettings("MultiTest-jvm_myMain").implementedModuleName)
Assert.assertEquals("MultiTest_myTest", facetSettings("MultiTest-jvm_myTest").implementedModuleName)
Assert.assertEquals("MultiTest_myMain", facetSettings("MultiTest-js_myMain").implementedModuleName)
Assert.assertEquals("MultiTest_myTest", facetSettings("MultiTest-js_myTest").implementedModuleName)
}
private fun assertAllModulesConfigured() { private fun assertAllModulesConfigured() {
runReadAction { runReadAction {
for (moduleGroup in ModuleSourceRootMap(myProject).groupByBaseModules(myProject.allModules())) { for (moduleGroup in ModuleSourceRootMap(myProject).groupByBaseModules(myProject.allModules())) {
@@ -149,6 +149,8 @@ class KotlinFacetSettings {
LanguageFeature.State.ENABLED_WITH_ERROR, LanguageFeature.State.DISABLED -> CommonCompilerArguments.ERROR LanguageFeature.State.ENABLED_WITH_ERROR, LanguageFeature.State.DISABLED -> CommonCompilerArguments.ERROR
} }
} }
var implementedModuleName: String? = null
} }
fun TargetPlatformKind<*>.createCompilerArguments(init: CommonCompilerArguments.() -> Unit = {}): CommonCompilerArguments { fun TargetPlatformKind<*>.createCompilerArguments(init: CommonCompilerArguments.() -> Unit = {}): CommonCompilerArguments {
@@ -22,6 +22,7 @@ import com.intellij.util.xmlb.SkipDefaultsSerializationFilter
import com.intellij.util.xmlb.XmlSerializer import com.intellij.util.xmlb.XmlSerializer
import org.jdom.DataConversionException import org.jdom.DataConversionException
import org.jdom.Element import org.jdom.Element
import org.jdom.Text
import org.jetbrains.kotlin.cli.common.arguments.* import org.jetbrains.kotlin.cli.common.arguments.*
import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.load.java.JvmAbi
import java.lang.reflect.Modifier import java.lang.reflect.Modifier
@@ -95,6 +96,9 @@ private fun readV2AndLaterConfig(element: Element): KotlinFacetSettings {
element.getAttributeValue("useProjectSettings")?.let { useProjectSettings = it.toBoolean() } element.getAttributeValue("useProjectSettings")?.let { useProjectSettings = it.toBoolean() }
val platformName = element.getAttributeValue("platform") val platformName = element.getAttributeValue("platform")
val platformKind = TargetPlatformKind.ALL_PLATFORMS.firstOrNull { it.description == platformName } ?: TargetPlatformKind.DEFAULT_PLATFORM val platformKind = TargetPlatformKind.ALL_PLATFORMS.firstOrNull { it.description == platformName } ?: TargetPlatformKind.DEFAULT_PLATFORM
element.getChild("implements")?.let {
implementedModuleName = (element.content.firstOrNull() as? Text)?.textTrim
}
element.getChild("compilerSettings")?.let { element.getChild("compilerSettings")?.let {
compilerSettings = CompilerSettings() compilerSettings = CompilerSettings()
XmlSerializer.deserializeInto(compilerSettings!!, it) XmlSerializer.deserializeInto(compilerSettings!!, it)
@@ -224,6 +228,9 @@ private fun KotlinFacetSettings.writeLatestConfig(element: Element) {
if (!useProjectSettings) { if (!useProjectSettings) {
element.setAttribute("useProjectSettings", useProjectSettings.toString()) element.setAttribute("useProjectSettings", useProjectSettings.toString())
} }
implementedModuleName?.let {
element.addContent(Element("implements").apply { addContent(it) })
}
compilerSettings?.let { copyBean(it) }?.let { compilerSettings?.let { copyBean(it) }?.let {
it.convertPathsToSystemIndependent() it.convertPathsToSystemIndependent()
buildChildElement(element, "compilerSettings", it, filter) buildChildElement(element, "compilerSettings", it, filter)
@@ -47,6 +47,7 @@ interface KotlinGradleModel : Serializable {
val compilerArgumentsBySourceSet: CompilerArgumentsBySourceSet val compilerArgumentsBySourceSet: CompilerArgumentsBySourceSet
val coroutines: String? val coroutines: String?
val platformPluginId: String? val platformPluginId: String?
val implements: String?
val transitiveCommonDependencies: Set<String> val transitiveCommonDependencies: Set<String>
} }
@@ -55,6 +56,7 @@ class KotlinGradleModelImpl(
override val compilerArgumentsBySourceSet: CompilerArgumentsBySourceSet, override val compilerArgumentsBySourceSet: CompilerArgumentsBySourceSet,
override val coroutines: String?, override val coroutines: String?,
override val platformPluginId: String?, override val platformPluginId: String?,
override val implements: String?,
override val transitiveCommonDependencies: Set<String> override val transitiveCommonDependencies: Set<String>
) : KotlinGradleModel ) : KotlinGradleModel
@@ -191,13 +193,15 @@ class KotlinGradleModelBuilder : ModelBuilderService {
} }
val platform = platformPluginId ?: pluginToPlatform.entries.singleOrNull { project.plugins.findPlugin(it.key) != null }?.value val platform = platformPluginId ?: pluginToPlatform.entries.singleOrNull { project.plugins.findPlugin(it.key) != null }?.value
val transitiveCommon = getImplements(project)?.let { transitiveCommonDependencies(it) } ?: emptySet() val implementedProject = getImplements(project)
val transitiveCommon = implementedProject?.let { transitiveCommonDependencies(it) } ?: emptySet()
return KotlinGradleModelImpl( return KotlinGradleModelImpl(
kotlinPluginId != null || platformPluginId != null, kotlinPluginId != null || platformPluginId != null,
compilerArgumentsBySourceSet, compilerArgumentsBySourceSet,
getCoroutines(project), getCoroutines(project),
platform, platform,
implementedProject?.pathOrName(),
transitiveCommon transitiveCommon
) )
} }