From 06a33763683f4bdef0b1b7ecaa8adde7032b7dbe Mon Sep 17 00:00:00 2001 From: Sergey Igushkin Date: Sat, 6 Jun 2020 00:24:09 +0300 Subject: [PATCH] Refactor the Kotlin Android plugin to make it compliant with TCA Also simplify some of the APIs and move some others which are only used by kapt to the kapt's codebase. --- .../kapt/Kapt3KotlinGradleSubplugin.kt | 19 ++ .../kotlin/gradle/plugin/KotlinPlugin.kt | 24 +- .../android/Android25ProjectHandler.kt | 206 ++++++++---------- .../jvm/KotlinJvmAndroidCompilation.kt | 3 +- 4 files changed, 114 insertions(+), 138 deletions(-) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/Kapt3KotlinGradleSubplugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/Kapt3KotlinGradleSubplugin.kt index 7746bfcefb2..48e3b7d35b7 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/Kapt3KotlinGradleSubplugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/internal/kapt/Kapt3KotlinGradleSubplugin.kt @@ -672,3 +672,22 @@ internal fun checkAndroidAnnotationProcessorDependencyUsage(project: Project) { ) } } + +private val BaseVariant.annotationProcessorOptions: Map? + get() = javaCompileOptions.annotationProcessorOptions.arguments + +private val BaseVariant.annotationProcessorOptionProviders: List<*> + get() = try { + // Public API added in Android Gradle Plugin 3.2.0-alpha15: + val apOptions = javaCompileOptions.annotationProcessorOptions + apOptions.javaClass.getMethod("getCompilerArgumentProviders").invoke(apOptions) as List<*> + } catch (e: NoSuchMethodException) { + emptyList() + } + +//TODO once the Android plugin reaches its 3.0.0 release, consider compiling against it (remove the reflective call) +private val BaseVariant.dataBindingDependencyArtifactsIfSupported: FileCollection? + get() = this::class.java.methods + .find { it.name == "getDataBindingDependencyArtifacts" } + ?.also { it.isAccessible = true } + ?.invoke(this) as? FileCollection \ No newline at end of file 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 7e1fabf412c..2a62792e73d 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 @@ -744,15 +744,10 @@ class KotlinConfigurationTools internal constructor( abstract class AbstractAndroidProjectHandler(private val kotlinConfigurationTools: KotlinConfigurationTools) { protected val logger = Logging.getLogger(this.javaClass) - abstract fun getResDirectories(variantData: BaseVariant): FileCollection abstract fun getFlavorNames(variant: BaseVariant): List abstract fun getBuildTypeName(variant: BaseVariant): String abstract fun getLibraryOutputTask(variant: BaseVariant): Any? - protected abstract fun getSourceProviders(variantData: BaseVariant): Iterable - protected abstract fun getJavaTask(variantData: BaseVariant): AbstractCompile? - protected abstract fun addJavaSourceDirectoryToVariantModel(variantData: BaseVariant, javaSourceDirectory: File): Unit - protected open fun checkVariantIsValid(variant: BaseVariant) = Unit protected open fun setUpDependencyResolution(variant: BaseVariant, compilation: KotlinJvmAndroidCompilation) = Unit @@ -763,12 +758,10 @@ abstract class AbstractAndroidProjectHandler(private val kotlinConfigurationTool androidPlugin: BasePlugin, androidExt: BaseExtension, variantData: BaseVariant, - javaTask: AbstractCompile, - kotlinTask: KotlinCompile + javaTask: TaskProvider, + kotlinTask: TaskProvider ) - protected abstract fun wrapVariantDataForKapt(variantData: BaseVariant): KaptVariantData - fun configureTarget(kotlinAndroidTarget: KotlinAndroidTarget) { val project = kotlinAndroidTarget.project val ext = project.extensions.getByName("android") as BaseExtension @@ -903,8 +896,8 @@ abstract class AbstractAndroidProjectHandler(private val kotlinConfigurationTool if (kotlinAndroidTarget.disambiguationClassifier != null) { val sourceSetToVariants = mutableMapOf>().apply { - project.forEachVariant { variant -> - for (sourceSet in getSourceProviders(variant)) { + forEachVariant(project) { variant -> + for (sourceSet in variant.sourceSets) { val androidSourceSet = sourceSet as? AndroidSourceSet ?: continue getOrPut(androidSourceSet) { mutableListOf() }.add(variant) } @@ -948,13 +941,6 @@ abstract class AbstractAndroidProjectHandler(private val kotlinConfigurationTool val variantDataName = getVariantName(variantData) logger.kotlinDebug("Process variant [$variantDataName]") - val javaTask = getJavaTask(variantData) - - if (javaTask == null) { - logger.info("KOTLIN: javaTask is missing for $variantDataName, so Kotlin files won't be compiled for it") - return - } - val defaultSourceSet = project.kotlinExtension.sourceSets.maybeCreate(compilation.defaultSourceSetName) val kotlinTaskName = compilation.compileKotlinTaskName @@ -989,7 +975,7 @@ abstract class AbstractAndroidProjectHandler(private val kotlinConfigurationTool androidExt: BaseExtension, androidPlugin: BasePlugin ) { - val javaTask = getJavaTask(variantData) ?: return + val javaTask = variantData.getJavaTaskProvider() getTestedVariantData(variantData)?.let { testedVariant -> val testedVariantName = getVariantName(testedVariant) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/Android25ProjectHandler.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/Android25ProjectHandler.kt index 13b9ba5e74f..99c396795bd 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/Android25ProjectHandler.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/android/Android25ProjectHandler.kt @@ -7,24 +7,23 @@ package org.jetbrains.kotlin.gradle.plugin import com.android.build.api.attributes.BuildTypeAttr -import com.android.build.gradle.BaseExtension -import com.android.build.gradle.BasePlugin +import com.android.build.gradle.* import com.android.build.gradle.api.* import com.android.build.gradle.tasks.MergeResources -import com.android.builder.model.SourceProvider import org.gradle.api.Project import org.gradle.api.artifacts.component.ProjectComponentIdentifier import org.gradle.api.attributes.Attribute import org.gradle.api.file.FileCollection +import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.bundling.AbstractArchiveTask import org.gradle.api.tasks.compile.AbstractCompile +import org.gradle.api.tasks.compile.JavaCompile import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin -import org.jetbrains.kotlin.gradle.internal.KaptTask -import org.jetbrains.kotlin.gradle.internal.KaptVariantData import org.jetbrains.kotlin.gradle.plugin.android.AndroidGradleWrapper import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import org.jetbrains.kotlin.gradle.tasks.thisTaskProvider import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation import java.io.File import java.util.concurrent.Callable @@ -39,25 +38,30 @@ class Android25ProjectHandler( androidPlugin: BasePlugin, androidExt: BaseExtension, variantData: BaseVariant, - javaTask: AbstractCompile, - kotlinTask: KotlinCompile + javaTask: TaskProvider, + kotlinTask: TaskProvider ) { - val preJavaKotlinOutputFiles = mutableListOf().apply { - add(kotlinTask.destinationDir) - if (Kapt3GradleSubplugin.isEnabled(project)) { - // Add Kapt3 output as well, since there's no SyncOutputTask with the new API - val kaptClasssesDir = Kapt3GradleSubplugin.getKaptGeneratedClassesDir(project, getVariantName(variantData)) - add(kaptClasssesDir) + val preJavaKotlinOutput = project.files(project.provider { + mutableListOf().apply { + add(kotlinTask.get().destinationDir) + if (Kapt3GradleSubplugin.isEnabled(project)) { + // Add Kapt3 output as well, since there's no SyncOutputTask with the new API + val kaptClasssesDir = Kapt3GradleSubplugin.getKaptGeneratedClassesDir(project, getVariantName(variantData)) + add(kaptClasssesDir) + } } - } - val preJavaKotlinOutput = project.files(*preJavaKotlinOutputFiles.toTypedArray()).builtBy(kotlinTask) + }).builtBy(kotlinTask) val preJavaClasspathKey = variantData.registerPreJavacGeneratedBytecode(preJavaKotlinOutput) - kotlinTask.dependsOn(variantData.getSourceFolders(SourceKind.JAVA)) + kotlinTask.configure { kotlinTaskInstance -> + kotlinTaskInstance.inputs.files(variantData.getSourceFolders(SourceKind.JAVA)).withPathSensitivity(PathSensitivity.RELATIVE) - kotlinTask.mapClasspath { - val kotlinClasspath = variantData.getCompileClasspath(preJavaClasspathKey) - kotlinClasspath + project.files(AndroidGradleWrapper.getRuntimeJars(androidPlugin, androidExt)) + kotlinTaskInstance.mapClasspath { + val kotlinClasspath = variantData.getCompileClasspath(preJavaClasspathKey) + kotlinClasspath + project.files(AndroidGradleWrapper.getRuntimeJars(androidPlugin, androidExt)) + } + + kotlinTaskInstance.javaOutputDir = javaTask.get().destinationDir } // Find the classpath entries that come from the tested variant and register them as the friend paths, lazily @@ -78,46 +82,16 @@ class Android25ProjectHandler( ) ) - kotlinTask.javaOutputDir = javaTask.destinationDir - - compilation.output.classesDirs.run { - from(project.files(kotlinTask.taskData.destinationDir).builtBy(kotlinTask)) - from(project.files(project.provider { javaTask.destinationDir }).builtBy(javaTask)) - } + compilation.output.classesDirs.from( + kotlinTask.map { it.destinationDir }, + javaTask.map { it.destinationDir } + ) } - override fun getSourceProviders(variantData: BaseVariant): Iterable = - variantData.sourceSets - override fun getFlavorNames(variant: BaseVariant): List = variant.productFlavors.map { it.name } override fun getBuildTypeName(variant: BaseVariant): String = variant.buildType.name - override fun getJavaTask(variantData: BaseVariant): AbstractCompile? { - @Suppress("DEPRECATION") // There is always a Java compile task -- the deprecation was for Jack - return variantData::class.java.methods.firstOrNull { it.name == "getJavaCompileProvider" } - ?.invoke(variantData) - ?.let { taskProvider -> - // org.gradle.api.tasks.TaskProvider is added in Gradle 4.8 - taskProvider::class.java.methods.firstOrNull { it.name == "get" }?.invoke(taskProvider) as AbstractCompile? - } ?: variantData.javaCompile - } - - override fun addJavaSourceDirectoryToVariantModel(variantData: BaseVariant, javaSourceDirectory: File) = - variantData.addJavaSourceFoldersToModel(javaSourceDirectory) - - override fun getResDirectories(variantData: BaseVariant): FileCollection { - val getAllResourcesMethod = - variantData::class.java.methods.firstOrNull { it.name == "getAllRawAndroidResources" } - if (getAllResourcesMethod != null) { - val allResources = getAllResourcesMethod.invoke(variantData) as FileCollection - return allResources - } - - val project = variantData.mergeResources.project - return project.files(Callable { variantData.mergeResources?.computeResourceSetList0() ?: emptyList() }) - } - // TODO the return type is actually `AbstractArchiveTask | TaskProvider`; // change the signature once the Android Gradle plugin versions that don't support task providers are dropped override fun getLibraryOutputTask(variant: BaseVariant): Any? { @@ -170,71 +144,6 @@ class Android25ProjectHandler( project.configurations.findByName(outputConfigurationName)?.usesPlatformOf(compilation.target) } } - - private inner class KaptVariant(variantData: BaseVariant) : KaptVariantData(variantData) { - override val name: String = getVariantName(variantData) - override val sourceProviders: Iterable = getSourceProviders(variantData) - override fun addJavaSourceFoldersToModel(generatedFilesDir: File) = - addJavaSourceDirectoryToVariantModel(variantData, generatedFilesDir) - - override val annotationProcessorOptions: Map? = - variantData.javaCompileOptions.annotationProcessorOptions.arguments - - override fun registerGeneratedJavaSource( - project: Project, - kaptTask: KaptTask, - javaTask: AbstractCompile - ) { - val kaptSourceOutput = project.fileTree(kaptTask.destinationDir).builtBy(kaptTask) - kaptSourceOutput.include("**/*.java") - variantData.registerExternalAptJavaOutput(kaptSourceOutput) - variantData.dataBindingDependencyArtifactsIfSupported?.let { kaptTask.dependsOn(it) } - } - - override val annotationProcessorOptionProviders: List<*> - get() = try { - // Public API added in Android Gradle Plugin 3.2.0-alpha15: - val apOptions = variantData.javaCompileOptions.annotationProcessorOptions - apOptions.javaClass.getMethod("getCompilerArgumentProviders").invoke(apOptions) as List<*> - } catch (e: NoSuchMethodException) { - emptyList() - } - } - - //TODO A public API is expected for this purpose. Once it is available, use the public API - private fun MergeResources.computeResourceSetList0(): List? { - val computeResourceSetListMethod = MergeResources::class.java.declaredMethods - .firstOrNull { it.name == "computeResourceSetList" && it.parameterCount == 0 } ?: return null - - val oldIsAccessible = computeResourceSetListMethod.isAccessible - try { - computeResourceSetListMethod.isAccessible = true - - val resourceSets = computeResourceSetListMethod.invoke(this) as? Iterable<*> - - return resourceSets - ?.mapNotNull { resourceSet -> - val getSourceFiles = resourceSet?.javaClass?.methods?.find { it.name == "getSourceFiles" && it.parameterCount == 0 } - val files = getSourceFiles?.invoke(resourceSet) - @Suppress("UNCHECKED_CAST") - files as? Iterable - } - ?.flatten() - - } finally { - computeResourceSetListMethod.isAccessible = oldIsAccessible - } - } - - //TODO once the Android plugin reaches its 3.0.0 release, consider compiling against it (remove the reflective call) - private val BaseVariant.dataBindingDependencyArtifactsIfSupported: FileCollection? - get() = this::class.java.methods - .find { it.name == "getDataBindingDependencyArtifacts" } - ?.also { it.isAccessible = true } - ?.invoke(this) as? FileCollection - - override fun wrapVariantDataForKapt(variantData: BaseVariant): KaptVariantData = - KaptVariant(variantData) } internal fun getTestedVariantData(variantData: BaseVariant): BaseVariant? = when (variantData) { @@ -244,3 +153,64 @@ internal fun getTestedVariantData(variantData: BaseVariant): BaseVariant? = when } internal fun getVariantName(variant: BaseVariant): String = variant.name + +@Suppress("UNCHECKED_CAST") +internal fun BaseVariant.getJavaTaskProvider(): TaskProvider = + this::class.java.methods.firstOrNull { it.name == "getJavaCompileProvider" } + ?.invoke(this) as? TaskProvider + ?: @Suppress("DEPRECATION") javaCompile.thisTaskProvider + +internal fun forEachVariant(project: Project, action: (BaseVariant) -> Unit) { + val androidExtension = project.extensions.getByName("android") + when (androidExtension) { + is AppExtension -> androidExtension.applicationVariants.all(action) + is LibraryExtension -> { + androidExtension.libraryVariants.all(action) + if (androidExtension is FeatureExtension) { + androidExtension.featureVariants.all(action) + } + } + is TestExtension -> androidExtension.applicationVariants.all(action) + } + if (androidExtension is TestedExtension) { + androidExtension.testVariants.all(action) + androidExtension.unitTestVariants.all(action) + } +} + +internal fun BaseVariant.getResDirectories(): FileCollection { + val getAllResourcesMethod = + this::class.java.methods.firstOrNull { it.name == "getAllRawAndroidResources" } + if (getAllResourcesMethod != null) { + val allResources = getAllResourcesMethod.invoke(this) as FileCollection + return allResources + } + + val project = mergeResources.project + return project.files(Callable { mergeResources?.computeResourceSetList0() ?: emptyList() }) +} + +//TODO A public API is expected for this purpose. Once it is available, use the public API +private fun MergeResources.computeResourceSetList0(): List? { + val computeResourceSetListMethod = MergeResources::class.java.declaredMethods + .firstOrNull { it.name == "computeResourceSetList" && it.parameterCount == 0 } ?: return null + + val oldIsAccessible = computeResourceSetListMethod.isAccessible + try { + computeResourceSetListMethod.isAccessible = true + + val resourceSets = computeResourceSetListMethod.invoke(this) as? Iterable<*> + + return resourceSets + ?.mapNotNull { resourceSet -> + val getSourceFiles = resourceSet?.javaClass?.methods?.find { it.name == "getSourceFiles" && it.parameterCount == 0 } + val files = getSourceFiles?.invoke(resourceSet) + @Suppress("UNCHECKED_CAST") + files as? Iterable + } + ?.flatten() + + } finally { + computeResourceSetListMethod.isAccessible = oldIsAccessible + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/jvm/KotlinJvmAndroidCompilation.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/jvm/KotlinJvmAndroidCompilation.kt index 648e2c930ef..49286476070 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/jvm/KotlinJvmAndroidCompilation.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/jvm/KotlinJvmAndroidCompilation.kt @@ -21,7 +21,8 @@ import org.jetbrains.kotlin.gradle.plugin.getTestedVariantData name: String ) : AbstractKotlinCompilationToRunnableFiles(target, name) { - lateinit internal var androidVariant: BaseVariant + lateinit var androidVariant: BaseVariant + internal set override val compileKotlinTask: org.jetbrains.kotlin.gradle.tasks.KotlinCompile get() = super.compileKotlinTask as org.jetbrains.kotlin.gradle.tasks.KotlinCompile