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") 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. * 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 * 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 args.moduleName = moduleName
logger.kotlinDebug { "args.moduleName = ${args.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() ?: "[]"}" } logger.kotlinDebug { "args.friendPaths = ${args.friendPaths?.joinToString() ?: "[]"}" }
if (DefaultsOnly in flags) return 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.api.*
import com.android.build.gradle.tasks.MergeResources import com.android.build.gradle.tasks.MergeResources
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.artifacts.ArtifactCollection
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.specs.Spec
import org.gradle.api.tasks.PathSensitivity 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
@@ -26,6 +28,9 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.gradle.tasks.thisTaskProvider 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.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.io.Serializable
import java.util.concurrent.Callable import java.util.concurrent.Callable
class Android25ProjectHandler( 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 // 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( compilation.testedVariantArtifacts.set(
project.files( originalArtifactCollection.artifactFiles.filter(
project.provider { AndroidTestedVariantArtifactsFilter(
variantData.getCompileClasspathArtifacts(preJavaClasspathKey) originalArtifactCollection,
.filter { testedVariantDataIsNotNull,
it.id.componentIdentifier is TestedComponentIdentifier || projectPath
// 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 }
}
) )
) )
@@ -214,3 +215,42 @@ private fun MergeResources.computeResourceSetList0(): List<File>? {
computeResourceSetListMethod.isAccessible = oldIsAccessible 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) classpath = classpathList.joinToString(File.pathSeparator)
destination = destinationDir.canonicalPath destination = destinationDir.canonicalPath
friendPaths = this@KotlinCompileCommon.friendPaths friendPaths = this@KotlinCompileCommon.friendPaths.files.map { it.absolutePath }.toTypedArray()
refinesPaths = refinesMetadataPaths.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 @get:Internal // takes part in the compiler arguments
val friendPaths: Array<String> by project.provider { val friendPaths: FileCollection = project.files(
taskData.compilation.run { project.provider {
if (this !is AbstractKotlinCompilation<*>) return@run emptyArray<String>() taskData.compilation.run {
associateWithTransitiveClosure if (this !is AbstractKotlinCompilation<*>) return@run project.files()
.flatMap { it.output.classesDirs } mutableListOf<FileCollection>().also { allCollections ->
.plus(friendArtifacts) associateWithTransitiveClosure.forEach { allCollections.add(it.output.classesDirs) }
.map { it.absolutePath }.toTypedArray() allCollections.add(friendArtifacts)
}
}
} }
} )
private val kotlinLogger by lazy { GradleKotlinLogger(logger) } private val kotlinLogger by lazy { GradleKotlinLogger(logger) }
@@ -415,7 +417,7 @@ class KotlinJvmCompilerArgumentsProvider
(taskProvider: KotlinCompile) : KotlinCompileArgumentsProvider<KotlinCompile>(taskProvider) { (taskProvider: KotlinCompile) : KotlinCompileArgumentsProvider<KotlinCompile>(taskProvider) {
val moduleName: String val moduleName: String
val friendPaths: Array<String> val friendPaths: FileCollection
val compileClasspath: Iterable<File> val compileClasspath: Iterable<File>
val destinationDir: File val destinationDir: File
internal val kotlinOptions: List<KotlinJvmOptionsImpl?> internal val kotlinOptions: List<KotlinJvmOptionsImpl?>
@@ -643,10 +645,9 @@ open class Kotlin2JsCompile : AbstractKotlinCompile<K2JSCompilerArguments>(), Ko
internal val friendDependencies: List<String> internal val friendDependencies: List<String>
get() { get() {
val filter = libraryFilter val filter = libraryFilter
return friendPaths.filter { return friendPaths.files.filter {
val file = File(it) it.exists() && filter(it)
file.exists() && filter(file) }.map { it.absolutePath }
}
} }
@Suppress("unused") @Suppress("unused")