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.
This commit is contained in:
Sergey Igushkin
2020-06-06 00:24:09 +03:00
parent 4dbc6803ba
commit 06a3376368
4 changed files with 114 additions and 138 deletions
@@ -672,3 +672,22 @@ internal fun checkAndroidAnnotationProcessorDependencyUsage(project: Project) {
) )
} }
} }
private val BaseVariant.annotationProcessorOptions: Map<String, String>?
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<Any>()
}
//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
@@ -744,15 +744,10 @@ class KotlinConfigurationTools internal constructor(
abstract class AbstractAndroidProjectHandler(private val kotlinConfigurationTools: KotlinConfigurationTools) { abstract class AbstractAndroidProjectHandler(private val kotlinConfigurationTools: KotlinConfigurationTools) {
protected val logger = Logging.getLogger(this.javaClass) protected val logger = Logging.getLogger(this.javaClass)
abstract fun getResDirectories(variantData: BaseVariant): FileCollection
abstract fun getFlavorNames(variant: BaseVariant): List<String> abstract fun getFlavorNames(variant: BaseVariant): List<String>
abstract fun getBuildTypeName(variant: BaseVariant): String abstract fun getBuildTypeName(variant: BaseVariant): String
abstract fun getLibraryOutputTask(variant: BaseVariant): Any? abstract fun getLibraryOutputTask(variant: BaseVariant): Any?
protected abstract fun getSourceProviders(variantData: BaseVariant): Iterable<SourceProvider>
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 checkVariantIsValid(variant: BaseVariant) = Unit
protected open fun setUpDependencyResolution(variant: BaseVariant, compilation: KotlinJvmAndroidCompilation) = Unit protected open fun setUpDependencyResolution(variant: BaseVariant, compilation: KotlinJvmAndroidCompilation) = Unit
@@ -763,12 +758,10 @@ abstract class AbstractAndroidProjectHandler(private val kotlinConfigurationTool
androidPlugin: BasePlugin, androidPlugin: BasePlugin,
androidExt: BaseExtension, androidExt: BaseExtension,
variantData: BaseVariant, variantData: BaseVariant,
javaTask: AbstractCompile, javaTask: TaskProvider<out AbstractCompile>,
kotlinTask: KotlinCompile kotlinTask: TaskProvider<out KotlinCompile>
) )
protected abstract fun wrapVariantDataForKapt(variantData: BaseVariant): KaptVariantData<BaseVariant>
fun configureTarget(kotlinAndroidTarget: KotlinAndroidTarget) { fun configureTarget(kotlinAndroidTarget: KotlinAndroidTarget) {
val project = kotlinAndroidTarget.project val project = kotlinAndroidTarget.project
val ext = project.extensions.getByName("android") as BaseExtension val ext = project.extensions.getByName("android") as BaseExtension
@@ -903,8 +896,8 @@ abstract class AbstractAndroidProjectHandler(private val kotlinConfigurationTool
if (kotlinAndroidTarget.disambiguationClassifier != null) { if (kotlinAndroidTarget.disambiguationClassifier != null) {
val sourceSetToVariants = mutableMapOf<AndroidSourceSet, MutableList<BaseVariant>>().apply { val sourceSetToVariants = mutableMapOf<AndroidSourceSet, MutableList<BaseVariant>>().apply {
project.forEachVariant { variant -> forEachVariant(project) { variant ->
for (sourceSet in getSourceProviders(variant)) { for (sourceSet in variant.sourceSets) {
val androidSourceSet = sourceSet as? AndroidSourceSet ?: continue val androidSourceSet = sourceSet as? AndroidSourceSet ?: continue
getOrPut(androidSourceSet) { mutableListOf() }.add(variant) getOrPut(androidSourceSet) { mutableListOf() }.add(variant)
} }
@@ -948,13 +941,6 @@ abstract class AbstractAndroidProjectHandler(private val kotlinConfigurationTool
val variantDataName = getVariantName(variantData) val variantDataName = getVariantName(variantData)
logger.kotlinDebug("Process variant [$variantDataName]") 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 defaultSourceSet = project.kotlinExtension.sourceSets.maybeCreate(compilation.defaultSourceSetName)
val kotlinTaskName = compilation.compileKotlinTaskName val kotlinTaskName = compilation.compileKotlinTaskName
@@ -989,7 +975,7 @@ abstract class AbstractAndroidProjectHandler(private val kotlinConfigurationTool
androidExt: BaseExtension, androidExt: BaseExtension,
androidPlugin: BasePlugin androidPlugin: BasePlugin
) { ) {
val javaTask = getJavaTask(variantData) ?: return val javaTask = variantData.getJavaTaskProvider()
getTestedVariantData(variantData)?.let { testedVariant -> getTestedVariantData(variantData)?.let { testedVariant ->
val testedVariantName = getVariantName(testedVariant) val testedVariantName = getVariantName(testedVariant)
@@ -7,24 +7,23 @@
package org.jetbrains.kotlin.gradle.plugin package org.jetbrains.kotlin.gradle.plugin
import com.android.build.api.attributes.BuildTypeAttr import com.android.build.api.attributes.BuildTypeAttr
import com.android.build.gradle.BaseExtension import com.android.build.gradle.*
import com.android.build.gradle.BasePlugin
import com.android.build.gradle.api.* import com.android.build.gradle.api.*
import com.android.build.gradle.tasks.MergeResources import com.android.build.gradle.tasks.MergeResources
import com.android.builder.model.SourceProvider
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.artifacts.component.ProjectComponentIdentifier import org.gradle.api.artifacts.component.ProjectComponentIdentifier
import org.gradle.api.attributes.Attribute import org.gradle.api.attributes.Attribute
import org.gradle.api.file.FileCollection import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.AbstractArchiveTask import org.gradle.api.tasks.bundling.AbstractArchiveTask
import org.gradle.api.tasks.compile.AbstractCompile 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.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.android.AndroidGradleWrapper
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.gradle.tasks.thisTaskProvider
import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation
import java.io.File import java.io.File
import java.util.concurrent.Callable import java.util.concurrent.Callable
@@ -39,25 +38,30 @@ class Android25ProjectHandler(
androidPlugin: BasePlugin, androidPlugin: BasePlugin,
androidExt: BaseExtension, androidExt: BaseExtension,
variantData: BaseVariant, variantData: BaseVariant,
javaTask: AbstractCompile, javaTask: TaskProvider<out AbstractCompile>,
kotlinTask: KotlinCompile kotlinTask: TaskProvider<out KotlinCompile>
) { ) {
val preJavaKotlinOutputFiles = mutableListOf<File>().apply { val preJavaKotlinOutput = project.files(project.provider {
add(kotlinTask.destinationDir) mutableListOf<File>().apply {
if (Kapt3GradleSubplugin.isEnabled(project)) { add(kotlinTask.get().destinationDir)
// Add Kapt3 output as well, since there's no SyncOutputTask with the new API if (Kapt3GradleSubplugin.isEnabled(project)) {
val kaptClasssesDir = Kapt3GradleSubplugin.getKaptGeneratedClassesDir(project, getVariantName(variantData)) // Add Kapt3 output as well, since there's no SyncOutputTask with the new API
add(kaptClasssesDir) val kaptClasssesDir = Kapt3GradleSubplugin.getKaptGeneratedClassesDir(project, getVariantName(variantData))
add(kaptClasssesDir)
}
} }
} }).builtBy(kotlinTask)
val preJavaKotlinOutput = project.files(*preJavaKotlinOutputFiles.toTypedArray()).builtBy(kotlinTask)
val preJavaClasspathKey = variantData.registerPreJavacGeneratedBytecode(preJavaKotlinOutput) 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 { kotlinTaskInstance.mapClasspath {
val kotlinClasspath = variantData.getCompileClasspath(preJavaClasspathKey) val kotlinClasspath = variantData.getCompileClasspath(preJavaClasspathKey)
kotlinClasspath + project.files(AndroidGradleWrapper.getRuntimeJars(androidPlugin, androidExt)) 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 // 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.from(
kotlinTask.map { it.destinationDir },
compilation.output.classesDirs.run { javaTask.map { it.destinationDir }
from(project.files(kotlinTask.taskData.destinationDir).builtBy(kotlinTask)) )
from(project.files(project.provider { javaTask.destinationDir }).builtBy(javaTask))
}
} }
override fun getSourceProviders(variantData: BaseVariant): Iterable<SourceProvider> =
variantData.sourceSets
override fun getFlavorNames(variant: BaseVariant): List<String> = variant.productFlavors.map { it.name } override fun getFlavorNames(variant: BaseVariant): List<String> = variant.productFlavors.map { it.name }
override fun getBuildTypeName(variant: BaseVariant): String = variant.buildType.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<out AbstractArchiveTask>`; // TODO the return type is actually `AbstractArchiveTask | TaskProvider<out AbstractArchiveTask>`;
// change the signature once the Android Gradle plugin versions that don't support task providers are dropped // change the signature once the Android Gradle plugin versions that don't support task providers are dropped
override fun getLibraryOutputTask(variant: BaseVariant): Any? { override fun getLibraryOutputTask(variant: BaseVariant): Any? {
@@ -170,71 +144,6 @@ class Android25ProjectHandler(
project.configurations.findByName(outputConfigurationName)?.usesPlatformOf(compilation.target) project.configurations.findByName(outputConfigurationName)?.usesPlatformOf(compilation.target)
} }
} }
private inner class KaptVariant(variantData: BaseVariant) : KaptVariantData<BaseVariant>(variantData) {
override val name: String = getVariantName(variantData)
override val sourceProviders: Iterable<SourceProvider> = getSourceProviders(variantData)
override fun addJavaSourceFoldersToModel(generatedFilesDir: File) =
addJavaSourceDirectoryToVariantModel(variantData, generatedFilesDir)
override val annotationProcessorOptions: Map<String, String>? =
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<Any>()
}
}
//TODO A public API is expected for this purpose. Once it is available, use the public API
private fun MergeResources.computeResourceSetList0(): List<File>? {
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<File>
}
?.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<BaseVariant> =
KaptVariant(variantData)
} }
internal fun getTestedVariantData(variantData: BaseVariant): BaseVariant? = when (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 internal fun getVariantName(variant: BaseVariant): String = variant.name
@Suppress("UNCHECKED_CAST")
internal fun BaseVariant.getJavaTaskProvider(): TaskProvider<out JavaCompile> =
this::class.java.methods.firstOrNull { it.name == "getJavaCompileProvider" }
?.invoke(this) as? TaskProvider<JavaCompile>
?: @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<File>? {
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<File>
}
?.flatten()
} finally {
computeResourceSetListMethod.isAccessible = oldIsAccessible
}
}
@@ -21,7 +21,8 @@ import org.jetbrains.kotlin.gradle.plugin.getTestedVariantData
name: String name: String
) : AbstractKotlinCompilationToRunnableFiles<KotlinJvmOptions>(target, name) { ) : AbstractKotlinCompilationToRunnableFiles<KotlinJvmOptions>(target, name) {
lateinit internal var androidVariant: BaseVariant lateinit var androidVariant: BaseVariant
internal set
override val compileKotlinTask: org.jetbrains.kotlin.gradle.tasks.KotlinCompile override val compileKotlinTask: org.jetbrains.kotlin.gradle.tasks.KotlinCompile
get() = super.compileKotlinTask as org.jetbrains.kotlin.gradle.tasks.KotlinCompile get() = super.compileKotlinTask as org.jetbrains.kotlin.gradle.tasks.KotlinCompile