Fix Android Extensions early dependency resolution

Make compiler plugin options evaluate lazily, with Lazy<String> and
FileCollection as replacements for the eagerly-evaluated data.

Adjust compiler plugin option usages so that their evaluation is not
triggered when not necessary.

Issue #KT-26065 Fixed
This commit is contained in:
Sergey Igushkin
2018-11-28 21:25:07 +03:00
parent 201c16ecb0
commit 0f670f806f
9 changed files with 104 additions and 52 deletions
@@ -21,20 +21,34 @@ import org.gradle.api.tasks.compile.AbstractCompile
import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions
import java.io.File
open class SubpluginOption(val key: String, val value: String)
open class SubpluginOption(val key: String, private val lazyValue: Lazy<String>) {
constructor(key: String, value: String) : this(key, lazyOf(value))
val value: String get() = lazyValue.value
}
class FilesSubpluginOption(
key: String,
val files: Iterable<File>,
val kind: FilesOptionKind = FilesOptionKind.INTERNAL,
lazyValue: Lazy<String> = lazy { files.joinToString(File.pathSeparator) { it.canonicalPath } }
) : SubpluginOption(key, lazyValue) {
constructor(
key: String,
val files: List<File>,
val kind: FilesOptionKind = FilesOptionKind.INTERNAL,
value: String = files.joinToString(File.pathSeparator) { it.canonicalPath })
: SubpluginOption(key, value)
files: List<File>,
kind: FilesOptionKind = FilesOptionKind.INTERNAL,
value: String? = null
) : this(key, files, kind, lazy { value ?: files.joinToString(File.pathSeparator) { it.canonicalPath } })
}
class CompositeSubpluginOption(
key: String,
value: String,
val originalOptions: List<SubpluginOption>)
: SubpluginOption(key, value)
key: String,
lazyValue: Lazy<String>,
val originalOptions: List<SubpluginOption>
) : SubpluginOption(key, lazyValue) {
constructor(key: String, value: String, originalOptions: List<SubpluginOption>) : this(key, lazyOf(value), originalOptions)
}
/** Defines how the files option should be handled with regard to Gradle model */
enum class FilesOptionKind {
@@ -419,6 +419,29 @@ fun getSomething() = 10
val project = Project("AndroidExtensionsSpecificFeatures")
val options = defaultBuildOptions().copy(incremental = false)
if (this is KotlinAndroid30GradleIT) {
project.setupWorkingDir()
project.gradleBuildScript("app").modify {
"""
def projectEvaluated = false
configurations.all { configuration ->
incoming.beforeResolve {
if (!projectEvaluated) {
throw new RuntimeException("${'$'}configuration resolved during project configuration phase.")
}
}
}
$it
afterEvaluate {
projectEvaluated = true
}
""".trimIndent()
}
}
project.build("assemble", options = options) {
assertFailed()
assertContains("Unresolved reference: textView")
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.gradle.utils.addExtendsFromRelation
import java.io.File
import java.util.concurrent.Callable
@Suppress("unused")
class Android25ProjectHandler(kotlinConfigurationTools: KotlinConfigurationTools)
@@ -101,15 +102,16 @@ class Android25ProjectHandler(kotlinConfigurationTools: KotlinConfigurationTools
override fun addJavaSourceDirectoryToVariantModel(variantData: BaseVariant, javaSourceDirectory: File) =
variantData.addJavaSourceFoldersToModel(javaSourceDirectory)
override fun getResDirectories(variantData: BaseVariant): List<File> {
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.files.toList()
return allResources
}
return variantData.mergeResources?.computeResourceSetList0() ?: emptyList()
val project = variantData.mergeResources.project
return project.files(Callable { variantData.mergeResources?.computeResourceSetList0() ?: emptyList() })
}
override fun setUpDependencyResolution(variant: BaseVariant, compilation: KotlinJvmAndroidCompilation) {
@@ -25,6 +25,8 @@ import com.android.build.gradle.internal.variant.TestVariantData
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.UnknownDomainObjectException
import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.compile.AbstractCompile
import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
import org.jetbrains.kotlin.gradle.model.builder.KotlinAndroidExtensionModelBuilder
@@ -34,6 +36,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.multiplatformExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.w3c.dom.Document
import java.io.File
import java.util.concurrent.Callable
import javax.inject.Inject
import javax.xml.parsers.DocumentBuilderFactory
@@ -133,16 +136,17 @@ class AndroidSubplugin : KotlinGradleSubplugin<KotlinCompile> {
pluginOptions += SubpluginOption("package", applicationPackage)
fun addVariant(sourceSet: AndroidSourceSet) {
val optionValue = sourceSet.name + ';' +
sourceSet.res.srcDirs.joinToString(";") { it.absolutePath }
val optionValue = lazy {
sourceSet.name + ';' + sourceSet.res.srcDirs.joinToString(";") { it.absolutePath }
}
pluginOptions += CompositeSubpluginOption(
"variant", optionValue, listOf(
SubpluginOption("sourceSetName", sourceSet.name),
//use the INTERNAL option kind since the resources are tracked as sources (see below)
FilesSubpluginOption("resDirs", sourceSet.res.srcDirs.toList())
FilesSubpluginOption("resDirs", project.files(Callable { sourceSet.res.srcDirs }))
)
)
kotlinCompile.source(project.files(getLayoutDirectories(sourceSet.res.srcDirs)))
kotlinCompile.inputs.files(getLayoutDirectories(project, sourceSet.res.srcDirs)).withPathSensitivity(PathSensitivity.RELATIVE)
}
addVariant(mainSourceSet)
@@ -157,12 +161,14 @@ class AndroidSubplugin : KotlinGradleSubplugin<KotlinCompile> {
return wrapPluginOptions(pluginOptions, "configuration")
}
private fun getLayoutDirectories(resDirectories: Collection<File>): List<File> {
private fun getLayoutDirectories(project: Project, resDirectories: Iterable<File>): FileCollection {
fun isLayoutDirectory(file: File) = file.name == "layout" || file.name.startsWith("layout-")
return resDirectories.flatMap { resDir ->
(resDir.listFiles(::isLayoutDirectory)).orEmpty().asList()
}
return project.files(Callable {
resDirectories.flatMap { resDir ->
(resDir.listFiles(::isLayoutDirectory)).orEmpty().asList()
}
})
}
private fun applyExperimental(
@@ -189,11 +195,13 @@ class AndroidSubplugin : KotlinGradleSubplugin<KotlinCompile> {
val mainSourceSet = androidExtension.sourceSets.getByName("main")
pluginOptions += SubpluginOption("package", getApplicationPackage(project, mainSourceSet))
fun addVariant(name: String, resDirectories: List<File>) {
val optionValue = buildString {
append(name)
append(';')
resDirectories.joinTo(this, separator = ";") { it.canonicalPath }
fun addVariant(name: String, resDirectories: FileCollection) {
val optionValue = lazy {
buildString {
append(name)
append(';')
resDirectories.joinTo(this, separator = ";") { it.canonicalPath }
}
}
pluginOptions += CompositeSubpluginOption(
"variant", optionValue, listOf(
@@ -203,27 +211,27 @@ class AndroidSubplugin : KotlinGradleSubplugin<KotlinCompile> {
)
)
kotlinCompile.source(project.files(getLayoutDirectories(resDirectories)))
kotlinCompile.inputs.files(getLayoutDirectories(project, resDirectories)).withPathSensitivity(PathSensitivity.RELATIVE)
}
fun addSourceSetAsVariant(name: String) {
val sourceSet = androidExtension.sourceSets.findByName(name) ?: return
val srcDirs = sourceSet.res.srcDirs.toList()
if (srcDirs.isNotEmpty()) {
addVariant(sourceSet.name, srcDirs)
addVariant(sourceSet.name, project.files(srcDirs))
}
}
val resDirectoriesForAllVariants = mutableListOf<List<File>>()
val resDirectoriesForAllVariants = mutableListOf<FileCollection>()
androidProjectHandler.forEachVariant(project) { variant ->
if (androidProjectHandler.getTestedVariantData(variant) != null) return@forEachVariant
resDirectoriesForAllVariants += androidProjectHandler.getResDirectories(variant)
}
val commonResDirectories = getCommonResDirectories(resDirectoriesForAllVariants)
val commonResDirectories = getCommonResDirectories(project, resDirectoriesForAllVariants)
addVariant("main", commonResDirectories.toList())
addVariant("main", commonResDirectories)
getVariantComponentNames(variantData)?.let { (variantName, flavorName, buildTypeName) ->
addSourceSetAsVariant(buildTypeName)
@@ -255,15 +263,11 @@ class AndroidSubplugin : KotlinGradleSubplugin<KotlinCompile> {
private data class VariantComponentNames(val variantName: String, val flavorName: String, val buildTypeName: String)
private fun getCommonResDirectories(resDirectories: List<List<File>>): Set<File> {
var common = resDirectories.firstOrNull()?.toSet() ?: return emptySet()
for (resDirs in resDirectories.drop(1)) {
common = common.intersect(resDirs)
}
return common
}
private fun getCommonResDirectories(project: Project, resDirectories: List<FileCollection>): FileCollection =
if (resDirectories.isEmpty())
project.files()
else
resDirectories.reduce { acc, other -> acc.filter { other.contains(it) } }
private fun getApplicationPackage(project: Project, mainSourceSet: AndroidSourceSet): String {
val manifestFile = mainSourceSet.manifest.srcFile
@@ -262,7 +262,7 @@ class Kapt3KotlinGradleSubplugin : KotlinGradleSubplugin<KotlinCompile> {
val apOptions = getAPOptions()
pluginOptions += CompositeSubpluginOption("apoptions", encodeList(apOptions.associate { it.key to it.value }), apOptions)
pluginOptions += CompositeSubpluginOption("apoptions", lazy { encodeList(apOptions.associate { it.key to it.value }) }, apOptions)
pluginOptions += SubpluginOption("javacArguments", encodeList(kaptExtension.getJavacOptions()))
@@ -62,8 +62,12 @@ internal fun CompilerPluginOptions.withWrappedKaptOptions(withApClasspath: Itera
}
fun wrapPluginOptions(options: List<SubpluginOption>, newOptionName: String): List<SubpluginOption> {
val groupedOptions = options.groupBy { it.key }.mapValues { opt -> opt.value.map { it.value } }
val encodedOptions = encodePluginOptions(groupedOptions)
val encodedOptions = lazy {
val groupedOptions = options
.groupBy { it.key }
.mapValues { (_, options) -> options.map { it.value } }
encodePluginOptions(groupedOptions)
}
val singleOption = CompositeSubpluginOption(newOptionName, encodedOptions, options)
return listOf(singleOption)
}
@@ -25,7 +25,6 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptionsImpl
import org.jetbrains.kotlin.gradle.dsl.KotlinSingleJavaTargetExtension
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin
import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin.Companion.getKaptGeneratedClassesDir
import org.jetbrains.kotlin.gradle.internal.Kapt3KotlinGradleSubplugin
import org.jetbrains.kotlin.gradle.internal.KaptVariantData
@@ -630,7 +629,7 @@ abstract class AbstractAndroidProjectHandler<V>(private val kotlinConfigurationT
abstract fun forEachVariant(project: Project, action: (V) -> Unit): Unit
abstract fun getTestedVariantData(variantData: V): V?
abstract fun getResDirectories(variantData: V): List<File>
abstract fun getResDirectories(variantData: V): FileCollection
protected abstract fun getSourceProviders(variantData: V): Iterable<SourceProvider>
protected abstract fun getAllJavaSources(variantData: V): Iterable<File>
@@ -875,7 +874,7 @@ internal fun Task.registerSubpluginOptionsAsInputs(subpluginId: String, subplugi
is InternalSubpluginOption -> Unit
is CompositeSubpluginOption -> {
val subpluginIdWithWrapperKey = "$subpluginId.${optionKey}$indexSuffix"
val subpluginIdWithWrapperKey = "$subpluginId.$optionKey$indexSuffix"
registerSubpluginOptionsAsInputs(subpluginIdWithWrapperKey, option.originalOptions)
}
@@ -884,7 +883,7 @@ internal fun Task.registerSubpluginOptionsAsInputs(subpluginId: String, subplugi
}.run { /* exhaustive when */ }
else -> {
inputsCompatible.propertyCompatible("$subpluginId." + option.key + indexSuffix, option.value)
inputsCompatible.propertyCompatible("$subpluginId." + option.key + indexSuffix, Callable { option.value })
}
}
}
@@ -14,6 +14,7 @@ import com.android.build.gradle.internal.variant.TestVariantData
import com.android.builder.model.SourceProvider
import org.gradle.api.Project
import org.gradle.api.ProjectConfigurationException
import org.gradle.api.file.FileCollection
import org.gradle.api.tasks.compile.AbstractCompile
import org.jetbrains.kotlin.gradle.internal.KaptTask
import org.jetbrains.kotlin.gradle.internal.KaptVariantData
@@ -23,6 +24,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.gradle.utils.checkedReflection
import java.io.File
import java.util.concurrent.Callable
internal class LegacyAndroidAndroidProjectHandler(kotlinConfigurationTools: KotlinConfigurationTools) :
AbstractAndroidProjectHandler<BaseVariantData<out BaseVariantOutputData>>(kotlinConfigurationTools) {
@@ -110,8 +112,11 @@ internal class LegacyAndroidAndroidProjectHandler(kotlinConfigurationTools: Kotl
override fun getTestedVariantData(variantData: BaseVariantData<*>): BaseVariantData<*>? =
((variantData as? TestVariantData)?.testedVariantData as? BaseVariantData<*>)
override fun getResDirectories(variantData: BaseVariantData<out BaseVariantOutputData>): List<File> {
return variantData.mergeResourcesTask?.rawInputFolders?.toList() ?: emptyList()
override fun getResDirectories(variantData: BaseVariantData<out BaseVariantOutputData>): FileCollection {
val project = variantData.mergeResourcesTask.project
return project.files(
Callable { variantData.mergeResourcesTask?.rawInputFolders?.toList().orEmpty() }
)
}
private val BaseVariantData<*>.sourceProviders: List<SourceProvider>
@@ -19,16 +19,17 @@ package org.jetbrains.kotlin.gradle.tasks
import org.jetbrains.kotlin.gradle.plugin.SubpluginOption
class CompilerPluginOptions {
private val mutableArguments = arrayListOf<String>()
internal val subpluginOptionsByPluginId =
mutableMapOf<String, MutableList<SubpluginOption>>()
val arguments: List<String>
get() = mutableArguments
get() = subpluginOptionsByPluginId.flatMap { (pluginId, subplubinOptions) ->
subplubinOptions.map { option ->
"plugin:$pluginId:${option.key}=${option.value}"
}
}
fun addPluginArgument(pluginId: String, option: SubpluginOption) {
mutableArguments.add("plugin:$pluginId:${option.key}=${option.value}")
subpluginOptionsByPluginId.getOrPut(pluginId) { mutableListOf() }.add(option)
}
}