From 7c30e80382d7de57a42e05bb3e03757c918fdb58 Mon Sep 17 00:00:00 2001 From: Vyacheslav Gerasimov Date: Thu, 23 Mar 2017 16:49:55 +0300 Subject: [PATCH] as31: Kotlin Facet: Configure facet on Gradle project sync in Android Studio 2.3 #KT-15909 Fixed (cherry picked from commit 6a490bc681f968ee209580f683804b1ee3baee96) --- ...droidModuleModelProjectDataService.kt.as31 | 53 ++++ .../KotlinGradleSourceSetDataService.kt.as31 | 253 ++++++++++++++++++ idea/src/META-INF/android.xml.as31 | 108 ++++++++ 3 files changed, 414 insertions(+) create mode 100644 idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinGradleAndroidModuleModelProjectDataService.kt.as31 create mode 100644 idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleSourceSetDataService.kt.as31 create mode 100644 idea/src/META-INF/android.xml.as31 diff --git a/idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinGradleAndroidModuleModelProjectDataService.kt.as31 b/idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinGradleAndroidModuleModelProjectDataService.kt.as31 new file mode 100644 index 00000000000..6807f56fcb5 --- /dev/null +++ b/idea/idea-android/src/org/jetbrains/kotlin/android/configure/KotlinGradleAndroidModuleModelProjectDataService.kt.as31 @@ -0,0 +1,53 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.android.configure + +import com.android.tools.idea.gradle.project.model.AndroidModuleModel +import com.intellij.openapi.externalSystem.model.DataNode +import com.intellij.openapi.externalSystem.model.Key +import com.intellij.openapi.externalSystem.model.ProjectKeys +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 org.jetbrains.kotlin.idea.configuration.GradleProjectImportHandler +import org.jetbrains.kotlin.idea.configuration.configureFacetByGradleModule + +class KotlinGradleAndroidModuleModelProjectDataService : AbstractProjectDataService() { + companion object { + val KEY = Key(AndroidModuleModel::class.qualifiedName!!, 0) + } + + override fun getTargetDataKey() = KEY + + override fun postProcess( + toImport: MutableCollection>, + projectData: ProjectData?, + project: Project, + modelsProvider: IdeModifiableModelsProvider + ) { + super.postProcess(toImport, projectData, project, modelsProvider) + for (moduleModelNode in toImport) { + val moduleNode = ExternalSystemApiUtil.findParent(moduleModelNode, ProjectKeys.MODULE) ?: continue + val moduleData = moduleNode.data + val ideModule = modelsProvider.findIdeModule(moduleData) ?: continue + val kotlinFacet = configureFacetByGradleModule(moduleNode, null, ideModule, modelsProvider) ?: continue + GradleProjectImportHandler.getInstances(project).forEach { it.importByModule(kotlinFacet, moduleNode) } + } + } +} \ No newline at end of file diff --git a/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleSourceSetDataService.kt.as31 b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleSourceSetDataService.kt.as31 new file mode 100644 index 00000000000..2b4903540b1 --- /dev/null +++ b/idea/idea-gradle/src/org/jetbrains/kotlin/idea/configuration/KotlinGradleSourceSetDataService.kt.as31 @@ -0,0 +1,253 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.configuration + +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.externalSystem.model.DataNode +import com.intellij.openapi.externalSystem.model.ProjectKeys +import com.intellij.openapi.externalSystem.model.project.LibraryData +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.OrderRootType +import com.intellij.openapi.roots.impl.libraries.LibraryEx +import com.intellij.openapi.roots.impl.libraries.LibraryImpl +import com.intellij.openapi.roots.libraries.PersistentLibraryKind +import com.intellij.util.PathUtil +import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments +import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments +import org.jetbrains.kotlin.cli.common.arguments.parseCommandLineArguments +import org.jetbrains.kotlin.config.CoroutineSupport +import org.jetbrains.kotlin.config.JvmTarget +import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.config.TargetPlatformKind +import org.jetbrains.kotlin.extensions.ProjectExtensionDescriptor +import org.jetbrains.kotlin.idea.facet.* +import org.jetbrains.kotlin.idea.framework.CommonLibraryKind +import org.jetbrains.kotlin.idea.framework.JSLibraryKind +import org.jetbrains.kotlin.idea.framework.detectLibraryKind +import org.jetbrains.kotlin.idea.inspections.gradle.findAll +import org.jetbrains.kotlin.idea.inspections.gradle.findKotlinPluginVersion +import org.jetbrains.kotlin.idea.inspections.gradle.getResolvedKotlinStdlibVersionByModuleData +import org.jetbrains.plugins.gradle.model.data.BuildScriptClasspathData +import org.jetbrains.plugins.gradle.model.data.GradleSourceSetData +import java.io.File +import java.util.* + +interface GradleProjectImportHandler { + companion object : ProjectExtensionDescriptor( + "org.jetbrains.kotlin.gradleProjectImportHandler", + GradleProjectImportHandler::class.java + ) + + fun importBySourceSet(facet: KotlinFacet, sourceSetNode: DataNode) + fun importByModule(facet: KotlinFacet, moduleNode: DataNode) +} + +class KotlinGradleSourceSetDataService : AbstractProjectDataService() { + override fun getTargetDataKey() = GradleSourceSetData.KEY + + override fun postProcess( + toImport: Collection>, + projectData: ProjectData?, + project: Project, + modelsProvider: IdeModifiableModelsProvider + ) { + for (sourceSetNode in toImport) { + val sourceSetData = sourceSetNode.data + val ideModule = modelsProvider.findIdeModule(sourceSetData) ?: continue + + val moduleNode = ExternalSystemApiUtil.findParent(sourceSetNode, ProjectKeys.MODULE) ?: continue + val kotlinFacet = configureFacetByGradleModule(moduleNode, sourceSetNode, ideModule, modelsProvider) ?: continue + GradleProjectImportHandler.getInstances(project).forEach { it.importBySourceSet(kotlinFacet, sourceSetNode) } + } + } +} + +class KotlinGradleProjectDataService : AbstractProjectDataService() { + override fun getTargetDataKey() = ProjectKeys.MODULE + + override fun postProcess( + toImport: MutableCollection>, + projectData: ProjectData?, + project: Project, + modelsProvider: IdeModifiableModelsProvider + ) { + for (moduleNode in toImport) { + // If source sets are present, configure facets in the their modules + if (ExternalSystemApiUtil.getChildren(moduleNode, GradleSourceSetData.KEY).isNotEmpty()) continue + + val moduleData = moduleNode.data + val ideModule = modelsProvider.findIdeModule(moduleData) ?: continue + val kotlinFacet = configureFacetByGradleModule(moduleNode, null, ideModule, modelsProvider) ?: continue + GradleProjectImportHandler.getInstances(project).forEach { it.importByModule(kotlinFacet, moduleNode) } + } + } +} + +class KotlinGradleLibraryDataService : AbstractProjectDataService() { + override fun getTargetDataKey() = ProjectKeys.LIBRARY + + override fun postProcess( + toImport: MutableCollection>, + projectData: ProjectData?, + project: Project, + modelsProvider: IdeModifiableModelsProvider + ) { + if (toImport.isEmpty()) return + val projectDataNode = toImport.first().parent!! + @Suppress("UNCHECKED_CAST") + val moduleDataNodes = projectDataNode.children.filter { it.data is ModuleData } as List> + val anyNonJvmModules = moduleDataNodes.any { detectPlatformByPlugin(it)?.takeIf { it !is TargetPlatformKind.Jvm } != null } + for (libraryDataNode in toImport) { + val ideLibrary = modelsProvider.findIdeLibrary(libraryDataNode.data) ?: continue + + val modifiableModel = modelsProvider.getModifiableLibraryModel(ideLibrary) as LibraryEx.ModifiableModelEx + if (anyNonJvmModules || ideLibrary.name?.looksAsNonJvmLibraryName() == true) { + detectLibraryKind(modifiableModel.getFiles(OrderRootType.CLASSES))?.let { modifiableModel.kind = it } + } else if (ideLibrary is LibraryImpl && (ideLibrary.kind === JSLibraryKind || ideLibrary.kind === CommonLibraryKind)) { + resetLibraryKind(modifiableModel) + } + } + } + + private fun String.looksAsNonJvmLibraryName() = nonJvmSuffixes.any { it in this } + + private fun resetLibraryKind(modifiableModel: LibraryEx.ModifiableModelEx) { + try { + val cls = LibraryImpl::class.java + // Don't use name-based lookup because field names are scrambled in IDEA Ultimate + for (field in cls.declaredFields) { + if (field.type == PersistentLibraryKind::class.java) { + field.isAccessible = true + field.set(modifiableModel, null) + return + } + } + LOG.info("Could not find field of type PersistentLibraryKind in LibraryImpl.class") + } catch (e: Exception) { + LOG.info("Failed to reset library kind", e) + } + } + + companion object { + val LOG = Logger.getInstance(KotlinGradleLibraryDataService::class.java) + + val nonJvmSuffixes = listOf("-common", "-js", "-native", "-kjsm") + } +} + +fun detectPlatformByPlugin(moduleNode: DataNode): TargetPlatformKind<*>? { + return when (moduleNode.platformPluginId) { + "kotlin-platform-jvm" -> TargetPlatformKind.Jvm[JvmTarget.JVM_1_6] + "kotlin-platform-js" -> TargetPlatformKind.JavaScript + "kotlin-platform-common" -> TargetPlatformKind.Common + else -> null + } +} + +private fun detectPlatformByLibrary(moduleNode: DataNode): TargetPlatformKind<*>? { + val detectedPlatforms = + mavenLibraryIdToPlatform.entries + .filter { moduleNode.getResolvedKotlinStdlibVersionByModuleData(listOf(it.key)) != null } + .map { it.value }.distinct() + return detectedPlatforms.singleOrNull() ?: detectedPlatforms.firstOrNull { it != TargetPlatformKind.Common } +} + +fun configureFacetByGradleModule( + moduleNode: DataNode, + sourceSetNode: DataNode?, + ideModule: Module, + modelsProvider: IdeModifiableModelsProvider +): KotlinFacet? { + if (!moduleNode.isResolved) return null + + if (!moduleNode.hasKotlinPlugin) { + val facetModel = modelsProvider.getModifiableFacetModel(ideModule) + val facet = facetModel.getFacetByType(KotlinFacetType.TYPE_ID) + if (facet != null) { + facetModel.removeFacet(facet) + } + return null + } + + val compilerVersion = moduleNode.findAll(BuildScriptClasspathData.KEY).firstOrNull()?.data?.let(::findKotlinPluginVersion) + ?: return null + val platformKind = detectPlatformByPlugin(moduleNode) ?: detectPlatformByLibrary(moduleNode) + + val coroutinesProperty = CoroutineSupport.byCompilerArgument( + moduleNode.coroutines ?: findKotlinCoroutinesProperty(ideModule.project) + ) + + val kotlinFacet = ideModule.getOrCreateFacet(modelsProvider, false) + kotlinFacet.configureFacet(compilerVersion, coroutinesProperty, platformKind, modelsProvider) + + val sourceSetName = sourceSetNode?.data?.id?.let { it.substring(it.lastIndexOf(':') + 1) } + + val argsInfo = moduleNode.compilerArgumentsBySourceSet?.get(sourceSetName ?: "main") + if (argsInfo != null) { + val currentCompilerArguments = argsInfo.currentArguments + val defaultCompilerArguments = argsInfo.defaultArguments + val dependencyClasspath = argsInfo.dependencyClasspath.map { PathUtil.toSystemIndependentName(it) } + if (currentCompilerArguments.isNotEmpty()) { + parseCompilerArgumentsToFacet(currentCompilerArguments, defaultCompilerArguments, kotlinFacet, modelsProvider) + } + adjustClasspath(kotlinFacet, dependencyClasspath) + } + + with(kotlinFacet.configuration.settings) { + implementedModuleNames = (sourceSetNode ?: moduleNode).implementedModuleNames + productionOutputPath = getExplicitOutputPath(moduleNode, platformKind, "main") + testOutputPath = getExplicitOutputPath(moduleNode, platformKind, "test") + } + + kotlinFacet.noVersionAutoAdvance() + + return kotlinFacet +} + +private fun getExplicitOutputPath(moduleNode: DataNode, platformKind: TargetPlatformKind<*>?, sourceSet: String): String? { + if (platformKind !== TargetPlatformKind.JavaScript) return null + val k2jsArgumentList = moduleNode.compilerArgumentsBySourceSet?.get(sourceSet)?.currentArguments ?: return null + return K2JSCompilerArguments().apply { parseCommandLineArguments(k2jsArgumentList, this) }.outputFile +} + +private 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() + if (fullClasspath.isEmpty()) return + val newClasspath = fullClasspath - dependencyClasspath + arguments.classpath = if (newClasspath.isNotEmpty()) newClasspath.joinToString(File.pathSeparator) else null +} + +private val gradlePropertyFiles = listOf("local.properties", "gradle.properties") + +private fun findKotlinCoroutinesProperty(project: Project): String { + for (propertyFileName in gradlePropertyFiles) { + val propertyFile = project.baseDir.findChild(propertyFileName) ?: continue + val properties = Properties() + properties.load(propertyFile.inputStream) + properties.getProperty("kotlin.coroutines")?.let { return it } + } + + return CoroutineSupport.getCompilerArgument(LanguageFeature.Coroutines.defaultState) +} diff --git a/idea/src/META-INF/android.xml.as31 b/idea/src/META-INF/android.xml.as31 new file mode 100644 index 00000000000..fd7d903225a --- /dev/null +++ b/idea/src/META-INF/android.xml.as31 @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + org.jetbrains.kotlin.android.intention.KotlinAndroidAddStringResource + Kotlin Android + + + + + + org.jetbrains.kotlin.android.intention.AddActivityToManifest + Kotlin Android + + + + org.jetbrains.kotlin.android.intention.AddServiceToManifest + Kotlin Android + + + + org.jetbrains.kotlin.android.intention.AddBroadcastReceiverToManifest + Kotlin Android + + + org.jetbrains.kotlin.android.intention.ImplementParcelableAction + Kotlin Android + + + + org.jetbrains.kotlin.android.intention.RemoveParcelableAction + Kotlin Android + + + + org.jetbrains.kotlin.android.intention.RedoParcelableAction + Kotlin Android + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +