Fix eager Kotlin/JVM task configuration on Gradle 7.3+

^KT-54836 Fixed
This commit is contained in:
Yahor Berdnikau
2022-11-07 16:37:24 +01:00
committed by Space Team
parent f8e6c59600
commit 2e5981eb94
11 changed files with 152 additions and 14 deletions
@@ -25,6 +25,30 @@ class ConfigurationAvoidanceIT : KGPBaseTest() {
} }
} }
@JvmGradlePluginTests
@GradleTestVersions(
maxVersion = TestVersions.Gradle.G_7_5,
additionalVersions = [TestVersions.Gradle.G_7_3, TestVersions.Gradle.G_7_4]
)
@DisplayName("KGP/Jvm does not eagerly configure any tasks")
@GradleTest
fun testJvmConfigurationAvoidance(gradleVersion: GradleVersion) {
project("simpleProject", gradleVersion) {
buildGradle.appendText(
"""
|
|tasks.configureEach {
| if (name != "help" && name != "clean") {
| throw new GradleException("Configuration avoidance failure for ${'$'}name!")
| }
|}
""".trimMargin()
)
build("--dry-run")
}
}
@AndroidGradlePluginTests @AndroidGradlePluginTests
@DisplayName("Android unrelated tasks are not configured") @DisplayName("Android unrelated tasks are not configured")
@AndroidTestVersions(minVersion = TestVersions.AGP.AGP_42) @AndroidTestVersions(minVersion = TestVersions.AGP.AGP_42)
@@ -9,6 +9,7 @@ import org.junit.jupiter.api.DisplayName
import kotlin.io.path.appendText import kotlin.io.path.appendText
import kotlin.io.path.createDirectories import kotlin.io.path.createDirectories
import kotlin.io.path.createFile import kotlin.io.path.createFile
import java.util.zip.ZipFile
@JvmGradlePluginTests @JvmGradlePluginTests
@DisplayName("KGP simple tests") @DisplayName("KGP simple tests")
@@ -202,7 +203,7 @@ class SimpleKotlinGradleIT : KGPBaseTest() {
@GradleTest @GradleTest
@DisplayName("Should be compatible with project isolation") @DisplayName("Should be compatible with project isolation")
@GradleTestVersions(minVersion = TestVersions.Gradle.G_7_1, maxVersion = TestVersions.Gradle.G_7_1) @GradleTestVersions(minVersion = TestVersions.Gradle.G_7_1)
fun testProjectIsolation(gradleVersion: GradleVersion) { fun testProjectIsolation(gradleVersion: GradleVersion) {
project( project(
projectName = "instantExecution", projectName = "instantExecution",
@@ -273,4 +274,49 @@ class SimpleKotlinGradleIT : KGPBaseTest() {
build("assemble") build("assemble")
} }
} }
@DisplayName("Changing compile task destination directory does not break test compilation")
@GradleTest
internal fun customDestinationDir(gradleVersion: GradleVersion) {
project("simpleProject", gradleVersion) {
//language=Groovy
buildGradle.appendText(
"""
|
|tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile.class).configureEach {
| if (it.name == "compileKotlin") {
| it.destinationDirectory.set(project.layout.buildDirectory.dir("banana"))
| }
|}
""".trimMargin()
)
build("build") {
assertFileInProjectExists("build/banana/demo/KotlinGreetingJoiner.class")
assertFileInProjectExists("build/libs/simpleProject.jar")
ZipFile(projectPath.resolve("build/libs/simpleProject.jar").toFile()).use { jar ->
assert(jar.entries().asSequence().count { it.name == "demo/KotlinGreetingJoiner.class" } == 1) {
"The jar should contain one entry `demo/KotlinGreetingJoiner.class` with no duplicates\n" +
jar.entries().asSequence().map { it.name }.joinToString()
}
}
}
}
}
@DisplayName("Default jar content should not contain duplicates")
@GradleTest
internal fun defaultJarContent(gradleVersion: GradleVersion) {
project("simpleProject", gradleVersion) {
build("build") {
assertFileInProjectExists("build/libs/simpleProject.jar")
ZipFile(projectPath.resolve("build/libs/simpleProject.jar").toFile()).use { jar ->
assert(jar.entries().asSequence().count { it.name == "demo/KotlinGreetingJoiner.class" } == 1) {
"The jar should contain one entry `demo/KotlinGreetingJoiner.class` with no duplicates\n" +
jar.entries().asSequence().map { it.name }.joinToString()
}
}
}
}
}
} }
@@ -43,6 +43,7 @@ dependencies {
task integrationTestJar(type: Jar) { task integrationTestJar(type: Jar) {
from sourceSets.integrationTest.output from sourceSets.integrationTest.output
from sourceSets.integrationTest.kotlin.classesDirectory
classifier = 'inttests' classifier = 'inttests'
} }
@@ -18,6 +18,6 @@ dependencies {
testImplementation 'org.testng:testng:6.8' testImplementation 'org.testng:testng:6.8'
} }
test { tasks.named("test").configure {
useTestNG() useTestNG()
} }
@@ -6,15 +6,17 @@ import java.util.ArrayList
class KotlinGreetingJoiner(val greeter : Greeter) { class KotlinGreetingJoiner(val greeter : Greeter) {
val names = ArrayList<String?>() private val names = ArrayList<String?>()
fun addName(name : String?): Unit{ fun addName(name : String?): Unit{
names.add(name) names.add(name)
} }
fun getJoinedGreeting() : String? { fun getJoinedGreeting() : String? {
val joiner = Joiner.on(" and ").skipNulls(); val joiner = Joiner.on(" and ").skipNulls();
return "${greeter.getGreeting()} ${joiner.join(names)}" return "${greeter.getGreeting()} ${joiner.join(names)}"
} }
internal fun getNames(): List<String?> = names.toList()
} }
@@ -1,10 +1,6 @@
package demo package demo
import com.google.common.primitives.Ints
import com.google.common.base.Joiner
import org.testng.Assert.* import org.testng.Assert.*
import org.testng.annotations.AfterMethod
import org.testng.annotations.BeforeMethod
import org.testng.annotations.Test as test import org.testng.annotations.Test as test
class TestSource() { class TestSource() {
@@ -16,6 +12,7 @@ class TestSource() {
example.addName("Hermione") example.addName("Hermione")
assertEquals(example.getJoinedGreeting(), "Hi Harry and Ron and Hermione") assertEquals(example.getJoinedGreeting(), "Hi Harry and Ron and Hermione")
assertEquals(example.getNames().size, 4)
} }
} }
@@ -164,6 +164,9 @@ internal val KotlinCompilationInfo.tcsOrNull: KotlinCompilationInfo.TCS?
is KotlinCompilationInfo.TCS -> this is KotlinCompilationInfo.TCS -> this
} }
internal val KotlinCompilationInfo.tcs: KotlinCompilationInfo.TCS
get() = this as KotlinCompilationInfo.TCS
internal val KotlinCompilationInfo.kpmOrNull: KotlinCompilationInfo.KPM? internal val KotlinCompilationInfo.kpmOrNull: KotlinCompilationInfo.KPM?
get() = when (this) { get() = when (this) {
is KotlinCompilationInfo.KPM -> this is KotlinCompilationInfo.KPM -> this
@@ -7,11 +7,14 @@ package org.jetbrains.kotlin.gradle.plugin
import org.gradle.api.DefaultTask import org.gradle.api.DefaultTask
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.file.Directory
import org.gradle.api.file.FileCollection import org.gradle.api.file.FileCollection
import org.gradle.api.file.SourceDirectorySet import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.logging.Logging import org.gradle.api.logging.Logging
import org.gradle.api.provider.Property
import org.gradle.api.tasks.SourceSet import org.gradle.api.tasks.SourceSet
import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.Jar
import org.jetbrains.kotlin.gradle.plugin.internal.JavaSourceSetsAccessor import org.jetbrains.kotlin.gradle.plugin.internal.JavaSourceSetsAccessor
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinWithJavaCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinWithJavaCompilation
@@ -50,7 +53,47 @@ internal abstract class KotlinSourceSetProcessor<T : AbstractKotlinCompile<*>>(
private fun prepareKotlinCompileTask(): TaskProvider<out T> = private fun prepareKotlinCompileTask(): TaskProvider<out T> =
doRegisterTask(project, compilationInfo.compileKotlinTaskName).also { task -> doRegisterTask(project, compilationInfo.compileKotlinTaskName).also { task ->
compilationInfo.classesDirs.from(task.flatMap { it.destinationDirectory }) // Workaround for case when 'compilationInfo.classesDirs' is actually Java 'SourceSet.output.classesDir'
// Gradle from 7.3 started to realize 'FileCollection's registered for 'BuildOutputCleanupRegistry'
// at the end of the configuration phase. One of this 'FileCollection' is 'SourceSetOutput'
// which causes 'KotlinCompile' tasks to be eagerly realized.
// Related commit into Gradle: https://github.com/gradle/gradle/commit/758adc5b0c5a5d27e2797a9fa8fd2690530c5de9
// Proper workaround is peeked from here:
// https://github.com/gradle/gradle/blob/16cbc3a678771b08418d086eb67e9934c9282dfb/subprojects/plugins/src/main/java/org/gradle/api/plugins/internal/JvmPluginsHelper.java#L142-L148
if (compilationInfo.tcsOrNull?.compilation is KotlinWithJavaCompilation<*, *>) {
val kotlinSourceDirectorySet = compilationInfo.tcs.compilation.defaultSourceSet.kotlin
kotlinSourceDirectorySet.destinationDirectory.value(defaultKotlinDestinationDir)
task.configure {
if (it is Kotlin2JsCompile) {
it.defaultDestinationDirectory.convention(kotlinSourceDirectorySet.destinationDirectory)
} else {
it.destinationDirectory.convention(kotlinSourceDirectorySet.destinationDirectory)
}
}
compilationInfo.classesDirs.from(kotlinSourceDirectorySet.destinationDirectory).builtBy(task)
if (compilationInfo.tcs.compilation.platformType == KotlinPlatformType.js) {
@Suppress("UNCHECKED_CAST")
kotlinSourceDirectorySet.compiledBy(
task as TaskProvider<Kotlin2JsCompile>,
Kotlin2JsCompile::defaultDestinationDirectory
)
(kotlinSourceDirectorySet.classesDirectory as Property<Directory>).set(task.flatMap { it.destinationDirectory })
} else {
kotlinSourceDirectorySet.compiledBy(task, AbstractKotlinCompile<*>::destinationDirectory)
}
if (compilationInfo.isMain) {
project.tasks.named(
compilationInfo.tcs.compilation.target.artifactsTaskName,
Jar::class.java
) {
it.from(kotlinSourceDirectorySet.classesDirectory)
}
}
} else {
compilationInfo.classesDirs.from(
task.flatMap { it.destinationDirectory }
)
}
} }
override fun run() { override fun run() {
@@ -34,6 +34,16 @@ internal object DefaultKotlinCompilationAssociator : KotlinCompilationAssociator
*/ */
project.dependencies.add(auxiliary.compileOnlyConfigurationName, project.files({ main.output.classesDirs })) project.dependencies.add(auxiliary.compileOnlyConfigurationName, project.files({ main.output.classesDirs }))
project.dependencies.add(auxiliary.runtimeOnlyConfigurationName, project.files({ main.output.allOutputs })) project.dependencies.add(auxiliary.runtimeOnlyConfigurationName, project.files({ main.output.allOutputs }))
// Adding classes that could be produced into non-default destination for JVM target
// Check KotlinSourceSetProcessor for details
project.dependencies.add(
auxiliary.implementationConfigurationName,
project.objects.fileCollection().from(
{
main.defaultSourceSet.kotlin.classesDirectory.orNull?.asFile
}
)
)
auxiliary.compileDependencyConfigurationName.addAllDependenciesFromOtherConfigurations( auxiliary.compileDependencyConfigurationName.addAllDependenciesFromOtherConfigurations(
project, project,
@@ -10,6 +10,8 @@ import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.AbstractArchiveTask import org.gradle.api.tasks.bundling.AbstractArchiveTask
import org.jetbrains.kotlin.gradle.plugin.mpp.* import org.jetbrains.kotlin.gradle.plugin.mpp.*
import org.jetbrains.kotlin.gradle.plugin.sources.getVisibleSourceSetsFromAssociateCompilations import org.jetbrains.kotlin.gradle.plugin.sources.getVisibleSourceSetsFromAssociateCompilations
import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinCompileTool
import org.jetbrains.kotlin.gradle.utils.filesProvider import org.jetbrains.kotlin.gradle.utils.filesProvider
internal interface KotlinCompilationFriendPathsResolver { internal interface KotlinCompilationFriendPathsResolver {
@@ -23,7 +25,17 @@ internal class DefaultKotlinCompilationFriendPathsResolver(
override fun resolveFriendPaths(compilation: InternalKotlinCompilation<*>): Iterable<FileCollection> { override fun resolveFriendPaths(compilation: InternalKotlinCompilation<*>): Iterable<FileCollection> {
return mutableListOf<FileCollection>().apply { return mutableListOf<FileCollection>().apply {
compilation.associateWithClosure.forEach { add(it.output.classesDirs) } compilation.associateWithClosure.forEach {
add(it.output.classesDirs)
// Adding classes that could be produced to non-default destination for JVM target
// Check KotlinSourceSetProcessor for details
@Suppress("UNCHECKED_CAST")
add(
compilation.project.files(
(it.compileTaskProvider as TaskProvider<KotlinCompileTool>).flatMap { task -> task.destinationDirectory }
)
)
}
add(friendArtifactResolver.resolveFriendArtifacts(compilation)) add(friendArtifactResolver.resolveFriendArtifacts(compilation))
} }
} }
@@ -42,7 +42,7 @@ open class GradleKpmCompilationTaskConfigurator(
val result = project.tasks.named(compilationData.compileKotlinTaskName, KotlinCompile::class.java) { val result = project.tasks.named(compilationData.compileKotlinTaskName, KotlinCompile::class.java) {
it.kotlinPluginData = project.compilerPluginProviderForPlatformCompilation(variant, compilationData) it.kotlinPluginData = project.compilerPluginProviderForPlatformCompilation(variant, compilationData)
} }
compilationData.output.classesDirs.from(result.map { it.destinationDirectory }) compilationData.output.classesDirs.from(result.flatMap { it.destinationDirectory })
return result return result
} }