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
@DisplayName("Android unrelated tasks are not configured")
@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.createDirectories
import kotlin.io.path.createFile
import java.util.zip.ZipFile
@JvmGradlePluginTests
@DisplayName("KGP simple tests")
@@ -202,7 +203,7 @@ class SimpleKotlinGradleIT : KGPBaseTest() {
@GradleTest
@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) {
project(
projectName = "instantExecution",
@@ -273,4 +274,49 @@ class SimpleKotlinGradleIT : KGPBaseTest() {
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) {
from sourceSets.integrationTest.output
from sourceSets.integrationTest.kotlin.classesDirectory
classifier = 'inttests'
}
@@ -18,6 +18,6 @@ dependencies {
testImplementation 'org.testng:testng:6.8'
}
test {
tasks.named("test").configure {
useTestNG()
}
@@ -6,15 +6,17 @@ import java.util.ArrayList
class KotlinGreetingJoiner(val greeter : Greeter) {
val names = ArrayList<String?>()
private val names = ArrayList<String?>()
fun addName(name : String?): Unit{
names.add(name)
}
fun getJoinedGreeting() : String? {
val joiner = Joiner.on(" and ").skipNulls();
return "${greeter.getGreeting()} ${joiner.join(names)}"
}
fun getJoinedGreeting() : String? {
val joiner = Joiner.on(" and ").skipNulls();
return "${greeter.getGreeting()} ${joiner.join(names)}"
}
internal fun getNames(): List<String?> = names.toList()
}
@@ -1,10 +1,6 @@
package demo
import com.google.common.primitives.Ints
import com.google.common.base.Joiner
import org.testng.Assert.*
import org.testng.annotations.AfterMethod
import org.testng.annotations.BeforeMethod
import org.testng.annotations.Test as test
class TestSource() {
@@ -16,6 +12,7 @@ class TestSource() {
example.addName("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
}
internal val KotlinCompilationInfo.tcs: KotlinCompilationInfo.TCS
get() = this as KotlinCompilationInfo.TCS
internal val KotlinCompilationInfo.kpmOrNull: KotlinCompilationInfo.KPM?
get() = when (this) {
is KotlinCompilationInfo.KPM -> this
@@ -7,11 +7,14 @@ package org.jetbrains.kotlin.gradle.plugin
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.file.Directory
import org.gradle.api.file.FileCollection
import org.gradle.api.file.SourceDirectorySet
import org.gradle.api.logging.Logging
import org.gradle.api.provider.Property
import org.gradle.api.tasks.SourceSet
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.mpp.KotlinJvmCompilation
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinWithJavaCompilation
@@ -50,7 +53,47 @@ internal abstract class KotlinSourceSetProcessor<T : AbstractKotlinCompile<*>>(
private fun prepareKotlinCompileTask(): TaskProvider<out T> =
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() {
@@ -34,6 +34,16 @@ internal object DefaultKotlinCompilationAssociator : KotlinCompilationAssociator
*/
project.dependencies.add(auxiliary.compileOnlyConfigurationName, project.files({ main.output.classesDirs }))
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(
project,
@@ -10,6 +10,8 @@ import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.AbstractArchiveTask
import org.jetbrains.kotlin.gradle.plugin.mpp.*
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
internal interface KotlinCompilationFriendPathsResolver {
@@ -23,7 +25,17 @@ internal class DefaultKotlinCompilationFriendPathsResolver(
override fun resolveFriendPaths(compilation: InternalKotlinCompilation<*>): Iterable<FileCollection> {
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))
}
}
@@ -42,7 +42,7 @@ open class GradleKpmCompilationTaskConfigurator(
val result = project.tasks.named(compilationData.compileKotlinTaskName, KotlinCompile::class.java) {
it.kotlinPluginData = project.compilerPluginProviderForPlatformCompilation(variant, compilationData)
}
compilationData.output.classesDirs.from(result.map { it.destinationDirectory })
compilationData.output.classesDirs.from(result.flatMap { it.destinationDirectory })
return result
}