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
This commit is contained in:
+67
@@ -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"
|
||||||
|
}
|
||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
id 'org.jetbrains.kotlin.jvm'
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceCompatibility = 1.8
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
|
mavenCentral()
|
||||||
|
google()
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
plugins {
|
||||||
|
id "org.jetbrains.kotlin.jvm"
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
kotlinCompilerPluginClasspath project(':plugin')
|
||||||
|
}
|
||||||
+8
@@ -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
|
||||||
+8
@@ -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
|
||||||
+13
@@ -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"
|
||||||
|
}
|
||||||
+112
@@ -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<KtFile>
|
||||||
|
): 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<KotlinSourceRoot>): 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"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
@@ -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
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
include ':plugin'
|
||||||
|
include ':library'
|
||||||
Reference in New Issue
Block a user