Skip to content

Commit 4fefab3

Browse files
authored
Cleanup old sources between compilations (#273)
This is required since KSP accepts a list of source directories while Kotlin accepts a list of source files. This means that if you compile multiple times, removing a source file between, KSP will still process the removed files.
1 parent 1380504 commit 4fefab3

File tree

6 files changed

+82
-6
lines changed

6 files changed

+82
-6
lines changed

core/src/main/kotlin/com/tschuchort/compiletesting/AbstractKotlinCompilation.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ abstract class AbstractKotlinCompilation<A : CommonCompilerArguments> internal c
247247
/* __HACK__: The Kotlin compiler expects at least one Kotlin source file or it will crash,
248248
so we trick the compiler by just including an empty .kt-File. We need the compiler to run
249249
even if there are no Kotlin files because some compiler plugins may also process Java files. */
250-
listOf(SourceFile.new("emptyKotlinFile.kt", "").writeIfNeeded(sourcesDir).absolutePath)
250+
listOf(SourceFile.new("emptyKotlinFile.kt", "").writeTo(sourcesDir).absolutePath)
251251
} else {
252252
emptyList()
253253
}

core/src/main/kotlin/com/tschuchort/compiletesting/KotlinCompilation.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,7 @@ class KotlinCompilation : AbstractKotlinCompilation<K2JVMCompilerArguments>() {
559559
/** Runs the compilation task */
560560
fun compile(): JvmCompilationResult {
561561
// make sure all needed directories exist
562+
sourcesDir.deleteRecursively()
562563
sourcesDir.mkdirs()
563564
classesDir.mkdirs()
564565
kaptSourceDir.mkdirs()
@@ -567,7 +568,7 @@ class KotlinCompilation : AbstractKotlinCompilation<K2JVMCompilerArguments>() {
567568
kaptKotlinGeneratedDir.mkdirs()
568569

569570
// write given sources to working directory
570-
val sourceFiles = sources.map { it.writeIfNeeded(sourcesDir) }
571+
val sourceFiles = sources.map { it.writeTo(sourcesDir) }
571572

572573
pluginClasspaths.forEach { filepath ->
573574
if (!filepath.exists()) {

core/src/main/kotlin/com/tschuchort/compiletesting/KotlinJsCompilation.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,12 @@ class KotlinJsCompilation : AbstractKotlinCompilation<K2JSCompilerArguments>() {
8686
/** Runs the compilation task */
8787
fun compile(): JsCompilationResult {
8888
// make sure all needed directories exist
89+
sourcesDir.deleteRecursively()
8990
sourcesDir.mkdirs()
9091
outputDir.mkdirs()
9192

9293
// write given sources to working directory
93-
val sourceFiles = sources.map { it.writeIfNeeded(sourcesDir) }
94+
val sourceFiles = sources.map { it.writeTo(sourcesDir) }
9495

9596
pluginClasspaths.forEach { filepath ->
9697
if (!filepath.exists()) {

core/src/main/kotlin/com/tschuchort/compiletesting/SourceFile.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import org.intellij.lang.annotations.Language
99
* A source file for the [KotlinCompilation]
1010
*/
1111
abstract class SourceFile {
12-
internal abstract fun writeIfNeeded(dir: File): File
12+
internal abstract fun writeTo(dir: File): File
1313

1414
companion object {
1515
/**
@@ -34,7 +34,7 @@ abstract class SourceFile {
3434
* Create a new source file for the compilation when the compilation is run
3535
*/
3636
fun new(name: String, contents: String) = object : SourceFile() {
37-
override fun writeIfNeeded(dir: File): File {
37+
override fun writeTo(dir: File): File {
3838
val file = dir.resolve(name)
3939
file.parentFile.mkdirs()
4040
file.createNewFile()
@@ -50,12 +50,13 @@ abstract class SourceFile {
5050
/**
5151
* Compile an existing source file
5252
*/
53+
@Deprecated("This will not work reliably with KSP, use `new` instead")
5354
fun fromPath(path: File) = object : SourceFile() {
5455
init {
5556
require(path.isFile)
5657
}
5758

58-
override fun writeIfNeeded(dir: File): File = path
59+
override fun writeTo(dir: File): File = path
5960
}
6061
}
6162
}

ksp/src/main/kotlin/com/tschuchort/compiletesting/Ksp.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,12 @@ var KotlinCompilation.kspLoggingLevels: Set<CompilerMessageSeverity>
117117
tool.loggingLevels = value
118118
}
119119

120+
@ExperimentalCompilerApi
121+
val JvmCompilationResult.sourcesGeneratedBySymbolProcessor: Sequence<File>
122+
get() = outputDirectory.parentFile.resolve("ksp/sources")
123+
.walkTopDown()
124+
.filter { it.isFile }
125+
120126
@OptIn(ExperimentalCompilerApi::class)
121127
internal val KotlinCompilation.kspJavaSourceDir: File
122128
get() = kspSourcesDir.resolve("java")

ksp/src/test/kotlin/com/tschuchort/compiletesting/KspTest.kt

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import org.junit.Test
2828
import org.junit.runner.RunWith
2929
import org.junit.runners.Parameterized
3030
import org.mockito.Mockito.`when`
31+
import java.nio.file.Files
3132

3233
@RunWith(Parameterized::class)
3334
class KspTest(private val useKSP2: Boolean) {
@@ -775,4 +776,70 @@ class KspTest(private val useKSP2: Boolean) {
775776
assertThat(result.messagesWithSeverity(DiagnosticSeverity.INFO)).contains("This is an info message")
776777
assertThat(result.messagesWithSeverity(DiagnosticSeverity.WARNING)).contains("This is an warn message")
777778
}
779+
780+
@Test
781+
fun `removes old source files`() {
782+
val tempDir = Files.createTempDirectory("Kotlin-Compilation").toFile()
783+
val annotation =
784+
kotlin(
785+
"TestAnnotation.kt",
786+
"""
787+
package foo.bar
788+
annotation class TestAnnotation
789+
"""
790+
)
791+
val c1 =
792+
kotlin(
793+
"C1.kt",
794+
"""
795+
package foo.bar
796+
@TestAnnotation
797+
class C1
798+
"""
799+
)
800+
val c2 =
801+
kotlin(
802+
"C2.kt",
803+
"""
804+
package foo.bar
805+
@TestAnnotation
806+
class C2
807+
"""
808+
)
809+
val processor = simpleProcessor { resolver, codeGenerator ->
810+
for (annotated in resolver.getSymbolsWithAnnotation("foo.bar.TestAnnotation")) {
811+
annotated as KSClassDeclaration
812+
codeGenerator.createNewFile(
813+
dependencies = Dependencies(false, annotated.containingFile!!),
814+
packageName = annotated.packageName.asString(),
815+
fileName = "Generated${annotated.simpleName.asString()}",
816+
).close()
817+
}
818+
}
819+
820+
val result1 = newCompilation()
821+
.apply {
822+
workingDir = tempDir
823+
kspIncremental = true
824+
sources = listOf(annotation, c1)
825+
symbolProcessorProviders += processor
826+
}
827+
.compile()
828+
829+
assertThat(result1.exitCode).isEqualTo(ExitCode.OK)
830+
assertThat(result1.sourcesGeneratedBySymbolProcessor.map { it.name }.toList()).contains("GeneratedC1.kt")
831+
832+
val result2 = newCompilation()
833+
.apply {
834+
workingDir = tempDir
835+
kspIncremental = true
836+
sources = listOf(annotation, c2)
837+
symbolProcessorProviders += processor
838+
}
839+
.compile()
840+
841+
assertThat(result2.exitCode).isEqualTo(ExitCode.OK)
842+
assertThat(result2.sourcesGeneratedBySymbolProcessor.map { it.name }.toList()).contains("GeneratedC2.kt")
843+
assertThat(result2.sourcesGeneratedBySymbolProcessor.map { it.name }.toList()).doesNotContain("GeneratedC1.kt")
844+
}
778845
}

0 commit comments

Comments
 (0)