[KGP] Treat friendPaths as non-incremental Input property

When friendPaths change, internal symbols from other modules become hidden
or visible. This might change which functions are called, which types
are returned, etc. So we should recompile the affected classes.

Gradle doesn't allow Input policy on FileCollection properties, so we
introduce friendPathsSet. It allows us to keep the ABI of BaseKotlinCompile
unchanged, detect non-incremental configuration changes on Gradle side,
and use normal incremental compilation for everything else.

^KT-61918 Fixed


Merge-request: KT-MR-12372
Merged-by: Evgenii Mazhukin <evgenii.mazhukin@jetbrains.com>
This commit is contained in:
Evgenii Mazhukin
2023-10-09 11:23:02 +00:00
committed by Space Team
parent d9ddac5571
commit 203b7a4da3
5 changed files with 128 additions and 0 deletions
@@ -0,0 +1,93 @@
/*
* Copyright 2010-2023 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.
*/
package org.jetbrains.kotlin.gradle
import org.gradle.api.logging.LogLevel
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.gradle.util.replaceFirst
import org.jetbrains.kotlin.gradle.util.replaceText
import org.junit.jupiter.api.DisplayName
@DisplayName("Incremental scenarios with associated compilation")
class AssociatedCompilationIT : KGPBaseTest() {
override val defaultBuildOptions: BuildOptions
get() = super.defaultBuildOptions.copy(logLevel = LogLevel.DEBUG)
@JvmGradlePluginTests
@DisplayName("Rebuild forced if associated compilation changes")
@GradleTest
fun testRebuildOnAssociatedCompilationChange(gradleVersion: GradleVersion) {
val project = getBaseProject(gradleVersion)
val testSrc = project.kotlinSourcesDir("integrationTest").resolve("test.kt")
val utilSrc = project.kotlinSourcesDir("main").resolve("util.kt")
project.build("compileIntegrationTestKotlin") {
assertCompiledKotlinSources(listOf(testSrc, utilSrc).relativizeTo(project.projectPath), output)
}
project.buildGradle.replaceFirst("associateWith(getByName(\"main\"))", "")
project.buildAndFail("compileIntegrationTestKotlin") {
assertTasksFailed(":compileIntegrationTestKotlin")
assertOutputContains("Cannot access 'fun doWork(): Int': it is internal in file")
}
}
@JvmGradlePluginTests
@DisplayName("Rebuild after used internal ABI change in associated compilation")
@GradleTest
fun testRebuildOnUsedApiChangeInAssociatedCompilation(gradleVersion: GradleVersion) {
val project = getBaseProject(gradleVersion)
val testSrc = project.kotlinSourcesDir("integrationTest").resolve("test.kt")
val utilSrc = project.kotlinSourcesDir("main").resolve("util.kt")
project.build("compileIntegrationTestKotlin")
utilSrc.replaceFirst("fun doWork() = 4", "fun doWork() = \"okay\"")
project.build("compileIntegrationTestKotlin") {
assertCompiledKotlinSources(listOf(testSrc, utilSrc).relativizeTo(project.projectPath), output)
}
}
@JvmGradlePluginTests
@DisplayName("No rebuild after private ABI change in associated compilation")
@GradleTest
fun testNoRebuildOnPrivateChangeInAssociatedCompilation(gradleVersion: GradleVersion) {
val project = getBaseProject(gradleVersion)
val utilSrc = project.kotlinSourcesDir("main").resolve("util.kt")
project.build("compileIntegrationTestKotlin")
utilSrc.append("private val bar = 2")
project.build("compileIntegrationTestKotlin") {
assertCompiledKotlinSources(listOf(utilSrc).relativizeTo(project.projectPath), output)
}
}
@JvmGradlePluginTests
@DisplayName("No rebuild after unused ABI change in associated compilation")
@GradleTest
fun testNoRebuildOnUnusedInternalChangeInAssociatedCompilation(gradleVersion: GradleVersion) {
val project = getBaseProject(gradleVersion)
val utilSrc = project.kotlinSourcesDir("main").resolve("util.kt")
project.build("compileIntegrationTestKotlin")
utilSrc.append("internal val bar = 2")
project.build("compileIntegrationTestKotlin") {
assertCompiledKotlinSources(listOf(utilSrc).relativizeTo(project.projectPath), output)
}
}
private fun getBaseProject(gradleVersion: GradleVersion): TestProject {
return project("kt-61918-associate-with-incremental", gradleVersion)
}
}
@@ -0,0 +1,23 @@
plugins {
id "org.jetbrains.kotlin.jvm"
}
repositories {
mavenLocal()
mavenCentral()
}
kotlin.target.compilations {
create("integrationTest") {
defaultSourceSet {
dependencies {
implementation(getByName("main").compileDependencyFiles + getByName("main").output.classesDirs)
}
}
}
//force task instantiation:
tasks.getByName("compileIntegrationTestKotlin")
named("integrationTest").configure { associateWith(getByName("main")) }
}
@@ -102,6 +102,16 @@ abstract class AbstractKotlinCompile<T : CommonCompilerArguments> @Inject constr
internal open fun isIncrementalCompilationEnabled(): Boolean =
incremental
// This allows us to treat friendPaths as Input rather than InputFiles
@get:Input
internal val friendPathsSet: Set<String>
get() {
val buildDirFile = projectLayout.buildDirectory.asFile.get()
return friendPaths
.filter { it.exists() }
.map { it.normalize().relativeTo(buildDirFile).path }.toSet()
}
@Deprecated("Scheduled for removal with Kotlin 2.0", ReplaceWith("moduleName"))
@get:Input
abstract val ownModuleName: Property<String>