Decouple KaptGenerateStubsTask from related KotlinCompile task configuration

This should simplify setup and remove task circular dependencies.

Generally users should not be affected by it unless they are configuring
 KotlinCompile task explicetly and expect the same configuration will be
  present on KaptGenerateStubsTask.

^KT-54468 In Progress
This commit is contained in:
Yahor Berdnikau
2023-04-20 20:31:46 +02:00
committed by Space Team
parent ee1517cd6a
commit 3ee089bb48
10 changed files with 142 additions and 132 deletions
@@ -1038,7 +1038,7 @@ open class Kapt3IT : Kapt3BaseIT() {
}
}
@DisplayName("KT-52761: generated sources attached to compile task are also used by generate stubs task")
@DisplayName("Generated sources attached to KotlinSourceSet are also used by generate stubs task")
@GradleTest
fun testGeneratedSourcesUsedInGenerateStubsTask(gradleVersion: GradleVersion) {
project("generatedSources".withPrefix, gradleVersion) {
@@ -192,8 +192,8 @@ class UpToDateIT : KGPBaseTest() {
}
override fun checkAfterRebuild(buildResult: BuildResult) = with(buildResult) {
assertTasksExecuted(":compileKotlin", ":kaptGenerateStubsKotlin")
assertTasksUpToDate(":kaptKotlin")
assertTasksExecuted(":compileKotlin")
assertTasksUpToDate(":kaptGenerateStubsKotlin", ":kaptKotlin")
}
}
@@ -11,7 +11,6 @@ import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.gradle.util.*
import org.junit.jupiter.api.DisplayName
import kotlin.io.path.appendText
import kotlin.io.path.readText
import kotlin.io.path.writeText
@DisplayName("android with kapt3 tests")
@@ -8,8 +8,10 @@ repositories {
mavenCentral()
}
tasks.named("compileKotlin", org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile.class) {
source("src/proto/generated")
sourceSets {
main {
kotlin.srcDir("src/proto/generated")
}
}
tasks.withType(JavaCompile.class).configureEach {
@@ -240,9 +240,10 @@ class Kapt3GradleSubplugin @Inject internal constructor(private val registry: To
kaptExtension.includeCompileClasspath
?: project.isIncludeCompileClasspath()
@Suppress("UNCHECKED_CAST")
val kotlinCompile: TaskProvider<KotlinCompile>
// Can't use just kotlinCompilation.compileKotlinTaskProvider, as the latter is not statically-known to be KotlinCompile
get() = checkNotNull(project.locateTask(kotlinCompilation.compileKotlinTaskName))
get() = kotlinCompilation.compileTaskProvider as TaskProvider<KotlinCompile>
}
override fun applyToCompilation(
@@ -355,7 +356,7 @@ class Kapt3GradleSubplugin @Inject internal constructor(private val registry: To
private fun Kapt3SubpluginContext.createKaptKotlinTask(
generateStubsTask: TaskProvider<KaptGenerateStubsTask>
): TaskProvider<out KaptTask> {
val taskName = getKaptTaskName("kapt")
val taskName = kotlinCompile.kaptTaskName
@Suppress("UNCHECKED_CAST")
val taskConfigAction = KaptWithoutKotlincConfig(
kotlinCompilation.project,
@@ -432,10 +433,10 @@ class Kapt3GradleSubplugin @Inject internal constructor(private val registry: To
}
private fun Kapt3SubpluginContext.createKaptGenerateStubsTask(): TaskProvider<KaptGenerateStubsTask> {
val kaptTaskName = getKaptTaskName("kaptGenerateStubs")
val kaptTaskName = kotlinCompile.kaptGenerateStubsTaskName
val kaptTaskProvider = project.registerTask<KaptGenerateStubsTask>(kaptTaskName)
val taskConfig = KaptGenerateStubsConfig(kotlinCompilation, kotlinCompile, classesOutputDir)
val taskConfig = KaptGenerateStubsConfig(kotlinCompilation)
taskConfig.configureTask {
it.stubsDir.set(getKaptStubsDir())
it.destinationDirectory.set(getKaptIncrementalDataDir())
@@ -451,13 +452,6 @@ class Kapt3GradleSubplugin @Inject internal constructor(private val registry: To
return kaptTaskProvider
}
private fun Kapt3SubpluginContext.getKaptTaskName(prefix: String): String {
// Replace compile*Kotlin to kapt*Kotlin
val baseName = kotlinCompile.name
assert(baseName.startsWith("compile"))
return baseName.replaceFirst("compile", prefix)
}
private fun Kapt3SubpluginContext.disableAnnotationProcessingInJavaTask() {
javaCompile?.configure { javaCompileInstance ->
if (javaCompileInstance !is JavaCompile)
@@ -491,6 +485,24 @@ class Kapt3GradleSubplugin @Inject internal constructor(private val registry: To
JetBrainsSubpluginArtifact(artifactId = KAPT_ARTIFACT_NAME)
}
internal const val KAPT_GENERATE_STUBS_PREFIX = "kaptGenerateStubs"
internal const val KAPT_PREFIX = "kapt"
internal val TaskProvider<out KotlinJvmCompile>.kaptGenerateStubsTaskName
get() = getKaptTaskName(name, KAPT_GENERATE_STUBS_PREFIX)
internal val TaskProvider<out KotlinJvmCompile>.kaptTaskName
get() = getKaptTaskName(name, KAPT_PREFIX)
internal fun getKaptTaskName(
kotlinCompileName: String,
prefix: String
): String {
// Replace compile*Kotlin to kapt*Kotlin
assert(kotlinCompileName.startsWith("compile"))
return kotlinCompileName.replaceFirst("compile", prefix)
}
internal fun buildKaptSubpluginOptions(
kaptExtension: KaptExtension,
project: Project,
@@ -25,7 +25,6 @@ import org.gradle.work.Incremental
import org.gradle.work.NormalizeLineEndings
import org.gradle.workers.WorkerExecutor
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptionsDefault
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptionsHelper
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerArgumentsProducer.CreateCompilerArgumentsContext
@@ -88,6 +87,9 @@ abstract class KaptGenerateStubsTask @Inject constructor(
@get:Internal
override val androidLayoutResources: FileCollection = objectFactory.fileCollection()
@get:Internal
abstract val kotlinCompileDestinationDirectory: DirectoryProperty
override val incrementalProps: List<FileCollection>
get() = listOf(
sources,
@@ -97,18 +99,10 @@ abstract class KaptGenerateStubsTask @Inject constructor(
classpathSnapshotProperties.classpathSnapshot
)
@get:Internal
internal abstract val compileTaskCompilerOptions: Property<KotlinJvmCompilerOptions>
override fun createCompilerArguments(context: CreateCompilerArgumentsContext) = context.create<K2JVMCompilerArguments> {
primitive { args ->
args.allowNoSourceFiles = true
KotlinJvmCompilerOptionsHelper.fillCompilerArguments(compileTaskCompilerOptions.get(), args)
// Workaround for freeCompiler args duplication when they were configured for both this task
// and linked KotlinCompile task with the same values. For now linked KotlinCompile task
// freeCompilerArgs is used as convention for this task freeCompilerArgs
args.freeArgs = emptyList()
KotlinJvmCompilerOptionsHelper.fillCompilerArguments(compilerOptions, args)
overrideArgsUsingTaskModuleNameWithWarning(args)
@@ -16,6 +16,7 @@ import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin
import org.jetbrains.kotlin.gradle.scripting.internal.ScriptingGradleSubplugin
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider
import org.jetbrains.kotlin.gradle.tasks.configuration.KaptGenerateStubsConfig
import org.jetbrains.kotlin.gradle.tasks.configuration.KotlinCompileConfig
import org.jetbrains.kotlin.gradle.tasks.withType
import org.jetbrains.kotlin.gradle.utils.whenKaptEnabled
@@ -34,7 +35,14 @@ internal class Kotlin2JvmSourceSetProcessor(
taskName,
compilationInfo.compilerOptions.options as KotlinJvmCompilerOptions,
configAction
)
).also { kotlinTask ->
// Configuring here to not interfere with 'kotlin-android' plugin configuration for 'libraries' input
KaptGenerateStubsConfig.configureLibraries(
project,
kotlinTask,
{ compilationInfo.compileDependencyFiles }
)
}
}
override fun doTargetSpecificProcessing() {
@@ -59,6 +67,12 @@ internal class Kotlin2JvmSourceSetProcessor(
kotlinTask.configure { kotlinCompile ->
kotlinCompile.javaOutputDir.set(javaTask.flatMap { it.destinationDirectory })
}
KaptGenerateStubsConfig.wireJavaAndKotlinOutputs(
project,
javaTask,
kotlinTask
)
}
if (sourceSetName == SourceSet.MAIN_SOURCE_SET_NAME) {
@@ -9,6 +9,8 @@ import org.gradle.api.Project
import org.gradle.api.provider.Provider
import org.jetbrains.kotlin.gradle.dsl.KotlinCommonCompilerOptions
import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions
import org.jetbrains.kotlin.gradle.internal.KAPT_GENERATE_STUBS_PREFIX
import org.jetbrains.kotlin.gradle.internal.getKaptTaskName
import org.jetbrains.kotlin.gradle.plugin.HasCompilerOptions
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.archivesName
@@ -52,16 +54,20 @@ internal fun addSourcesToKotlinCompileTask(
// The `commonSourceSet` is passed to the compiler as-is, converted with toList
commonSourceSet.from(
Callable<Any> { if (addAsCommonSources.value) sources else emptyList<Any>() }
{ if (addAsCommonSources.value) sources else emptyList<Any>() }
)
}
project.tasks
// To configure a task that may have not yet been created at this point, use 'withType-matching-configureEach`:
.withType(AbstractKotlinCompile::class.java)
.matching { it.name == taskName }
.configureEach { compileKotlinTask ->
compileKotlinTask.configureAction()
val compileTaskName = compileKotlinTask.name
// We also should configure related Kapt* tasks as they are not pickup configuration from
// related KotlinJvmCompile to avoid circular task dependencies
val kaptGenerateStubsTaskName = getKaptTaskName(compileTaskName, KAPT_GENERATE_STUBS_PREFIX)
if (compileTaskName == taskName || kaptGenerateStubsTaskName == taskName) {
compileKotlinTask.configureAction()
}
}
}
@@ -26,7 +26,7 @@ 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.dsl.*
import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin
import org.jetbrains.kotlin.gradle.internal.*
import org.jetbrains.kotlin.gradle.internal.checkAndroidAnnotationProcessorDependencyUsage
import org.jetbrains.kotlin.gradle.logging.kotlinDebug
import org.jetbrains.kotlin.gradle.plugin.android.AndroidGradleWrapper
@@ -37,6 +37,7 @@ import org.jetbrains.kotlin.gradle.plugin.sources.android.KotlinAndroidSourceSet
import org.jetbrains.kotlin.gradle.plugin.sources.android.findKotlinSourceSet
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider
import org.jetbrains.kotlin.gradle.tasks.configuration.KaptGenerateStubsConfig
import org.jetbrains.kotlin.gradle.tasks.configuration.KotlinCompileConfig
import org.jetbrains.kotlin.gradle.tasks.thisTaskProvider
import org.jetbrains.kotlin.gradle.testing.internal.kotlinTestRegistry
@@ -46,7 +47,6 @@ import java.io.File
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.io.Serializable
import java.util.concurrent.Callable
internal class AndroidProjectHandler(
private val kotlinTasksProvider: KotlinTasksProvider
@@ -186,13 +186,22 @@ internal class AndroidProjectHandler(
task.destinationDirectory.set(project.layout.buildDirectory.dir("tmp/kotlin-classes/$variantDataName"))
task.description = "Compiles the $variantDataName kotlin."
}
tasksProvider.registerKotlinJVMTask(
val kotlinTask = tasksProvider.registerKotlinJVMTask(
project,
compilation.compileKotlinTaskName,
compilation.compilerOptions.options as KotlinJvmCompilerOptions,
configAction
)
// Need to move it into afterEvaluate, so it will be executed after KaptGenerateStubsConfig config actions
// Otherwise build will fail within AbstractKotlinCompileConfig trying to modify value with 'disallowChanges()' state
project.afterEvaluate {
KaptGenerateStubsConfig.configureUseModuleDetection(
project,
kotlinTask
) { value(true).disallowChanges() }
}
// Register the source only after the task is created, because the task is required for that:
compilation.source(defaultSourceSet)
@@ -250,11 +259,23 @@ internal class AndroidProjectHandler(
kotlinTask.configure { kotlinTaskInstance ->
kotlinTaskInstance.libraries
.from(variantData.getCompileClasspath(preJavaClasspathKey))
.from(Callable { AndroidGradleWrapper.getRuntimeJars(androidPlugin, androidExt) })
.from({ AndroidGradleWrapper.getRuntimeJars(androidPlugin, androidExt) })
kotlinTaskInstance.javaOutputDir.set(javaTask.flatMap { it.destinationDirectory })
}
KaptGenerateStubsConfig.configureLibraries(
project,
kotlinTask,
variantData.getCompileClasspath(preJavaClasspathKey),
{ AndroidGradleWrapper.getRuntimeJars(androidPlugin, androidExt) }
)
KaptGenerateStubsConfig.wireJavaAndKotlinOutputs(
project,
javaTask,
kotlinTask
)
// 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
@@ -306,7 +327,7 @@ internal class AndroidProjectHandler(
project.addExtendsFromRelation(name, compilation.runtimeDependencyConfigurationName)
}
val buildTypeAttrValue = project.objects.named(BuildTypeAttr::class.java, variant.buildType.name)
val buildTypeAttrValue = project.objects.named<BuildTypeAttr>(variant.buildType.name)
listOf(compilation.compileDependencyConfigurationName, compilation.runtimeDependencyConfigurationName).forEach {
project.configurations.findByName(it)?.attributes?.attribute(Attribute.of(BuildTypeAttr::class.java), buildTypeAttrValue)
}
@@ -6,11 +6,12 @@
package org.jetbrains.kotlin.gradle.tasks.configuration
import org.gradle.api.Project
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileCollection
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.specs.Spec
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.compile.AbstractCompile
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptions
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmCompilerOptionsHelper
import org.jetbrains.kotlin.gradle.dsl.KotlinTopLevelExtension
import org.jetbrains.kotlin.gradle.internal.*
import org.jetbrains.kotlin.gradle.internal.Kapt3GradleSubplugin.Companion.KAPT_SUBPLUGIN_ID
@@ -19,66 +20,29 @@ import org.jetbrains.kotlin.gradle.plugin.KaptExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationInfo
import org.jetbrains.kotlin.gradle.tasks.CompilerPluginOptions
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompilerArgumentsProvider
import org.jetbrains.kotlin.gradle.utils.isConfigurationCacheAvailable
import org.jetbrains.kotlin.gradle.utils.isGradleVersionAtLeast
import org.jetbrains.kotlin.gradle.utils.isParentOf
import java.io.File
import java.nio.file.Files
import java.util.concurrent.ConcurrentHashMap
import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
import org.jetbrains.kotlin.gradle.tasks.withType
import org.jetbrains.kotlin.gradle.utils.whenKaptEnabled
internal class KaptGenerateStubsConfig : BaseKotlinCompileConfig<KaptGenerateStubsTask> {
constructor(
compilation: KotlinCompilation<*>,
kotlinTaskProvider: TaskProvider<KotlinCompile>,
kaptClassesDir: File
compilation: KotlinCompilation<*>
) : super(KotlinCompilationInfo(compilation)) {
configureFromExtension(project.extensions.getByType(KaptExtension::class.java))
configureTask { task ->
val kotlinCompileTask = kotlinTaskProvider.get()
task.useModuleDetection.value(kotlinCompileTask.useModuleDetection).disallowChanges()
@Suppress("DEPRECATION")
task.moduleName.value(kotlinCompileTask.moduleName).disallowChanges()
task.libraries.from({ kotlinCompileTask.libraries - project.files(kaptClassesDir) })
task.compileTaskCompilerOptions.set(providers.provider { kotlinCompileTask.compilerOptions })
task.pluginOptions.addAll(kotlinCompileTask.pluginOptions)
task.compilerOptions.moduleName.convention(kotlinCompileTask.compilerOptions.moduleName)
task.compilerOptions.freeCompilerArgs.convention(kotlinCompileTask.compilerOptions.freeCompilerArgs)
// KotlinCompile will also have as input output from KaptGenerateStubTask and KaptTask
// We are filtering them to avoid failed UP-TO-DATE checks
val kaptJavaSourcesDir = Kapt3GradleSubplugin.getKaptGeneratedSourcesDir(
project,
compilation.compilationName
)
val kaptKotlinSourcesDir = Kapt3GradleSubplugin.getKaptGeneratedKotlinSourcesDir(
project,
compilation.compilationName
)
val destinationDirectory = task.destinationDirectory
val stubsDir = task.stubsDir
val kaptFilterSpec = KaptFilterSpec(destinationDirectory, stubsDir, kaptJavaSourcesDir, kaptKotlinSourcesDir)
// FileTree filtering approach fails with configuration cache until Gradle 7.5 leading to failed UP-TO-DATE checks
val kaptFilter = if (shouldUseFileTreeKaptFilter) {
FileTreeKaptInputsFilter(kaptFilterSpec)
} else {
FileCollectionKaptInputsFilter(kaptFilterSpec)
}
task.source(
kaptFilter.filtered(kotlinCompileTask.javaSources),
kaptFilter.filtered(kotlinCompileTask.sources),
configureTask { kaptGenerateStubsTask ->
// Syncing compiler options from related KotlinJvmCompile task
val jvmCompilerOptions = compilation.compilerOptions.options as KotlinJvmCompilerOptions
KotlinJvmCompilerOptionsHelper.syncOptionsAsConvention(
from = jvmCompilerOptions,
into = kaptGenerateStubsTask.compilerOptions
)
}
}
constructor(project: Project, ext: KotlinTopLevelExtension, kaptExtension: KaptExtension) : super(project, ext) {
configureFromExtension(kaptExtension)
configureTask { task ->
task.compileTaskCompilerOptions.set(
providers.provider { task.compilerOptions }
)
}
}
private fun configureFromExtension(kaptExtension: KaptExtension) {
@@ -118,53 +82,51 @@ internal class KaptGenerateStubsConfig : BaseKotlinCompileConfig<KaptGenerateStu
}
}
private abstract class CachingKaptInputsFilter {
private val filterCache = ConcurrentHashMap<File, Boolean>()
abstract fun filtered(fileCollection: FileCollection): FileCollection
protected fun isSatisfiedBy(file: File) = filterCache[file] ?: predicate(file).also { filterCache[file] = it }
abstract fun predicate(file: File): Boolean
}
// Drop `isEmptyDirectory` check after min supported Gradle version will be bumped to 6.8
// It will be covered by '@IgnoreEmptyDirectories' input annotation
private class FileCollectionKaptInputsFilter(val spec: KaptFilterSpec) : CachingKaptInputsFilter() {
override fun filtered(fileCollection: FileCollection): FileCollection {
return fileCollection.filter(::isSatisfiedBy)
}
override fun predicate(file: File) = !file.isEmptyDirectory && spec.isSatisfiedBy(file)
private val File.isEmptyDirectory: Boolean
get() = with(toPath()) {
Files.isDirectory(this) && !Files.list(this).use { it.findFirst().isPresent }
companion object {
internal fun wireJavaAndKotlinOutputs(
project: Project,
javaCompileTask: TaskProvider<out AbstractCompile>,
kotlinCompileTask: TaskProvider<out KotlinJvmCompile>
) {
project.whenKaptEnabled {
val kaptGenerateStubsTaskName = getKaptTaskName(kotlinCompileTask.name, KAPT_GENERATE_STUBS_PREFIX)
project.tasks.withType<KaptGenerateStubsTask>().configureEach { task ->
if (task.name == kaptGenerateStubsTaskName) {
task.javaOutputDir.set(javaCompileTask.flatMap { it.destinationDirectory })
task.kotlinCompileDestinationDirectory.set(kotlinCompileTask.flatMap { it.destinationDirectory })
}
}
}
}
private val shouldUseFileTreeKaptFilter
get() = isGradleVersionAtLeast(7, 5) || !isConfigurationCacheAvailable(project.gradle)
private class FileTreeKaptInputsFilter(val spec: KaptFilterSpec) : CachingKaptInputsFilter() {
override fun filtered(fileCollection: FileCollection): FileCollection {
return fileCollection.asFileTree.matching { it.include { elem -> isSatisfiedBy(elem.file) } }
}
override fun predicate(file: File) = spec.isSatisfiedBy(file)
}
internal fun configureLibraries(
project: Project,
kotlinCompileTask: TaskProvider<out KotlinJvmCompile>,
vararg paths: Any
) {
project.whenKaptEnabled {
val kaptGenerateStubsTaskName = getKaptTaskName(kotlinCompileTask.name, KAPT_GENERATE_STUBS_PREFIX)
project.tasks.withType<KaptGenerateStubsTask>().configureEach { task ->
if (task.name == kaptGenerateStubsTaskName) {
task.libraries.from(paths)
}
}
}
}
private class KaptFilterSpec(
private val destinationDirectory: DirectoryProperty,
private val stubsDir: DirectoryProperty,
private val kaptJavaSourcesDir: File,
private val kaptKotlinSourcesDir: File,
) : Spec<File> {
override fun isSatisfiedBy(element: File) = element.isSourceRootAllowed()
private fun File.isSourceRootAllowed(): Boolean =
!destinationDirectory.get().asFile.isParentOf(this) &&
!stubsDir.asFile.get().isParentOf(this) &&
!kaptJavaSourcesDir.isParentOf(this) &&
!kaptKotlinSourcesDir.isParentOf(this)
internal fun configureUseModuleDetection(
project: Project,
kotlinCompileTask: TaskProvider<out KotlinJvmCompile>,
config: Property<Boolean>.() -> Unit
) {
project.whenKaptEnabled {
val kaptGenerateStubsTaskName = getKaptTaskName(kotlinCompileTask.name, KAPT_GENERATE_STUBS_PREFIX)
project.tasks.withType<KaptGenerateStubsTask>().configureEach { task ->
if (task.name == kaptGenerateStubsTaskName) {
config(task.useModuleDetection)
}
}
}
}
}
}
}