Migrate KaptIncrementalWithIsolatingApt to new test DSL

^KT-45745 In Progress
This commit is contained in:
Yahor Berdnikau
2022-01-27 16:51:11 +01:00
committed by TeamCityServer
parent 9ca2ce6647
commit baa3610c7d
5 changed files with 352 additions and 393 deletions
@@ -6,6 +6,7 @@ import org.gradle.testkit.runner.BuildResult
import org.gradle.util.GradleVersion import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.testbase.* import org.jetbrains.kotlin.gradle.testbase.*
import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.DisplayName
import java.io.File
import java.nio.file.Path import java.nio.file.Path
import java.util.* import java.util.*
import kotlin.io.path.createDirectories import kotlin.io.path.createDirectories
@@ -31,10 +32,23 @@ open class KaptIncrementalIT : KGPBaseTest() {
kaptOptions = BuildOptions.KaptOptions(incrementalKapt = true) kaptOptions = BuildOptions.KaptOptions(incrementalKapt = true)
) )
protected open fun KGPBaseTest.kaptProject(
gradleVersion: GradleVersion,
buildOptions: BuildOptions = defaultBuildOptions,
buildJdk: File? = null,
test: TestProject.() -> Unit
): TestProject = project(
PROJECT_NAME,
gradleVersion,
buildOptions = buildOptions,
buildJdk = buildJdk,
test = test
)
@DisplayName("After adding new line compilation is incremental") @DisplayName("After adding new line compilation is incremental")
@GradleTest @GradleTest
fun testAddNewLine(gradleVersion: GradleVersion) { fun testAddNewLine(gradleVersion: GradleVersion) {
project(PROJECT_NAME, gradleVersion) { kaptProject(gradleVersion) {
build("clean", "build") build("clean", "build")
javaSourcesDir().resolve("bar/useB.kt").modify { "\n$it" } javaSourcesDir().resolve("bar/useB.kt").modify { "\n$it" }
@@ -49,8 +63,7 @@ open class KaptIncrementalIT : KGPBaseTest() {
@DisplayName("On rebuild without changes tasks should be UP-TO-DATE") @DisplayName("On rebuild without changes tasks should be UP-TO-DATE")
@GradleTest @GradleTest
fun testBasic(gradleVersion: GradleVersion) { fun testBasic(gradleVersion: GradleVersion) {
project( kaptProject(
PROJECT_NAME,
gradleVersion, gradleVersion,
buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.DEBUG) buildOptions = defaultBuildOptions.copy(logLevel = LogLevel.DEBUG)
) { ) {
@@ -77,7 +90,7 @@ open class KaptIncrementalIT : KGPBaseTest() {
@DisplayName("Successfully rebuild after error") @DisplayName("Successfully rebuild after error")
@GradleTest @GradleTest
fun testCompileError(gradleVersion: GradleVersion) { fun testCompileError(gradleVersion: GradleVersion) {
project(PROJECT_NAME, gradleVersion) { kaptProject(gradleVersion) {
build("assemble") build("assemble")
val bKt = javaSourcesDir().resolve("bar/B.kt") val bKt = javaSourcesDir().resolve("bar/B.kt")
@@ -100,7 +113,7 @@ open class KaptIncrementalIT : KGPBaseTest() {
@DisplayName("Change in the function body without changing the signature") @DisplayName("Change in the function body without changing the signature")
@GradleTest @GradleTest
fun testChangeFunctionBodyWithoutChangingSignature(gradleVersion: GradleVersion) { fun testChangeFunctionBodyWithoutChangingSignature(gradleVersion: GradleVersion) {
project(PROJECT_NAME, gradleVersion) { kaptProject(gradleVersion) {
build("build", buildOptions = buildOptions.copy(logLevel = LogLevel.DEBUG)) { build("build", buildOptions = buildOptions.copy(logLevel = LogLevel.DEBUG)) {
checkGenerated(kaptGeneratedToPath, *annotatedElements) checkGenerated(kaptGeneratedToPath, *annotatedElements)
checkNotGenerated(kaptGeneratedToPath, "notAnnotatedFun") checkNotGenerated(kaptGeneratedToPath, "notAnnotatedFun")
@@ -123,7 +136,7 @@ open class KaptIncrementalIT : KGPBaseTest() {
@DisplayName("Adding new annotated element") @DisplayName("Adding new annotated element")
@GradleTest @GradleTest
fun testAddAnnotatedElement(gradleVersion: GradleVersion) { fun testAddAnnotatedElement(gradleVersion: GradleVersion) {
project(PROJECT_NAME, gradleVersion) { kaptProject(gradleVersion) {
build("assemble") build("assemble")
val utilKt = javaSourcesDir().resolve("baz/util.kt") val utilKt = javaSourcesDir().resolve("baz/util.kt")
@@ -148,7 +161,7 @@ open class KaptIncrementalIT : KGPBaseTest() {
@DisplayName("Adding new annotation triggers kapt run") @DisplayName("Adding new annotation triggers kapt run")
@GradleTest @GradleTest
fun testAddAnnotation(gradleVersion: GradleVersion) { fun testAddAnnotation(gradleVersion: GradleVersion) {
project(PROJECT_NAME, gradleVersion) { kaptProject(gradleVersion) {
build("assemble") build("assemble")
val utilKt = javaSourcesDir().resolve("baz/util.kt") val utilKt = javaSourcesDir().resolve("baz/util.kt")
@@ -167,7 +180,7 @@ open class KaptIncrementalIT : KGPBaseTest() {
@DisplayName("Kapt run is incremental after source file was removed") @DisplayName("Kapt run is incremental after source file was removed")
@GradleTest @GradleTest
fun testRemoveSourceFile(gradleVersion: GradleVersion) { fun testRemoveSourceFile(gradleVersion: GradleVersion) {
project(PROJECT_NAME, gradleVersion) { kaptProject(gradleVersion) {
val kapt3IncDataPath = "build/tmp/kapt3/incrementalData/main" val kapt3IncDataPath = "build/tmp/kapt3/incrementalData/main"
val kapt3StubsPath = "build/tmp/kapt3/stubs/main" val kapt3StubsPath = "build/tmp/kapt3/stubs/main"
@@ -214,7 +227,7 @@ open class KaptIncrementalIT : KGPBaseTest() {
@DisplayName("Incremental kapt run is correct after removing all Kotlin sources") @DisplayName("Incremental kapt run is correct after removing all Kotlin sources")
@GradleTest @GradleTest
fun testRemoveAllKotlinSources(gradleVersion: GradleVersion) { fun testRemoveAllKotlinSources(gradleVersion: GradleVersion) {
project(PROJECT_NAME, gradleVersion) { kaptProject(gradleVersion) {
build("assemble") { build("assemble") {
assertFileInProjectExists("$KAPT3_STUBS_PATH/bar/UseBKt.java") assertFileInProjectExists("$KAPT3_STUBS_PATH/bar/UseBKt.java")
} }
@@ -259,7 +272,7 @@ open class KaptIncrementalIT : KGPBaseTest() {
@DisplayName("On all annotations remove kapt and compile runs incremenatally") @DisplayName("On all annotations remove kapt and compile runs incremenatally")
@GradleTest @GradleTest
fun testRemoveAnnotations(gradleVersion: GradleVersion) { fun testRemoveAnnotations(gradleVersion: GradleVersion) {
project(PROJECT_NAME, gradleVersion) { kaptProject(gradleVersion) {
build("assemble") build("assemble")
val bKt = javaSourcesDir().resolve("bar/B.kt") val bKt = javaSourcesDir().resolve("bar/B.kt")
@@ -294,7 +307,7 @@ open class KaptIncrementalIT : KGPBaseTest() {
@DisplayName("Changing annotated property type is handled correctly") @DisplayName("Changing annotated property type is handled correctly")
@GradleTest @GradleTest
fun testChangeAnnotatedPropertyType(gradleVersion: GradleVersion) { fun testChangeAnnotatedPropertyType(gradleVersion: GradleVersion) {
project(PROJECT_NAME, gradleVersion) { kaptProject(gradleVersion) {
build("assemble") build("assemble")
val bKt = javaSourcesDir().resolve("bar/B.kt") val bKt = javaSourcesDir().resolve("bar/B.kt")
@@ -315,7 +328,7 @@ open class KaptIncrementalIT : KGPBaseTest() {
@DisplayName("Change in inline delegate is handled correctly") @DisplayName("Change in inline delegate is handled correctly")
@GradleTest @GradleTest
fun testChangeInlineDelegate(gradleVersion: GradleVersion) { fun testChangeInlineDelegate(gradleVersion: GradleVersion) {
project(PROJECT_NAME, gradleVersion) { kaptProject(gradleVersion) {
build("assemble") build("assemble")
val file = javaSourcesDir().resolve("delegate/Usage.kt") val file = javaSourcesDir().resolve("delegate/Usage.kt")
@@ -5,299 +5,313 @@
package org.jetbrains.kotlin.gradle package org.jetbrains.kotlin.gradle
import org.gradle.api.JavaVersion
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.incapt.IncrementalAggregatingReferencingClasspathProcessor import org.jetbrains.kotlin.gradle.incapt.IncrementalAggregatingReferencingClasspathProcessor
import org.jetbrains.kotlin.gradle.incapt.IncrementalBinaryIsolatingProcessor import org.jetbrains.kotlin.gradle.incapt.IncrementalBinaryIsolatingProcessor
import org.jetbrains.kotlin.gradle.incapt.IncrementalProcessor import org.jetbrains.kotlin.gradle.incapt.IncrementalProcessor
import org.jetbrains.kotlin.gradle.incapt.IncrementalProcessorReferencingClasspath import org.jetbrains.kotlin.gradle.incapt.IncrementalProcessorReferencingClasspath
import org.jetbrains.kotlin.gradle.testbase.TestProject import org.jetbrains.kotlin.gradle.testbase.*
import org.jetbrains.kotlin.gradle.util.AGPVersion import org.junit.jupiter.api.DisplayName
import org.jetbrains.kotlin.gradle.util.modify import org.junit.jupiter.api.condition.DisabledOnOs
import org.junit.Assert.assertEquals import org.junit.jupiter.api.condition.OS
import org.junit.Assert.assertTrue
import org.junit.Assume
import org.junit.Test
import test.kt33617.MyClass import test.kt33617.MyClass
import java.io.File import java.io.File
import java.nio.file.Path import java.nio.file.Path
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream import java.util.zip.ZipOutputStream
import kotlin.io.path.ExperimentalPathApi import kotlin.io.path.*
import kotlin.io.path.readText import kotlin.test.assertEquals
import kotlin.io.path.writeText
@DisplayName("Kapt incremental tests with isolating apt")
class KaptIncrementalWithIsolatingApt : KaptIncrementalIT() { class KaptIncrementalWithIsolatingApt : KaptIncrementalIT() {
override fun getProject() = override val defaultBuildOptions = super.defaultBuildOptions.copy(
Project( incremental = true,
"kaptIncrementalCompilationProject", kaptOptions = super.defaultBuildOptions.kaptOptions!!.copy(
GradleVersionRequired.None verbose = true,
).apply { useWorkers = true,
incrementalKapt = true,
includeCompileClasspath = false
)
)
override fun KGPBaseTest.kaptProject(
gradleVersion: GradleVersion,
buildOptions: BuildOptions,
buildJdk: File?,
test: TestProject.() -> Unit
): TestProject {
return project(PROJECT_NAME, gradleVersion, buildOptions = buildOptions, buildJdk = buildJdk) {
setupIncrementalAptProject("ISOLATING") setupIncrementalAptProject("ISOLATING")
}
override fun defaultBuildOptions(): BuildOptions = test(this)
super.defaultBuildOptions().copy(
incremental = true,
kaptOptions = KaptOptions(
verbose = true,
useWorkers = true,
incrementalKapt = true,
includeCompileClasspath = false
)
)
@Test
fun testIncrementalChanges() {
val project = getProject()
project.build("clean", "build") {
assertSuccessful()
}
project.projectFile("useB.kt").modify { current -> "$current\nfun otherFunction() {}" }
project.build("build") {
assertSuccessful()
assertEquals(
setOf(
fileInWorkingDir("build/tmp/kapt3/stubs/main/bar/UseBKt.java").canonicalPath,
fileInWorkingDir("build/tmp/kapt3/stubs/main/error/NonExistentClass.java").canonicalPath
), getProcessedSources(output)
)
}
project.projectFile("B.kt").modify { current ->
val lastBrace = current.lastIndexOf("}")
current.substring(0, lastBrace) + "fun anotherFun() {}\n }"
}
project.build("build") {
assertSuccessful()
assertEquals(
setOf(
fileInWorkingDir("build/tmp/kapt3/stubs/main/bar/B.java").canonicalPath,
fileInWorkingDir("build/tmp/kapt3/stubs/main/bar/UseBKt.java").canonicalPath,
fileInWorkingDir("build/tmp/kapt3/stubs/main/error/NonExistentClass.java").canonicalPath
),
getProcessedSources(output)
)
} }
} }
@Test @DisplayName("Incremental changes")
fun testChangingAnnotationProcessorClasspath() { @GradleTest
val project = getProject() fun testIncrementalChanges(gradleVersion: GradleVersion) {
kaptProject(gradleVersion) {
build("clean", "assemble")
project.build("clean", "build") { javaSourcesDir().resolve("bar/useB.kt").modify { current -> "$current\nfun otherFunction() {}" }
assertSuccessful() build("assemble") {
} assertEquals(
setOf(
project.gradleBuildScript().appendText( "$KAPT3_STUBS_PATH/bar/UseBKt.java",
""" "$KAPT3_STUBS_PATH/error/NonExistentClass.java"
)
dependencies { .map { projectPath.resolve(it).toRealPath().toString() }
kapt 'com.google.guava:guava:12.0' .toSet(),
getProcessedSources(output)
)
}
javaSourcesDir().resolve("bar/B.kt").modify { current ->
val lastBrace = current.lastIndexOf("}")
current.substring(0, lastBrace) + "fun anotherFun() {}\n }"
}
build("assemble") {
assertEquals(
setOf(
"$KAPT3_STUBS_PATH/bar/B.java",
"$KAPT3_STUBS_PATH/bar/UseBKt.java",
"$KAPT3_STUBS_PATH/error/NonExistentClass.java",
)
.map { projectPath.resolve(it).toRealPath().toString() }
.toSet(),
getProcessedSources(output)
)
} }
""".trimIndent()
)
project.build("build") {
assertSuccessful()
assertContains("The input changes require a full rebuild for incremental task ':kaptKotlin'.")
} }
} }
@Test @DisplayName("On changing annotation processor classpath")
fun testUnchangedAnnotationProcessorClasspathButContentChanged() { @GradleTest
val project = getProject() fun testChangingAnnotationProcessorClasspath(gradleVersion: GradleVersion) {
val processorJar = project.projectDir.resolve("processor.jar").also { kaptProject(gradleVersion) {
ZipOutputStream(it.outputStream()).use { build("clean", "assemble")
// create an empty jar
}
}
project.gradleBuildScript().appendText(
"""
dependencies {
kapt files("processor.jar")
}
""".trimIndent()
)
project.build("clean", "build") { buildGradle.append(
assertSuccessful() """
}
dependencies {
kapt 'com.google.guava:guava:12.0'
}
""".trimIndent()
)
ZipOutputStream(processorJar.outputStream()).use { build("assemble") {
it.putNextEntry(ZipEntry("resource.txt")) assertOutputContains("The input changes require a full rebuild for incremental task ':kaptKotlin'.")
it.closeEntry() }
}
project.build("build") {
assertSuccessful()
assertContains("The input changes require a full rebuild for incremental task ':kaptKotlin'.")
} }
} }
@Test @DisplayName("On unchanged annotation processor classpath, but processor itself is updated")
fun testNonIncrementalWithUnrecognizedInputs() { @GradleTest
val project = getProject() fun testUnchangedAnnotationProcessorClasspathButContentChanged(gradleVersion: GradleVersion) {
kaptProject(gradleVersion) {
val additionalInputs = project.projectDir.resolve("additionalInputs").also { it.mkdirs() } val processorJar = projectPath.resolve("processor.jar").also {
project.gradleBuildScript().appendText( ZipOutputStream(it.outputStream()).use {
""" // create an empty jar
tasks.whenTaskAdded {
if (it.name == "kaptKotlin") {
it.getInputs().files("${additionalInputs.invariantSeparatorsPath}")
} }
} }
""".trimIndent()
)
project.build("clean", "build") { buildGradle.append(
assertSuccessful()
}
additionalInputs.resolve("layout.xml").createNewFile()
project.build("build") {
assertSuccessful()
assertContains("Incremental annotation processing (apt mode): false")
}
}
/** Regression test for https://youtrack.jetbrains.com/issue/KT-33617. */
@Test
fun testSourcesInCompileClasspathJars() {
val javaHome = File(System.getProperty("jdk9Home")!!)
Assume.assumeTrue("JDK 9 isn't available", javaHome.isDirectory)
val options = defaultBuildOptions().copy(javaHome = javaHome)
val project = getProject()
// create jar with .class and .java file for the same type
ZipOutputStream(project.projectDir.resolve("lib-with-sources.jar").outputStream()).use {
it.putNextEntry(ZipEntry("test/kt33617/MyClass.class"))
MyClass::class.java.classLoader.getResourceAsStream("test/kt33617/MyClass.class").use { input ->
it.write(input!!.readBytes())
}
it.closeEntry()
it.putNextEntry(ZipEntry("test/kt33617/MyClass.java"))
it.write(
""" """
package test.kt33617;
public class MyClass {} dependencies {
""".trimIndent().toByteArray(Charsets.UTF_8) kapt files("processor.jar")
}
""".trimIndent()
) )
it.closeEntry()
}
project.gradleBuildScript().appendText(
"""
dependencies {
implementation files('lib-with-sources.jar')
}
""".trimIndent()
)
project.projectFile("useB.kt").modify { current -> "$current\nfun otherFunction(param: test.kt33617.MyClass) {}" }
project.build("clean", "kaptKotlin", options = options) { build("clean", "assemble")
assertSuccessful()
ZipOutputStream(processorJar.outputStream()).use {
it.putNextEntry(ZipEntry("resource.txt"))
it.closeEntry()
}
build("assemble") {
assertOutputContains("The input changes require a full rebuild for incremental task ':kaptKotlin'.")
}
} }
} }
/** Regression test for https://youtrack.jetbrains.com/issue/KT-42182. */ @DisplayName("Runs non-incrementally on unrecognized inputs")
@Test @GradleTest
fun testGeneratedSourcesImpactedByClasspathChanges() { fun testNonIncrementalWithUnrecognizedInputs(gradleVersion: GradleVersion) {
val project = Project( kaptProject(gradleVersion) {
"kaptIncrementalCompilationProject", val additionalInputs = projectPath.resolve("additionalInputs").also { it.createDirectories() }
GradleVersionRequired.None buildGradle.appendText(
).apply {
setupIncrementalAptProject("ISOLATING", procClass = IncrementalProcessorReferencingClasspath::class.java)
}
project.gradleSettingsScript().appendText("\ninclude ':', ':lib'")
val classpathTypeSource = project.projectDir.resolve("lib").run {
mkdirs()
resolve("build.gradle").writeText(
""" """
plugins {
id 'java' tasks.whenTaskAdded {
if (it.name == "kaptKotlin") {
it.getInputs().files("${additionalInputs.invariantSeparatorsPathString}")
}
}
""".trimIndent()
)
build("clean", "assemble")
additionalInputs.resolve("layout.xml").createFile()
build("assemble") {
assertOutputContains("Incremental annotation processing (apt mode): false")
}
}
}
@DisplayName("KT-33617: sources in compile classpath jars")
@JdkVersions(versions = [JavaVersion.VERSION_1_9])
@GradleWithJdkTest
fun testSourcesInCompileClasspathJars(gradleVersion: GradleVersion, jdk: JdkVersions.ProvidedJdk) {
kaptProject(gradleVersion, buildJdk = jdk.location) {
// create jar with .class and .java file for the same type
ZipOutputStream(projectPath.resolve("lib-with-sources.jar").outputStream()).use {
it.putNextEntry(ZipEntry("test/kt33617/MyClass.class"))
MyClass::class.java.classLoader.getResourceAsStream("test/kt33617/MyClass.class").use { input ->
it.write(input!!.readBytes())
}
it.closeEntry()
it.putNextEntry(ZipEntry("test/kt33617/MyClass.java"))
it.write(
"""
package test.kt33617;
public class MyClass {}
""".trimIndent().toByteArray(Charsets.UTF_8)
)
it.closeEntry()
}
buildGradle.append(
"""
dependencies {
implementation files('lib-with-sources.jar')
}
""".trimIndent()
)
javaSourcesDir().resolve("bar/useB.kt").modify { current ->
"$current\nfun otherFunction(param: test.kt33617.MyClass) {}"
}
build("clean", "kaptKotlin")
}
}
@DisplayName("KT-42182: generated sources are updated on classpath changes")
@GradleTest
fun testGeneratedSourcesImpactedByClasspathChanges(gradleVersion: GradleVersion) {
project(PROJECT_NAME, gradleVersion) {
setupIncrementalAptProject(
"ISOLATING",
procClass = IncrementalProcessorReferencingClasspath::class.java
)
settingsGradle.append("\ninclude ':', ':lib'")
val classpathTypeSource = subProject("lib").run {
projectPath.createDirectory()
buildGradle.writeText(
"""
plugins {
id 'java'
}
""".trimIndent()
)
val source = javaSourcesDir()
.resolve(
IncrementalProcessorReferencingClasspath.CLASSPATH_TYPE
.replace(".", "/") + ".java"
)
source.parent.createDirectories()
source.writeText(
"""
package ${IncrementalProcessorReferencingClasspath.CLASSPATH_TYPE.substringBeforeLast(".")};
public class ${IncrementalProcessorReferencingClasspath.CLASSPATH_TYPE.substringAfterLast(".")} {}
""".trimIndent()
)
return@run source
}
buildGradle.appendText(
"""
dependencies {
implementation project(':lib')
} }
""".trimIndent() """.trimIndent()
) )
val source = resolve("src/main/java/" + IncrementalProcessorReferencingClasspath.CLASSPATH_TYPE.replace(".", "/") + ".java")
source.parentFile.mkdirs()
source.writeText( val annotatedKotlinStubs = listOf(
""" "$KAPT3_STUBS_PATH/foo/A.java",
package ${IncrementalProcessorReferencingClasspath.CLASSPATH_TYPE.substringBeforeLast(".")}; "$KAPT3_STUBS_PATH/bar/B.java",
public class ${IncrementalProcessorReferencingClasspath.CLASSPATH_TYPE.substringAfterLast(".")} {} "$KAPT3_STUBS_PATH/bar/UseBKt.java",
""".trimIndent() "$KAPT3_STUBS_PATH/baz/UtilKt.java",
"$KAPT3_STUBS_PATH/baz/UtilKt.java",
"$KAPT3_STUBS_PATH/error/NonExistentClass.java"
) )
return@run source
} val allKotlinStubs = annotatedKotlinStubs + listOf(
project.gradleBuildScript().appendText( "$KAPT3_STUBS_PATH/delegate/Delegate.java",
""" "$KAPT3_STUBS_PATH/delegate/Usage.java"
)
dependencies {
implementation project(':lib') build("clean", "assemble") {
assertEquals(
allKotlinStubs
.plus("src/main/java/foo/JavaClass.java")
.map { projectPath.resolve(it).toRealPath().toString() }.toSet(),
getProcessedSources(output)
)
} }
""".trimIndent()
)
val annotatedKotlinStubs = setOf( // change type that all generated sources reference
project.projectDir.resolve("build/tmp/kapt3/stubs/main/foo/A.java").canonicalPath, classpathTypeSource.writeText(
project.projectDir.resolve("build/tmp/kapt3/stubs/main/bar/B.java").canonicalPath, classpathTypeSource.readText().replace("}", "int i = 10;\n}")
project.projectDir.resolve("build/tmp/kapt3/stubs/main/bar/UseBKt.java").canonicalPath, )
project.projectDir.resolve("build/tmp/kapt3/stubs/main/baz/UtilKt.java").canonicalPath, build("assemble") {
project.projectDir.resolve("build/tmp/kapt3/stubs/main/baz/UtilKt.java").canonicalPath, assertEquals(
project.projectDir.resolve("build/tmp/kapt3/stubs/main/error/NonExistentClass.java").canonicalPath annotatedKotlinStubs.map { projectPath.resolve(it).toRealPath().toString() }.toSet(),
) getProcessedSources(output)
)
val allKotlinStubs = annotatedKotlinStubs + setOf( }
project.projectDir.resolve("build/tmp/kapt3/stubs/main/delegate/Delegate.java").canonicalPath,
project.projectDir.resolve("build/tmp/kapt3/stubs/main/delegate/Usage.java").canonicalPath
)
project.build("clean", "build") {
assertSuccessful()
assertEquals(allKotlinStubs + fileInWorkingDir("src/main/java/foo/JavaClass.java").canonicalPath, getProcessedSources(output))
}
// change type that all generated sources reference
classpathTypeSource.writeText(classpathTypeSource.readText().replace("}", "int i = 10;\n}"))
project.build("build") {
assertSuccessful()
assertEquals(annotatedKotlinStubs, getProcessedSources(output))
} }
} }
/** Regression test for KT-34340. */ @DisplayName("KT-34340: origins in classpath")
@Test @GradleTest
fun testIsolatingWithOriginsInClasspath() { @DisabledOnOs(OS.WINDOWS, disabledReason = "https://youtrack.jetbrains.com/issue/KTI-405")
//https://youtrack.jetbrains.com/issue/KTI-405 fun testIsolatingWithOriginsInClasspath(gradleVersion: GradleVersion) {
if (System.getProperty("os.name")?.toLowerCase()?.contains("windows") == true) return project(
"kaptIncrementalWithParceler",
gradleVersion,
buildOptions = defaultBuildOptions.copy(androidVersion = TestVersions.AGP.AGP_42)
) {
build("clean", ":mylibrary:assembleDebug")
val project = Project( subProject("baseLibrary")
projectName = "kaptIncrementalWithParceler" .javaSourcesDir()
).apply { .resolve("com/example/lib2/basemodule/BaseClassParcel.java")
setupWorkingDir() .modify { current ->
} current.replace("protected FieldClassParcel", "private FieldClassParcel")
val options = defaultBuildOptions().copy(androidGradlePluginVersion = AGPVersion.v4_2_0) }
project.build("clean", ":mylibrary:assembleDebug", options = options) {
assertSuccessful()
}
project.projectFile("BaseClassParcel.java").modify { current -> build(":mylibrary:assembleDebug") {
current.replace("protected FieldClassParcel", "private FieldClassParcel") assertEquals(
} listOf(
"mylibrary/src/main/java/com/example/lib/ExampleParcel.java",
project.build(":mylibrary:assembleDebug", options = options) { "baseLibrary/src/main/java/com/example/lib2/basemodule/BaseClassParcel.java",
assertSuccessful() ).map { projectPath.resolve(it).toRealPath().toString() }.toSet(),
assertEquals( getProcessedSources(output)
setOf( )
fileInWorkingDir("mylibrary/src/main/java/com/example/lib/ExampleParcel.java").canonicalPath, }
fileInWorkingDir("baseLibrary/src/main/java/com/example/lib2/basemodule/BaseClassParcel.java").canonicalPath,
),
getProcessedSources(output)
)
} }
} }
@@ -305,54 +319,56 @@ class KaptIncrementalWithIsolatingApt : KaptIncrementalIT() {
* Make sure that changes to classpath can cause types to be reprocessed (i.e types in generated .class files that contain annotations * Make sure that changes to classpath can cause types to be reprocessed (i.e types in generated .class files that contain annotations
* claimed by annotation processors). * claimed by annotation processors).
*/ */
@Test @DisplayName("Changed types causing incremental compilation")
fun testClasspathChangesCauseTypesToBeReprocessed() { @GradleTest
val project = Project( fun testClasspathChangesCauseTypesToBeReprocessed(gradleVersion: GradleVersion) {
"kaptIncrementalCompilationProject", project(PROJECT_NAME, gradleVersion) {
GradleVersionRequired.None
).apply {
setupIncrementalAptProject( setupIncrementalAptProject(
Pair("ISOLATING", IncrementalBinaryIsolatingProcessor::class.java), Pair("ISOLATING", IncrementalBinaryIsolatingProcessor::class.java),
Pair("AGGREGATING", IncrementalAggregatingReferencingClasspathProcessor::class.java), Pair("AGGREGATING", IncrementalAggregatingReferencingClasspathProcessor::class.java),
) )
}
project.gradleSettingsScript().appendText("\ninclude ':', ':lib'\n") settingsGradle.append("\ninclude ':', ':lib'\n")
val classpathTypeSource = project.projectDir.resolve("lib").run { val classpathTypeSource = subProject("lib").run {
mkdirs() projectPath.createDirectories()
resolve("build.gradle").writeText(
buildGradle.writeText(
"""
plugins {
id 'java'
}
""".trimIndent()
)
val source = javaSourcesDir()
.resolve(
IncrementalAggregatingReferencingClasspathProcessor.CLASSPATH_TYPE
.replace(".", "/") + ".java"
)
source.parent.createDirectories()
source.writeText(
"""
package ${IncrementalAggregatingReferencingClasspathProcessor.CLASSPATH_TYPE.substringBeforeLast(".")};
public class ${IncrementalAggregatingReferencingClasspathProcessor.CLASSPATH_TYPE.substringAfterLast(".")} {}
""".trimIndent()
)
return@run source
}
buildGradle.append(
""" """
plugins {
id 'java' dependencies {
implementation project(':lib')
} }
""".trimIndent() """.trimIndent()
) )
val source =
resolve("src/main/java/" + IncrementalAggregatingReferencingClasspathProcessor.CLASSPATH_TYPE.replace(".", "/") + ".java")
source.parentFile.mkdirs()
source.writeText( // Remove all sources, and add only 1 source file
""" javaSourcesDir().deleteRecursively()
package ${IncrementalAggregatingReferencingClasspathProcessor.CLASSPATH_TYPE.substringBeforeLast(".")}; with(javaSourcesDir().resolve("example/A.kt")) {
public class ${IncrementalAggregatingReferencingClasspathProcessor.CLASSPATH_TYPE.substringAfterLast(".")} {} parent.createDirectories()
""".trimIndent()
)
return@run source
}
project.gradleBuildScript().appendText(
"""
dependencies {
implementation project(':lib')
}
""".trimIndent()
)
// Remove all sources, and add only 1 source file
project.projectDir.resolve("src").let {
it.deleteRecursively()
with(it.resolve("main/java/example/A.kt")) {
parentFile.mkdirs()
writeText( writeText(
""" """
package example package example
@@ -360,37 +376,31 @@ class KaptIncrementalWithIsolatingApt : KaptIncrementalIT() {
annotation class ExampleAnnotation annotation class ExampleAnnotation
@ExampleAnnotation @ExampleAnnotation
class A class A
""".trimIndent() """.trimIndent()
) )
} }
}
val allKotlinStubs = setOf( val allKotlinStubs = setOf(
project.projectDir.resolve("build/tmp/kapt3/stubs/main/example/ExampleAnnotation.java").canonicalPath, "build/tmp/kapt3/stubs/main/example/ExampleAnnotation.java",
project.projectDir.resolve("build/tmp/kapt3/stubs/main/example/A.java").canonicalPath, "build/tmp/kapt3/stubs/main/example/A.java",
project.projectDir.resolve("build/tmp/kapt3/stubs/main/error/NonExistentClass.java").canonicalPath "build/tmp/kapt3/stubs/main/error/NonExistentClass.java"
)
project.build("clean", "build") {
assertSuccessful()
assertEquals(allKotlinStubs, getProcessedSources(output))
assertTrue(
"Aggregating sources exists",
fileInWorkingDir("build/generated/source/kapt/main/com/example/AggGenerated.java").exists()
) )
}
// change type that the aggregated generated source reference build("clean", "assemble") {
classpathTypeSource.writeText(classpathTypeSource.readText().replace("}", "int i = 10;\n}")) assertEquals(
project.build("build") { allKotlinStubs.map { projectPath.resolve(it).toRealPath().toString() }.toSet(),
assertSuccessful() getProcessedSources(output)
assertEquals(emptySet<String>(), getProcessedSources(output)) )
assertEquals(setOf("example.AGenerated"), getProcessedTypes(output)) assertFileInProjectExists("build/generated/source/kapt/main/com/example/AggGenerated.java")
assertTrue( }
"Aggregating sources exists",
fileInWorkingDir("build/generated/source/kapt/main/com/example/AggGenerated.java").exists() // change type that the aggregated generated source reference
) classpathTypeSource.writeText(classpathTypeSource.readText().replace("}", "int i = 10;\n}"))
build("assemble") {
assertEquals(emptySet(), getProcessedSources(output))
assertEquals(setOf("example.AGenerated"), getProcessedTypes(output))
assertFileInProjectExists("build/generated/source/kapt/main/com/example/AggGenerated.java")
}
} }
} }
} }
@@ -399,7 +409,7 @@ private const val patternApt = "Processing java sources with annotation processo
fun getProcessedSources(output: String): Set<String> { fun getProcessedSources(output: String): Set<String> {
return output.lines().filter { it.contains(patternApt) }.flatMapTo(HashSet()) { logging -> return output.lines().filter { it.contains(patternApt) }.flatMapTo(HashSet()) { logging ->
val indexOf = logging.indexOf(patternApt) + patternApt.length val indexOf = logging.indexOf(patternApt) + patternApt.length
logging.drop(indexOf).split(",").map { it.trim() }.filter { !it.isEmpty() }.toSet() logging.drop(indexOf).split(",").map { it.trim() }.filter { it.isNotEmpty() }.toSet()
} }
} }
@@ -407,59 +417,10 @@ private const val patternClassesApt = "Processing types with annotation processo
fun getProcessedTypes(output: String): Set<String> { fun getProcessedTypes(output: String): Set<String> {
return output.lines().filter { it.contains(patternClassesApt) }.flatMapTo(HashSet()) { logging -> return output.lines().filter { it.contains(patternClassesApt) }.flatMapTo(HashSet()) { logging ->
val indexOf = logging.indexOf(patternClassesApt) + patternClassesApt.length val indexOf = logging.indexOf(patternClassesApt) + patternClassesApt.length
logging.drop(indexOf).split(",").map { it.trim() }.filter { !it.isEmpty() }.toSet() logging.drop(indexOf).split(",").map { it.trim() }.filter { it.isNotEmpty() }.toSet()
} }
} }
fun BaseGradleIT.Project.setupIncrementalAptProject(
procType: String,
buildFile: File = projectDir.resolve("build.gradle"),
procClass: Class<*> = IncrementalProcessor::class.java
) {
setupIncrementalAptProject(procType to procClass, buildFile = buildFile)
}
fun BaseGradleIT.Project.setupIncrementalAptProject(
vararg processors: Pair<String, Class<*>>,
buildFile: File = projectDir.resolve("build.gradle")
) {
setupWorkingDir()
val content = buildFile.readText()
val processorPath = generateProcessor(*processors)
val updatedContent = content.replace(
Regex("^\\s*kapt\\s\"org\\.jetbrains\\.kotlin.*$", RegexOption.MULTILINE),
" kapt files(\"${processorPath.invariantSeparatorsPath}\")"
)
buildFile.writeText(updatedContent)
}
fun BaseGradleIT.Project.generateProcessor(vararg processors: Pair<String, Class<*>>): File {
val processorPath = projectDir.resolve("incrementalProcessor.jar")
ZipOutputStream(processorPath.outputStream()).use {
for ((_, procClass) in processors) {
val path = procClass.name.replace(".", "/") + ".class"
procClass.classLoader.getResourceAsStream(path).use { inputStream ->
it.putNextEntry(ZipEntry(path))
it.write(inputStream.readBytes())
it.closeEntry()
}
}
it.putNextEntry(ZipEntry("META-INF/gradle/incremental.annotation.processors"))
it.write(processors.joinToString("\n") { (procType, procClass) ->
"${procClass.name},$procType"
}.toByteArray())
it.closeEntry()
it.putNextEntry(ZipEntry("META-INF/services/javax.annotation.processing.Processor"))
it.write(processors.joinToString("\n") { (_, procClass) ->
procClass.name
}.toByteArray())
it.closeEntry()
}
return processorPath
}
fun TestProject.setupIncrementalAptProject( fun TestProject.setupIncrementalAptProject(
procType: String, procType: String,
buildFile: Path = buildGradle, buildFile: Path = buildGradle,
@@ -493,7 +454,7 @@ fun TestProject.generateProcessor(
val path = procClass.name.replace(".", "/") + ".class" val path = procClass.name.replace(".", "/") + ".class"
procClass.classLoader.getResourceAsStream(path).use { inputStream -> procClass.classLoader.getResourceAsStream(path).use { inputStream ->
it.putNextEntry(ZipEntry(path)) it.putNextEntry(ZipEntry(path))
it.write(inputStream.readBytes()) it.write(inputStream!!.readBytes())
it.closeEntry() it.closeEntry()
} }
} }
@@ -1,7 +1,7 @@
plugins { plugins {
id 'com.android.library' id 'com.android.library'
id 'kotlin-android' id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt' id 'org.jetbrains.kotlin.kapt'
} }
android { android {
@@ -1,22 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
mavenLocal()
maven { url 'https://maven.google.com' }
mavenCentral()
maven { url = uri("https://jcenter.bintray.com/") }
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.android.tools.build:gradle:$android_tools_version"
}
}
allprojects { allprojects {
repositories { repositories {
mavenLocal() mavenLocal()
maven { url 'https://maven.google.com' } google()
mavenCentral() mavenCentral()
maven { url = uri("https://jcenter.bintray.com/") }
} }
} }
@@ -1,7 +1,7 @@
plugins { plugins {
id 'com.android.library' id 'com.android.library'
id 'kotlin-android' id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt' id 'org.jetbrains.kotlin.kapt'
} }
android { android {