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) {
protected val logger = Logging.getLogger(this.javaClass)
abstract fun getResDirectories(variantData: BaseVariant): FileCollection
abstract fun getFlavorNames(variant: BaseVariant): List<String>
abstract fun getBuildTypeName(variant: BaseVariant): String
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 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<out AbstractCompile>,
kotlinTask: TaskProvider<out KotlinCompile>
)
protected abstract fun wrapVariantDataForKapt(variantData: BaseVariant): KaptVariantData<BaseVariant>
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<AndroidSourceSet, MutableList<BaseVariant>>().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)
@@ -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<out AbstractCompile>,
kotlinTask: TaskProvider<out KotlinCompile>
) {
val preJavaKotlinOutputFiles = mutableListOf<File>().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<File>().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<SourceProvider> =
variantData.sourceSets
override fun getFlavorNames(variant: BaseVariant): List<String> = 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<out AbstractArchiveTask>`;
// 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<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) {
@@ -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<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
) : AbstractKotlinCompilationToRunnableFiles<KotlinJvmOptions>(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