KAPT: Avoid evaluating apOptions from AGP too early

This is causing issues such as https://issuetracker.google.com/183423660.
Annotation processor options that are provided by the Android Gradle plugin
may contain references to files and file collections that are safe to
evaluate only at execution time. This change avoids eagerly creating
compiler plugin options for these options, as they are already a task input.

Test: AbstractKotlinAndroidGradleTests.testAgpNestedArgsNotEvaluatedDuringConfiguration

^KT-39715 In Progress
This commit is contained in:
Ivan Gavrilovic
2021-03-24 18:20:25 +00:00
committed by TeamCityServer
parent 1123f97a15
commit 19708cfa87
3 changed files with 85 additions and 32 deletions
@@ -521,6 +521,42 @@ open class KotlinAndroid34GradleIT : KotlinAndroid3GradleIT() {
}
}
@Test
fun testAgpNestedArgsNotEvaluatedDuringConfiguration() = with(Project("AndroidProject")) {
setupWorkingDir()
gradleBuildScript(subproject = "Android").appendText(
"""
apply plugin: 'kotlin-kapt'
class MyNested implements org.gradle.process.CommandLineArgumentProvider {
@Override
Iterable<String> asArguments() {
throw new RuntimeException("This should not be invoked during configuration.")
}
}
def nested = new MyNested()
android.applicationVariants.all {
it.javaCompileOptions.annotationProcessorOptions.compilerArgumentProviders.add(nested)
}
""".trimIndent()
)
build(":Android:kaptFlavor1DebugKotlin", "--dry-run") {
assertSuccessful()
}
build(
":Android:kaptFlavor1DebugKotlin", "--dry-run",
options = defaultBuildOptions().copy(kaptOptions = KaptOptions(verbose = false, useWorkers = false))
) {
assertSuccessful()
}
}
@Test
fun testOmittedStdlibVersion() = Project("AndroidProject").run {
setupWorkingDir()
@@ -324,13 +324,10 @@ class Kapt3GradleSubplugin @Inject internal constructor(private val registry: To
pluginOptions += SubpluginOption("processors", annotationProcessors)
}
val apOptions = getAPOptions().get()
pluginOptions += CompositeSubpluginOption(
"apoptions",
lazy { encodeList(apOptions.associate { it.key to it.value }) },
apOptions
)
if (aptMode == "apt") {
// apOptions are needed only for "apt" mode
pluginOptions += getAPOptions().get()
}
pluginOptions += SubpluginOption("javacArguments", encodeList(javacOptions.get()))
@@ -342,35 +339,46 @@ class Kapt3GradleSubplugin @Inject internal constructor(private val registry: To
}
}
private fun Kapt3SubpluginContext.getAPOptions(): Provider<List<SubpluginOption>> = project.provider {
private fun Kapt3SubpluginContext.getAPOptions(): Provider<CompositeSubpluginOption> = project.provider {
val androidVariantData = KaptWithAndroid.androidVariantData(this)
val androidPlugin = androidVariantData?.let {
val androidOptions = androidVariantData?.annotationProcessorOptions ?: emptyMap()
val annotationProcessorProviders = androidVariantData?.annotationProcessorOptionProviders
val subluginOptionsFromProvidedApOptions = lazy {
val apOptionsFromProviders =
annotationProcessorProviders?.flatMap {
(it as CommandLineArgumentProvider).asArguments()
}.orEmpty()
apOptionsFromProviders.map {
// Use the internal subplugin option type to exclude them from Gradle input/output checks, as their providers are already
// properly registered as a nested input:
// Pass options as they are in the key-only form (key = 'a=b'), kapt will deal with them:
InternalSubpluginOption(key = it.removePrefix("-A"), value = "")
}
}
val nonAndroidOptions = androidOptions.toList().map { SubpluginOption(it.first, it.second) } + getKaptApOptions().get()
CompositeSubpluginOption(
"apoptions",
lazy { encodeList((nonAndroidOptions + subluginOptionsFromProvidedApOptions.value).associate { it.key to it.value }) },
nonAndroidOptions
)
}
private fun Kapt3SubpluginContext.getKaptApOptions(): Provider<List<SubpluginOption>> = project.provider {
val androidVariantData = KaptWithAndroid.androidVariantData(this)
val androidExtension = androidVariantData?.let {
project.extensions.findByName("android") as? BaseExtension
}
val androidOptions = androidVariantData?.annotationProcessorOptions ?: emptyMap()
val apOptionsFromProviders =
androidVariantData?.annotationProcessorOptionProviders
?.flatMap { (it as CommandLineArgumentProvider).asArguments() }
.orEmpty()
val subluginOptionsFromProvidedApOptions = apOptionsFromProviders.map {
// Use the internal subplugin option type to exclude them from Gradle input/output checks, as their providers are already
// properly registered as a nested input:
// Pass options as they are in the key-only form (key = 'a=b'), kapt will deal with them:
InternalSubpluginOption(key = it.removePrefix("-A"), value = "")
}
val apOptionsPairsList: List<Pair<String, String>> =
kaptExtension.getAdditionalArguments(project, androidVariantData, androidPlugin).toList() +
androidOptions.toList()
apOptionsPairsList.map { SubpluginOption(it.first, it.second) } +
FilesSubpluginOption(KAPT_KOTLIN_GENERATED, listOf(kotlinSourcesOutputDir)) +
subluginOptionsFromProvidedApOptions
kaptExtension.getAdditionalArguments(project, androidVariantData, androidExtension).toList()
.map { SubpluginOption(it.first, it.second) } +
FilesSubpluginOption(KAPT_KOTLIN_GENERATED, listOf(kotlinSourcesOutputDir))
}
private fun Kapt3SubpluginContext.registerSubpluginOptions(
@@ -563,7 +571,7 @@ class Kapt3GradleSubplugin @Inject internal constructor(private val registry: To
it.javacOptions = dslJavacOptions.get()
}
val subpluginOptions = getAPOptions()
val subpluginOptions = getKaptApOptions()
registerSubpluginOptions(kaptTaskProvider, subpluginOptions)
}
@@ -11,6 +11,7 @@ import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import org.gradle.api.tasks.incremental.IncrementalTaskInputs
import org.gradle.process.CommandLineArgumentProvider
import org.gradle.workers.IsolationMode
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
@@ -74,6 +75,14 @@ abstract class KaptWithoutKotlincTask @Inject constructor(
for (option in options) {
result[option.key] = option.value
}
annotationProcessorOptionProviders.forEach {
(it as List<Any>).forEach {
(it as CommandLineArgumentProvider).asArguments().forEach {
result[it.removePrefix("-A")] = ""
}
}
}
return result
}