From 8e72a1211b79ade007042493c920f326d2c56a04 Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Thu, 3 Aug 2017 23:27:03 +0300 Subject: [PATCH] Kapt: Add IDEA integration (KT-19097, KT-17923, KT-19823) --- .idea/artifacts/KotlinPlugin.xml | 3 + .idea/modules.xml | 1 + .../src/KotlinGradleModelBuilder.kt | 27 +-- idea/src/META-INF/gradle.xml | 1 + .../plugin/android/Android25ProjectHandler.kt | 2 +- .../internal/Kapt3KotlinGradleSubplugin.kt | 36 ++-- .../kotlin/gradle/plugin/KotlinPlugin.kt | 4 +- plugins/kapt3-idea/kapt3-idea.iml | 19 ++ ...plugins.gradle.tooling.ModelBuilderService | 1 + .../kapt/idea/KaptProjectResolverExtension.kt | 190 ++++++++++++++++++ 10 files changed, 253 insertions(+), 31 deletions(-) create mode 100644 plugins/kapt3-idea/kapt3-idea.iml create mode 100644 plugins/kapt3-idea/src/META-INF/services/org.jetbrains.plugins.gradle.tooling.ModelBuilderService create mode 100644 plugins/kapt3-idea/src/org/jetbrains/kotlin/kapt/idea/KaptProjectResolverExtension.kt diff --git a/.idea/artifacts/KotlinPlugin.xml b/.idea/artifacts/KotlinPlugin.xml index 1c420dbd6a4..89b5c746b88 100644 --- a/.idea/artifacts/KotlinPlugin.xml +++ b/.idea/artifacts/KotlinPlugin.xml @@ -109,6 +109,9 @@ + + + diff --git a/.idea/modules.xml b/.idea/modules.xml index 520fa2e16e7..1e6d1ac1a68 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -78,6 +78,7 @@ + diff --git a/idea/kotlin-gradle-tooling/src/KotlinGradleModelBuilder.kt b/idea/kotlin-gradle-tooling/src/KotlinGradleModelBuilder.kt index 3e90366eca1..102ac18ceed 100644 --- a/idea/kotlin-gradle-tooling/src/KotlinGradleModelBuilder.kt +++ b/idea/kotlin-gradle-tooling/src/KotlinGradleModelBuilder.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.gradle import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.artifacts.ProjectDependency +import org.gradle.tooling.ModelBuilder import org.jetbrains.plugins.gradle.tooling.ErrorMessageBuilder import org.jetbrains.plugins.gradle.tooling.ModelBuilderService import java.io.File @@ -60,7 +61,7 @@ class KotlinGradleModelImpl( override val transitiveCommonDependencies: Set ) : KotlinGradleModel -class KotlinGradleModelBuilder : ModelBuilderService { +abstract class AbstractKotlinGradleModelBuilder : ModelBuilderService { companion object { val kotlinCompileTaskClasses = listOf("org.jetbrains.kotlin.gradle.tasks.KotlinCompile_Decorated", "org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile_Decorated") @@ -70,10 +71,20 @@ class KotlinGradleModelBuilder : ModelBuilderService { "kotlin2js" to "kotlin-platform-js" ) val kotlinPluginIds = listOf("kotlin", "kotlin2js", "kotlin-android") - private val kotlinPlatformCommonPluginId = "kotlin-platform-common" - private val ABSTRACT_KOTLIN_COMPILE_CLASS = "org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile" - } + val kotlinPlatformCommonPluginId = "kotlin-platform-common" + val ABSTRACT_KOTLIN_COMPILE_CLASS = "org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile" + fun Task.getSourceSetName(): String { + return try { + javaClass.methods.firstOrNull { it.name.startsWith("getSourceSetName") && it.parameterTypes.isEmpty() }?.invoke(this) as? String + } catch (e : InvocationTargetException) { + null // can be thrown if property is not initialized yet + } ?: "main" + } + } +} + +class KotlinGradleModelBuilder : AbstractKotlinGradleModelBuilder() { override fun getErrorMessageBuilder(project: Project, e: Exception): ErrorMessageBuilder { return ErrorMessageBuilder.create(project, e, "Gradle import errors").withDescription("Unable to build Kotlin project configuration") } @@ -168,14 +179,6 @@ class KotlinGradleModelBuilder : ModelBuilderService { } } - private fun Task.getSourceSetName(): String { - return try { - javaClass.methods.firstOrNull { it.name.startsWith("getSourceSetName") && it.parameterTypes.isEmpty() }?.invoke(this) as? String - } catch (e : InvocationTargetException) { - null // can be thrown if property is not initialized yet - } ?: "main" - } - override fun buildAll(modelName: String?, project: Project): KotlinGradleModelImpl { val kotlinPluginId = kotlinPluginIds.singleOrNull { project.plugins.findPlugin(it) != null } val platformPluginId = platformPluginIds.singleOrNull { project.plugins.findPlugin(it) != null } diff --git a/idea/src/META-INF/gradle.xml b/idea/src/META-INF/gradle.xml index f5fdce48bcb..efff1b2c218 100644 --- a/idea/src/META-INF/gradle.xml +++ b/idea/src/META-INF/gradle.xml @@ -7,6 +7,7 @@ + diff --git a/libraries/tools/kotlin-gradle-plugin/src/agp25/kotlin/org/jetbrains/kotlin/gradle/plugin/android/Android25ProjectHandler.kt b/libraries/tools/kotlin-gradle-plugin/src/agp25/kotlin/org/jetbrains/kotlin/gradle/plugin/android/Android25ProjectHandler.kt index f7e4d793808..93f4578bda9 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/agp25/kotlin/org/jetbrains/kotlin/gradle/plugin/android/Android25ProjectHandler.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/agp25/kotlin/org/jetbrains/kotlin/gradle/plugin/android/Android25ProjectHandler.kt @@ -53,7 +53,7 @@ class Android25ProjectHandler(kotlinConfigurationTools: KotlinConfigurationTools } if (Kapt3GradleSubplugin.isEnabled(project)) { // Add Kapt3 output as well, since there's no SyncOutputTask with the new API - val kaptClasssesDir = Kapt3KotlinGradleSubplugin.getKaptClasssesDir(project, getVariantName(variantData)) + val kaptClasssesDir = Kapt3GradleSubplugin.getKaptGeneratedClassesDir(project, getVariantName(variantData)) add(kaptClasssesDir) } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/Kapt3KotlinGradleSubplugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/Kapt3KotlinGradleSubplugin.kt index 9b77cb56e0e..56c2805b765 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/Kapt3KotlinGradleSubplugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/Kapt3KotlinGradleSubplugin.kt @@ -34,11 +34,26 @@ import java.io.ByteArrayOutputStream import java.io.File import java.io.ObjectOutputStream import javax.xml.bind.DatatypeConverter.printBase64Binary +import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin.Companion.getKaptGeneratedSourcesDir +import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin.Companion.getKaptGeneratedClassesDir +import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin.Companion.getKaptGeneratedKotlinSourcesDir // apply plugin: 'kotlin-kapt' class Kapt3GradleSubplugin : Plugin { companion object { fun isEnabled(project: Project) = project.plugins.findPlugin(Kapt3GradleSubplugin::class.java) != null + + @JvmStatic + fun getKaptGeneratedClassesDir(project: Project, sourceSetName: String) = + File(project.project.buildDir, "tmp/kapt3/classes/$sourceSetName") + + @JvmStatic + fun getKaptGeneratedSourcesDir(project: Project, sourceSetName: String) = + File(project.project.buildDir, "generated/source/kapt/$sourceSetName") + + @JvmStatic + fun getKaptGeneratedKotlinSourcesDir(project: Project, sourceSetName: String) = + File(project.project.buildDir, "generated/source/kaptKotlin/$sourceSetName") } override fun apply(project: Project) {} @@ -77,23 +92,12 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin { } fun findMainKaptConfiguration(project: Project) = project.findKaptConfiguration(MAIN_KAPT_CONFIGURATION_NAME) - - fun getKaptClasssesDir(project: Project, sourceSetName: String): File = - File(project.project.buildDir, "tmp/kapt3/classes/$sourceSetName") } private val kotlinToKaptGenerateStubsTasksMap = mutableMapOf() override fun isApplicable(project: Project, task: AbstractCompile) = task is KotlinCompile && Kapt3GradleSubplugin.isEnabled(project) - fun getKaptGeneratedDir(project: Project, sourceSetName: String): File { - return File(project.project.buildDir, "generated/source/kapt/$sourceSetName") - } - - fun getKaptGeneratedDirForKotlin(project: Project, sourceSetName: String): File { - return File(project.project.buildDir, "generated/source/kaptKotlin/$sourceSetName") - } - private fun Kapt3SubpluginContext.getKaptStubsDir() = createAndReturnTemporaryKaptDirectory("stubs") private fun Kapt3SubpluginContext.getKaptIncrementalDataDir() = createAndReturnTemporaryKaptDirectory("incrementalData") @@ -113,9 +117,9 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin { val javaSourceSet: SourceSet?, val kaptExtension: KaptExtension, val kaptClasspath: MutableList) { - val sourcesOutputDir = getKaptGeneratedDir(project, sourceSetName) - val kotlinSourcesOutputDir = getKaptGeneratedDirForKotlin(project, sourceSetName) - val classesOutputDir = getKaptClasssesDir(project, sourceSetName) + val sourcesOutputDir = getKaptGeneratedSourcesDir(project, sourceSetName) + val kotlinSourcesOutputDir = getKaptGeneratedKotlinSourcesDir(project, sourceSetName) + val classesOutputDir = getKaptGeneratedClassesDir(project, sourceSetName) val kaptClasspathArtifacts = project .resolveSubpluginArtifacts(listOf(this@Kapt3KotlinGradleSubplugin)) @@ -193,7 +197,7 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin { private fun Kapt3SubpluginContext.buildOptions(aptMode: String): List { val pluginOptions = mutableListOf() - val generatedFilesDir = getKaptGeneratedDir(project, sourceSetName) + val generatedFilesDir = getKaptGeneratedSourcesDir(project, sourceSetName) kaptVariantData?.addJavaSourceFoldersToModel(generatedFilesDir) pluginOptions += SubpluginOption("aptMode", aptMode) @@ -207,7 +211,7 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin { javaCompile.source(generatedFilesDir) pluginOptions += SubpluginOption("sources", generatedFilesDir.canonicalPath) - pluginOptions += SubpluginOption("classes", getKaptClasssesDir(project, sourceSetName).canonicalPath) + pluginOptions += SubpluginOption("classes", getKaptGeneratedClassesDir(project, sourceSetName).canonicalPath) pluginOptions += SubpluginOption("incrementalData", getKaptIncrementalDataDir().canonicalPath) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt index 9b9fa3c2c11..d12a08e7ab7 100755 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPlugin.kt @@ -27,7 +27,7 @@ import org.jetbrains.kotlin.com.intellij.openapi.util.text.StringUtil.compareVer import org.jetbrains.kotlin.com.intellij.util.ReflectionUtil import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptionsImpl import org.jetbrains.kotlin.gradle.internal.* -import org.jetbrains.kotlin.gradle.internal.Kapt3KotlinGradleSubplugin.Companion.getKaptClasssesDir +import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin.Companion.getKaptGeneratedClassesDir import org.jetbrains.kotlin.gradle.tasks.* import org.jetbrains.kotlin.gradle.utils.ParsedGradleVersion import org.jetbrains.kotlin.gradle.utils.checkedReflection @@ -679,7 +679,7 @@ internal fun createSyncOutputTask( syncTask.kotlinTask = kotlinCompile kotlinTask.javaOutputDir = javaDir kotlinAfterJavaTask?.javaOutputDir = javaDir - syncTask.kaptClassesDir = getKaptClasssesDir(project, variantName) + syncTask.kaptClassesDir = getKaptGeneratedClassesDir(project, variantName) // copying should be executed after a latter task val previousTask = kotlinAfterJavaTask ?: javaTask diff --git a/plugins/kapt3-idea/kapt3-idea.iml b/plugins/kapt3-idea/kapt3-idea.iml new file mode 100644 index 00000000000..0b3bb9d456d --- /dev/null +++ b/plugins/kapt3-idea/kapt3-idea.iml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/kapt3-idea/src/META-INF/services/org.jetbrains.plugins.gradle.tooling.ModelBuilderService b/plugins/kapt3-idea/src/META-INF/services/org.jetbrains.plugins.gradle.tooling.ModelBuilderService new file mode 100644 index 00000000000..310ea0a40cf --- /dev/null +++ b/plugins/kapt3-idea/src/META-INF/services/org.jetbrains.plugins.gradle.tooling.ModelBuilderService @@ -0,0 +1 @@ +org.jetbrains.kotlin.kapt.idea.KaptModelBuilderService \ No newline at end of file diff --git a/plugins/kapt3-idea/src/org/jetbrains/kotlin/kapt/idea/KaptProjectResolverExtension.kt b/plugins/kapt3-idea/src/org/jetbrains/kotlin/kapt/idea/KaptProjectResolverExtension.kt new file mode 100644 index 00000000000..f35eef9deb5 --- /dev/null +++ b/plugins/kapt3-idea/src/org/jetbrains/kotlin/kapt/idea/KaptProjectResolverExtension.kt @@ -0,0 +1,190 @@ +/* + * 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.kapt.idea + +import com.android.tools.idea.gradle.project.model.AndroidModuleModel +import com.intellij.openapi.externalSystem.model.DataNode +import com.intellij.openapi.externalSystem.model.ProjectKeys +import com.intellij.openapi.externalSystem.model.project.* +import org.gradle.api.Project +import org.gradle.tooling.model.idea.IdeaModule +import org.jetbrains.kotlin.gradle.AbstractKotlinGradleModelBuilder +import org.jetbrains.kotlin.idea.framework.GRADLE_SYSTEM_ID +import org.jetbrains.plugins.gradle.model.data.GradleSourceSetData +import org.jetbrains.plugins.gradle.service.project.AbstractProjectResolverExtension +import org.jetbrains.plugins.gradle.tooling.ErrorMessageBuilder +import java.io.File +import java.io.Serializable +import java.lang.Exception +import java.lang.reflect.Modifier + +interface KaptSourceSetModel : Serializable { + val sourceSetName: String + val isTest: Boolean + val generatedSourcesDir: String + val generatedClassesDir: String + val generatedKotlinSourcesDir: String + + val generatedSourcesDirFile get() = generatedSourcesDir.takeIf { it.isNotEmpty() }?.let(::File) + val generatedClassesDirFile get() = generatedClassesDir.takeIf { it.isNotEmpty() }?.let(::File) + val generatedKotlinSourcesDirFile get() = generatedKotlinSourcesDir.takeIf { it.isNotEmpty() }?.let(::File) +} + +class KaptSourceSetModelImpl( + override val sourceSetName: String, + override val isTest: Boolean, + override val generatedSourcesDir: String, + override val generatedClassesDir: String, + override val generatedKotlinSourcesDir: String +) : KaptSourceSetModel + +interface KaptGradleModel : Serializable { + val isEnabled: Boolean + val buildDirectory: File + val sourceSets: List +} + +class KaptGradleModelImpl( + override val isEnabled: Boolean, + override val buildDirectory: File, + override val sourceSets: List +) : KaptGradleModel + +@Suppress("unused") +class KaptProjectResolverExtension : AbstractProjectResolverExtension() { + override fun getExtraProjectModelClasses() = setOf(KaptGradleModel::class.java) + override fun getToolingExtensionsClasses() = setOf(KaptModelBuilderService::class.java) + + override fun populateModuleExtraModels(gradleModule: IdeaModule, ideModule: DataNode) { + val kaptModel = resolverCtx.getExtraProject(gradleModule, KaptGradleModel::class.java) ?: return + + if (kaptModel.isEnabled) { + for (sourceSet in kaptModel.sourceSets) { + populateAndroidModuleModelIfNeeded(ideModule, sourceSet) + + val sourceSetDataNode = ideModule.findGradleSourceSet(sourceSet.sourceSetName) ?: continue + + fun addSourceSet(path: String, type: ExternalSystemSourceType) { + val contentRootData = ContentRootData(GRADLE_SYSTEM_ID, path) + contentRootData.storePath(type, path) + val child = sourceSetDataNode.createChild(ProjectKeys.CONTENT_ROOT, contentRootData) + sourceSetDataNode.addChild(child) + } + + val sourceType = if (sourceSet.isTest) ExternalSystemSourceType.TEST_GENERATED else ExternalSystemSourceType.SOURCE_GENERATED + sourceSet.generatedSourcesDirFile?.let { addSourceSet(it.absolutePath, sourceType) } + sourceSet.generatedKotlinSourcesDirFile?.let { addSourceSet(it.absolutePath, sourceType) } + + sourceSet.generatedClassesDirFile?.let { generatedClassesDir -> + val libraryData = LibraryData(GRADLE_SYSTEM_ID, "Kapt generated classes") + libraryData.addPath(LibraryPathType.BINARY, generatedClassesDir.absolutePath) + val libraryDependencyData = LibraryDependencyData(ideModule.data, libraryData, LibraryLevel.MODULE) + val child = sourceSetDataNode.createChild(ProjectKeys.LIBRARY_DEPENDENCY, libraryDependencyData) + sourceSetDataNode.addChild(child) + } + } + } + + super.populateModuleExtraModels(gradleModule, ideModule) + } + + private fun populateAndroidModuleModelIfNeeded(ideModule: DataNode, sourceSet: KaptSourceSetModel) { + if (true) return + + ideModule.findAndroidModuleModel()?.let { androidModelAny -> + // We can cast to AndroidModuleModel cause we already checked in findAndroidModuleModel() that the class exists + + val androidModel = androidModelAny.data as? AndroidModuleModel + val generatedKotlinSources = sourceSet.generatedKotlinSourcesDirFile + + if (androidModel != null && generatedKotlinSources != null) { + // Looks like we can't add generated class root as a library dependency + androidModel.registerExtraGeneratedSourceFolder(generatedKotlinSources) + } + } + } + + private fun DataNode.findAndroidModuleModel(): DataNode<*>? { + val modelClassName = "com.android.tools.idea.gradle.project.model.AndroidModuleModel" + val node = children.firstOrNull { it.key.dataType == modelClassName } ?: return null + return if (!hasClassInClasspath(modelClassName)) null else node + } + + private fun hasClassInClasspath(name: String): Boolean { + return try { + Class.forName(name) != null + } catch (thr: Throwable) { + false + } + } + + private fun DataNode.findGradleSourceSet(sourceSetName: String): DataNode? { + val moduleName = data.id + for (child in children) { + val gradleSourceSetData = child.data as? GradleSourceSetData ?: continue + if (gradleSourceSetData.id == "$moduleName:$sourceSetName") { + @Suppress("UNCHECKED_CAST") + return child as DataNode? + } + } + + return null + } +} + +class KaptModelBuilderService : AbstractKotlinGradleModelBuilder() { + override fun getErrorMessageBuilder(project: Project, e: Exception): ErrorMessageBuilder { + return ErrorMessageBuilder.create(project, e, "Gradle import errors") + .withDescription("Unable to build kotlin-kapt plugin configuration") + } + + override fun canBuild(modelName: String?): Boolean = modelName == KaptGradleModel::class.java.name + + override fun buildAll(modelName: String?, project: Project): Any { + val kaptPlugin = project.plugins.findPlugin("kotlin-kapt") + val kaptIsEnabled = kaptPlugin != null + + val sourceSets = mutableListOf() + + if (kaptIsEnabled) { + project.getAllTasks(false)[project]?.forEach { compileTask -> + if (compileTask.javaClass.name !in kotlinCompileTaskClasses) return@forEach + + val sourceSetName = compileTask.getSourceSetName() + val isTest = sourceSetName.toLowerCase().endsWith("test") + + val kaptGeneratedSourcesDir = getKaptDirectory("getKaptGeneratedSourcesDir", project, sourceSetName) + val kaptGeneratedClassesDir = getKaptDirectory("getKaptGeneratedClassesDir", project, sourceSetName) + val kaptGeneratedKotlinSourcesDir = getKaptDirectory("getKaptGeneratedKotlinSourcesDir", project, sourceSetName) + sourceSets += KaptSourceSetModelImpl( + sourceSetName, isTest, kaptGeneratedSourcesDir, kaptGeneratedClassesDir, kaptGeneratedKotlinSourcesDir) + } + } + + return KaptGradleModelImpl(kaptIsEnabled, project.buildDir, sourceSets) + } + + private fun getKaptDirectory(funName: String, project: Project, sourceSetName: String): String { + val kotlinKaptPlugin = project.plugins.findPlugin("kotlin-kapt") ?: return "" + + val targetMethod = kotlinKaptPlugin::class.java.methods.firstOrNull { + Modifier.isStatic(it.modifiers) && it.name == funName && it.parameterCount == 2 + } ?: return "" + + return (targetMethod(null, project, sourceSetName) as? File)?.absolutePath ?: "" + } +} \ No newline at end of file