From d253f9ba0eecaa5a0ee59baa2d133dceb1826bf2 Mon Sep 17 00:00:00 2001 From: Yahor Berdnikau Date: Thu, 25 Nov 2021 12:45:00 +0100 Subject: [PATCH] Add test to validate incremental changes in compiler plugin. If compiler plugin was recompiled incrementally, other projects in the same build should pick it up and regenerate sources. ^KT-38570 Fixed --- .../gradle/CompilerPluginsIncrementalIT.kt | 67 +++++++++++ .../incrementalChangeInPlugin/build.gradle | 14 +++ .../library/build.gradle | 7 ++ .../src/main/kotlin/library/SomeClass.kt | 8 ++ .../src/main/kotlin/library/SomeInterface.kt | 8 ++ .../plugin/build.gradle | 13 ++ .../compiler/plugin/TestComponentRegistrar.kt | 112 ++++++++++++++++++ ....kotlin.compiler.plugin.ComponentRegistrar | 6 + .../incrementalChangeInPlugin/settings.gradle | 2 + 9 files changed, 237 insertions(+) create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/CompilerPluginsIncrementalIT.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/build.gradle create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/library/build.gradle create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/library/src/main/kotlin/library/SomeClass.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/library/src/main/kotlin/library/SomeInterface.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/plugin/build.gradle create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/plugin/src/main/kotlin/test/compiler/plugin/TestComponentRegistrar.kt create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/plugin/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/settings.gradle diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/CompilerPluginsIncrementalIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/CompilerPluginsIncrementalIT.kt new file mode 100644 index 00000000000..c40b0474383 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/CompilerPluginsIncrementalIT.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2010-2021 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.util.GradleVersion +import org.jetbrains.kotlin.gradle.testbase.* +import org.junit.jupiter.api.DisplayName +import kotlin.io.path.exists + +@DisplayName("Compiler plugin incremental compilation") +@OtherGradlePluginTests +class CompilerPluginsIncrementalIT : KGPBaseTest() { + override val defaultBuildOptions: BuildOptions + get() = super.defaultBuildOptions.copy( + incremental = true + ) + + @DisplayName("KT-38570: After changing compiler plugin code, next incremental build picks it up") + @GradleTest + internal fun afterChangeInPluginBuildDoesIncrementalProcessing(gradleVersion: GradleVersion) { + project("incrementalChangeInPlugin".prefix, gradleVersion) { + build("assemble") + + val genDir = subProject("library").projectPath.resolve("build/sample-dir/plugin/test/gen") + assert(genDir.exists()) { "$genDir does not exists!" } + + val generatedFiles = genDir.toFile() + .walkTopDown() + .asSequence() + .filterNot { it.isDirectory } + .associate { + it.absolutePath to it.readBytes() + } + + subProject("plugin") + .kotlinSourcesDir() + .resolve("test/compiler/plugin/TestComponentRegistrar.kt") + .modify { + it.replace("world.", "something else.") + } + + build("assemble") { + assert(genDir.exists()) { "$genDir does not exists!" } + } + + genDir.toFile() + .walkTopDown() + .asSequence() + .filterNot { it.isDirectory } + .forEach { + assert(!it.readBytes().contentEquals(generatedFiles[it.absolutePath])) { + """ + + File ${it.absolutePath} content is equals to previous one! + New content: + ${it.readText()} + """.trimIndent() + } + } + } + } + + private val String.prefix get() = "compilerPlugins/$this" +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/build.gradle new file mode 100644 index 00000000000..62254c6f99f --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/build.gradle @@ -0,0 +1,14 @@ +plugins { + id 'java' + id 'org.jetbrains.kotlin.jvm' +} + +sourceCompatibility = 1.8 + +allprojects { + repositories { + mavenLocal() + mavenCentral() + google() + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/library/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/library/build.gradle new file mode 100644 index 00000000000..f048d835541 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/library/build.gradle @@ -0,0 +1,7 @@ +plugins { + id "org.jetbrains.kotlin.jvm" +} + +dependencies { + kotlinCompilerPluginClasspath project(':plugin') +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/library/src/main/kotlin/library/SomeClass.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/library/src/main/kotlin/library/SomeClass.kt new file mode 100644 index 00000000000..7487685001d --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/library/src/main/kotlin/library/SomeClass.kt @@ -0,0 +1,8 @@ +/* + * Copyright 2010-2021 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 library + +class SomeClass \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/library/src/main/kotlin/library/SomeInterface.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/library/src/main/kotlin/library/SomeInterface.kt new file mode 100644 index 00000000000..e317ae986cf --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/library/src/main/kotlin/library/SomeInterface.kt @@ -0,0 +1,8 @@ +/* + * Copyright 2010-2021 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 library + +interface SomeInterface diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/plugin/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/plugin/build.gradle new file mode 100644 index 00000000000..73c06f3a9d2 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/plugin/build.gradle @@ -0,0 +1,13 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-gradle-plugin-api" + + compileOnly "org.jetbrains.kotlin:kotlin-compiler-embeddable" +} + +compileKotlin { + kotlinOptions.jvmTarget = "1.8" +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/plugin/src/main/kotlin/test/compiler/plugin/TestComponentRegistrar.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/plugin/src/main/kotlin/test/compiler/plugin/TestComponentRegistrar.kt new file mode 100644 index 00000000000..e04d6ac22b4 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/plugin/src/main/kotlin/test/compiler/plugin/TestComponentRegistrar.kt @@ -0,0 +1,112 @@ +/* + * Copyright 2010-2021 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 test.compiler.plugin + +import org.jetbrains.kotlin.analyzer.AnalysisResult +import org.jetbrains.kotlin.analyzer.AnalysisResult.RetryWithAdditionalRoots +import org.jetbrains.kotlin.cli.common.config.KotlinSourceRoot +import org.jetbrains.kotlin.cli.common.config.kotlinSourceRoots +import org.jetbrains.kotlin.com.intellij.mock.MockProject +import org.jetbrains.kotlin.com.intellij.openapi.project.Project +import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.resolve.BindingTrace +import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension +import java.io.File + +class TestComponentRegistrar : ComponentRegistrar { + override fun registerProjectComponents( + project: MockProject, + configuration: CompilerConfiguration + ) { + val kotlinSourceRoots = configuration.kotlinSourceRoots.ifEmpty { return } + val sourceGenFolder = createSourceGenFolder(kotlinSourceRoots) + + var counter = 0 + + AnalysisHandlerExtension.registerExtension(project, object : AnalysisHandlerExtension { + private var didRecompile = false + + override fun analysisCompleted( + project: Project, + module: ModuleDescriptor, + bindingTrace: BindingTrace, + files: Collection + ): AnalysisResult? { + if (didRecompile) return null + didRecompile = true + + val classes = files + .flatMap { ktFile -> + val packageName = ktFile.packageFqName.asString() + ktFile.findChildrenByClass(KtClassOrObject::class.java) + .map { packageName + "." + it.nameAsSafeName.asString() } + } + .mapNotNull { + module.findClassAcrossModuleDependencies(ClassId.topLevel(FqName(it))) + } + + // Nothing to do. + if (classes.isEmpty()) return null + + classes.forEach { classDescriptor -> + val className = classDescriptor.name.asString() + + val directory = File(sourceGenFolder, "plugin/test/gen") + val file = File(directory, "$className.kt") + .also { + check(it.parentFile.exists() || it.parentFile.mkdirs()) { + "Could not generate package directory: $this" + } + } + + file.writeText( + """ + package plugin.test.gen + + val hello${counter++} = "world." + """.trimIndent() + ) + } + + // This restarts the analysis phase and will include our file. + return RetryWithAdditionalRoots( + bindingTrace.bindingContext, module, emptyList(), listOf(sourceGenFolder), addToEnvironment = true + ) + } + }) + } + + private fun createSourceGenFolder(kotlinSourceRoots: List): File { + fun sampleDir(parent: File): File = File(parent, "sample-dir").also { + check(it.exists() || it.mkdirs()) { + "Could not create source generation directory: $it" + } + } + + val oneSourceFile = kotlinSourceRoots.first().path.let { File(it) } + val parentSequence = generateSequence(oneSourceFile) { it.parentFile } + + // Try to find the src dir. + parentSequence.firstOrNull { it.name == "src" } + ?.let { return sampleDir(File(it.parentFile, "build")) } + + // If the src dir is not part of the input (incremental build), look for the build dir + // directly. + parentSequence.firstOrNull { it.name == "build" } + ?.let { return sampleDir(it) } + + throw IllegalStateException( + "Could not create source generation directory: $oneSourceFile" + ) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/plugin/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/plugin/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar new file mode 100644 index 00000000000..f5a262d7b90 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/plugin/src/main/resources/META-INF/services/org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar @@ -0,0 +1,6 @@ +# +# Copyright 2010-2021 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. +# + +test.compiler.plugin.TestComponentRegistrar \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/settings.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/settings.gradle new file mode 100644 index 00000000000..e348251d1ac --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/compilerPlugins/incrementalChangeInPlugin/settings.gradle @@ -0,0 +1,2 @@ +include ':plugin' +include ':library' \ No newline at end of file