KT-41295: Friend paths and compiler options compatible with conf caching

This change fixes serialization of friend paths and it uses
a file collection to record all friend paths. Also, when
computing the tested classpath for android projects, we now avoid
resolving the file collection until necessary.

Fixes: KT-41295
Test: ConfigurationCacheForAndroidIT
This commit is contained in:
Ivan Gavrilovic
2020-09-09 20:08:41 +01:00
committed by nataliya.valtman
parent be916e556a
commit 162dc3aa0c
7 changed files with 95 additions and 28 deletions
@@ -41,6 +41,12 @@ class ConfigurationCacheForAndroidIT : AbstractConfigurationCacheIT() {
testConfigurationCacheOf(":Lib:compileFlavor1DebugKotlin", ":Android:compileFlavor1DebugKotlin")
}
@Test
fun testKotlinAndroidProjectTests() = with(Project("AndroidIncrementalMultiModule")) {
applyAndroid40Alpha4KotlinVersionWorkaround()
testConfigurationCacheOf(":app:compileDebugAndroidTestKotlin", ":app:compileDebugUnitTestKotlin")
}
/**
* Android Gradle plugin 4.0-alpha4 depends on the EAP versions of some o.j.k modules.
* Force the current Kotlin version, so the EAP versions are not queried from the
@@ -0,0 +1,10 @@
import com.example.AppDummy
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
fun main(args: Array<String>) {
println(AppDummy())
}
@@ -0,0 +1,10 @@
import com.example.AppDummy
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
fun main(args: Array<String>) {
println(AppDummy())
}
@@ -99,7 +99,7 @@ internal open class KotlinJvmCompilerArgumentsContributor(
args.moduleName = moduleName
logger.kotlinDebug { "args.moduleName = ${args.moduleName}" }
args.friendPaths = friendPaths
args.friendPaths = friendPaths.files.map { it.absolutePath }.toTypedArray()
logger.kotlinDebug { "args.friendPaths = ${args.friendPaths?.joinToString() ?: "[]"}" }
if (DefaultsOnly in flags) return
@@ -11,9 +11,11 @@ import com.android.build.gradle.*
import com.android.build.gradle.api.*
import com.android.build.gradle.tasks.MergeResources
import org.gradle.api.Project
import org.gradle.api.artifacts.ArtifactCollection
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
import org.gradle.api.attributes.Attribute
import org.gradle.api.file.FileCollection
import org.gradle.api.specs.Spec
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.AbstractArchiveTask
@@ -26,6 +28,9 @@ 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.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.io.Serializable
import java.util.concurrent.Callable
class Android25ProjectHandler(
@@ -65,20 +70,16 @@ class Android25ProjectHandler(
}
// Find the classpath entries that come from the tested variant and register them as the friend paths, lazily
val originalArtifactCollection = variantData.getCompileClasspathArtifacts(preJavaClasspathKey)
val testedVariantDataIsNotNull = getTestedVariantData(variantData) != null
val projectPath = project.path
compilation.testedVariantArtifacts.set(
project.files(
project.provider {
variantData.getCompileClasspathArtifacts(preJavaClasspathKey)
.filter {
it.id.componentIdentifier is TestedComponentIdentifier ||
// If tests depend on the main classes transitively, through a test dependency on another module which
// depends on this module, then there's no artifact with a TestedComponentIdentifier, so consider the artifact of the
// current module a friend path, too:
getTestedVariantData(variantData) != null &&
(it.id.componentIdentifier as? ProjectComponentIdentifier)?.projectPath == project.path
}
.map { it.file }
}
originalArtifactCollection.artifactFiles.filter(
AndroidTestedVariantArtifactsFilter(
originalArtifactCollection,
testedVariantDataIsNotNull,
projectPath
)
)
)
@@ -213,4 +214,43 @@ private fun MergeResources.computeResourceSetList0(): List<File>? {
} finally {
computeResourceSetListMethod.isAccessible = oldIsAccessible
}
}
/** Filter for the AGP test variant classpath artifacts. */
class AndroidTestedVariantArtifactsFilter(
private val artifactCollection: ArtifactCollection,
private val testedVariantDataIsNotNull: Boolean,
private val projectPath: String
) : Serializable, Spec<File> {
/** Make transient as it should be derived from the [artifactCollection] property which may change in configuration cached runs. */
@Transient
private var filteredFiles = initFilteredFiles()
private fun initFilteredFiles(): Lazy<Set<File>> {
return lazy {
artifactCollection.filter {
it.id.componentIdentifier is TestedComponentIdentifier ||
// If tests depend on the main classes transitively, through a test dependency on another module which
// depends on this module, then there's no artifact with a TestedComponentIdentifier, so consider the artifact of the
// current module a friend path, too:
testedVariantDataIsNotNull &&
(it.id.componentIdentifier as? ProjectComponentIdentifier)?.projectPath == projectPath
}
.mapTo(mutableSetOf()) { it.file }
}
}
private fun writeObject(objectOutputStream: ObjectOutputStream) {
objectOutputStream.defaultWriteObject()
}
private fun readObject(objectInputStream: ObjectInputStream) {
objectInputStream.defaultReadObject()
filteredFiles = initFilteredFiles()
}
override fun isSatisfiedBy(element: File): Boolean {
return element in filteredFiles.value
}
}
@@ -70,7 +70,7 @@ open class KotlinCompileCommon : AbstractKotlinCompile<K2MetadataCompilerArgumen
classpath = classpathList.joinToString(File.pathSeparator)
destination = destinationDir.canonicalPath
friendPaths = this@KotlinCompileCommon.friendPaths
friendPaths = this@KotlinCompileCommon.friendPaths.files.map { it.absolutePath }.toTypedArray()
refinesPaths = refinesMetadataPaths.map { it.absolutePath }.toTypedArray()
}
@@ -288,15 +288,17 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments>() : AbstractKo
}
@get:Internal // takes part in the compiler arguments
val friendPaths: Array<String> by project.provider {
taskData.compilation.run {
if (this !is AbstractKotlinCompilation<*>) return@run emptyArray<String>()
associateWithTransitiveClosure
.flatMap { it.output.classesDirs }
.plus(friendArtifacts)
.map { it.absolutePath }.toTypedArray()
val friendPaths: FileCollection = project.files(
project.provider {
taskData.compilation.run {
if (this !is AbstractKotlinCompilation<*>) return@run project.files()
mutableListOf<FileCollection>().also { allCollections ->
associateWithTransitiveClosure.forEach { allCollections.add(it.output.classesDirs) }
allCollections.add(friendArtifacts)
}
}
}
}
)
private val kotlinLogger by lazy { GradleKotlinLogger(logger) }
@@ -415,7 +417,7 @@ class KotlinJvmCompilerArgumentsProvider
(taskProvider: KotlinCompile) : KotlinCompileArgumentsProvider<KotlinCompile>(taskProvider) {
val moduleName: String
val friendPaths: Array<String>
val friendPaths: FileCollection
val compileClasspath: Iterable<File>
val destinationDir: File
internal val kotlinOptions: List<KotlinJvmOptionsImpl?>
@@ -643,10 +645,9 @@ open class Kotlin2JsCompile : AbstractKotlinCompile<K2JSCompilerArguments>(), Ko
internal val friendDependencies: List<String>
get() {
val filter = libraryFilter
return friendPaths.filter {
val file = File(it)
file.exists() && filter(file)
}
return friendPaths.files.filter {
it.exists() && filter(it)
}.map { it.absolutePath }
}
@Suppress("unused")