diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/ConfigurationAvoidanceIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/ConfigurationAvoidanceIT.kt index bb492da820d..f29ca5b1198 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/ConfigurationAvoidanceIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/ConfigurationAvoidanceIT.kt @@ -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) diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/SimpleKotlinGradleIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/SimpleKotlinGradleIT.kt index a833d5b8702..88b1bc95210 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/SimpleKotlinGradleIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/SimpleKotlinGradleIT.kt @@ -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() + } + } + } + } + } } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin2JsProjectWithCustomSourceset/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin2JsProjectWithCustomSourceset/build.gradle index da2613bc19c..ba39c3ec18f 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin2JsProjectWithCustomSourceset/build.gradle +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin2JsProjectWithCustomSourceset/build.gradle @@ -43,6 +43,7 @@ dependencies { task integrationTestJar(type: Jar) { from sourceSets.integrationTest.output + from sourceSets.integrationTest.kotlin.classesDirectory classifier = 'inttests' } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/simpleProject/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/simpleProject/build.gradle index ffda2c07b45..61778998b21 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/simpleProject/build.gradle +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/simpleProject/build.gradle @@ -18,6 +18,6 @@ dependencies { testImplementation 'org.testng:testng:6.8' } -test { +tasks.named("test").configure { useTestNG() } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/simpleProject/src/main/kotlin/helloWorld.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/simpleProject/src/main/kotlin/helloWorld.kt index b3e99bbdd59..0ba95e3f4bd 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/simpleProject/src/main/kotlin/helloWorld.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/simpleProject/src/main/kotlin/helloWorld.kt @@ -6,15 +6,17 @@ import java.util.ArrayList class KotlinGreetingJoiner(val greeter : Greeter) { - val names = ArrayList() + private val names = ArrayList() 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 = names.toList() } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/simpleProject/src/test/kotlin/tests.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/simpleProject/src/test/kotlin/tests.kt index 1a7a3a23b80..192a962d305 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/simpleProject/src/test/kotlin/tests.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/simpleProject/src/test/kotlin/tests.kt @@ -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) } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinCompilationInfo.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinCompilationInfo.kt index fdc16987585..5d5063c674f 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinCompilationInfo.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinCompilationInfo.kt @@ -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 diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinSourceSetProcessor.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinSourceSetProcessor.kt index 99c3b728fa2..50d415e63b5 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinSourceSetProcessor.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinSourceSetProcessor.kt @@ -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>( private fun prepareKotlinCompileTask(): TaskProvider = 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::defaultDestinationDirectory + ) + (kotlinSourceDirectorySet.classesDirectory as Property).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() { diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationAssociator.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationAssociator.kt index f3353513b0a..e5d6bc5405e 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationAssociator.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationAssociator.kt @@ -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, diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationFriendPathsResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationFriendPathsResolver.kt index cf5502fce67..46c499ddde9 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationFriendPathsResolver.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/compilationImpl/KotlinCompilationFriendPathsResolver.kt @@ -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 { return mutableListOf().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).flatMap { task -> task.destinationDirectory } + ) + ) + } add(friendArtifactResolver.resolveFriendArtifacts(compilation)) } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/GradleKpmCompilationTaskConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/GradleKpmCompilationTaskConfigurator.kt index 7b6ded55b1d..bfe8d56bb1d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/GradleKpmCompilationTaskConfigurator.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/pm20/GradleKpmCompilationTaskConfigurator.kt @@ -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 }