Skip to content

Cleanup old sources between compilations #273

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ abstract class AbstractKotlinCompilation<A : CommonCompilerArguments> internal c
/* __HACK__: The Kotlin compiler expects at least one Kotlin source file or it will crash,
so we trick the compiler by just including an empty .kt-File. We need the compiler to run
even if there are no Kotlin files because some compiler plugins may also process Java files. */
listOf(SourceFile.new("emptyKotlinFile.kt", "").writeIfNeeded(sourcesDir).absolutePath)
listOf(SourceFile.new("emptyKotlinFile.kt", "").writeTo(sourcesDir).absolutePath)
} else {
emptyList()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,7 @@ class KotlinCompilation : AbstractKotlinCompilation<K2JVMCompilerArguments>() {
/** Runs the compilation task */
fun compile(): JvmCompilationResult {
// make sure all needed directories exist
sourcesDir.deleteRecursively()
sourcesDir.mkdirs()
classesDir.mkdirs()
kaptSourceDir.mkdirs()
Expand All @@ -567,7 +568,7 @@ class KotlinCompilation : AbstractKotlinCompilation<K2JVMCompilerArguments>() {
kaptKotlinGeneratedDir.mkdirs()

// write given sources to working directory
val sourceFiles = sources.map { it.writeIfNeeded(sourcesDir) }
val sourceFiles = sources.map { it.writeTo(sourcesDir) }

pluginClasspaths.forEach { filepath ->
if (!filepath.exists()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,12 @@ class KotlinJsCompilation : AbstractKotlinCompilation<K2JSCompilerArguments>() {
/** Runs the compilation task */
fun compile(): JsCompilationResult {
// make sure all needed directories exist
sourcesDir.deleteRecursively()
sourcesDir.mkdirs()
outputDir.mkdirs()

// write given sources to working directory
val sourceFiles = sources.map { it.writeIfNeeded(sourcesDir) }
val sourceFiles = sources.map { it.writeTo(sourcesDir) }

pluginClasspaths.forEach { filepath ->
if (!filepath.exists()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import org.intellij.lang.annotations.Language
* A source file for the [KotlinCompilation]
*/
abstract class SourceFile {
internal abstract fun writeIfNeeded(dir: File): File
internal abstract fun writeTo(dir: File): File
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I figured that renaming this makes sense since it will always write the contents now. Can revert though.


companion object {
/**
Expand All @@ -34,7 +34,7 @@ abstract class SourceFile {
* Create a new source file for the compilation when the compilation is run
*/
fun new(name: String, contents: String) = object : SourceFile() {
override fun writeIfNeeded(dir: File): File {
override fun writeTo(dir: File): File {
val file = dir.resolve(name)
file.parentFile.mkdirs()
file.createNewFile()
Expand All @@ -50,12 +50,13 @@ abstract class SourceFile {
/**
* Compile an existing source file
*/
@Deprecated("This will not work reliably with KSP, use `new` instead")
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since KSP2 accepts source directories KCT can't allow arbitrary files now.

fun fromPath(path: File) = object : SourceFile() {
init {
require(path.isFile)
}

override fun writeIfNeeded(dir: File): File = path
override fun writeTo(dir: File): File = path
}
}
}
6 changes: 6 additions & 0 deletions ksp/src/main/kotlin/com/tschuchort/compiletesting/Ksp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ var KotlinCompilation.kspLoggingLevels: Set<CompilerMessageSeverity>
tool.loggingLevels = value
}

@ExperimentalCompilerApi
val JvmCompilationResult.sourcesGeneratedBySymbolProcessor: Sequence<File>
get() = outputDirectory.parentFile.resolve("ksp/sources")
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not thrilled about this solution but other solutions require exposing the compilation which doesn't seem ideal.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the round matter here? KSP does output files by round in some circumstances

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once you have a result all rounds should be over, so this should include all files. But perhaps I misunderstand your question?

.walkTopDown()
.filter { it.isFile }

@OptIn(ExperimentalCompilerApi::class)
internal val KotlinCompilation.kspJavaSourceDir: File
get() = kspSourcesDir.resolve("java")
Expand Down
67 changes: 67 additions & 0 deletions ksp/src/test/kotlin/com/tschuchort/compiletesting/KspTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.mockito.Mockito.`when`
import java.nio.file.Files

@RunWith(Parameterized::class)
class KspTest(private val useKSP2: Boolean) {
Expand Down Expand Up @@ -775,4 +776,70 @@ class KspTest(private val useKSP2: Boolean) {
assertThat(result.messagesWithSeverity(DiagnosticSeverity.INFO)).contains("This is an info message")
assertThat(result.messagesWithSeverity(DiagnosticSeverity.WARNING)).contains("This is an warn message")
}

@Test
fun `removes old source files`() {
val tempDir = Files.createTempDirectory("Kotlin-Compilation").toFile()
val annotation =
kotlin(
"TestAnnotation.kt",
"""
package foo.bar
annotation class TestAnnotation
"""
)
val c1 =
kotlin(
"C1.kt",
"""
package foo.bar
@TestAnnotation
class C1
"""
)
val c2 =
kotlin(
"C2.kt",
"""
package foo.bar
@TestAnnotation
class C2
"""
)
val processor = simpleProcessor { resolver, codeGenerator ->
for (annotated in resolver.getSymbolsWithAnnotation("foo.bar.TestAnnotation")) {
annotated as KSClassDeclaration
codeGenerator.createNewFile(
dependencies = Dependencies(false, annotated.containingFile!!),
packageName = annotated.packageName.asString(),
fileName = "Generated${annotated.simpleName.asString()}",
).close()
}
}

val result1 = newCompilation()
.apply {
workingDir = tempDir
kspIncremental = true
sources = listOf(annotation, c1)
symbolProcessorProviders += processor
}
.compile()

assertThat(result1.exitCode).isEqualTo(ExitCode.OK)
assertThat(result1.sourcesGeneratedBySymbolProcessor.map { it.name }.toList()).contains("GeneratedC1.kt")

val result2 = newCompilation()
.apply {
workingDir = tempDir
kspIncremental = true
sources = listOf(annotation, c2)
symbolProcessorProviders += processor
}
.compile()

assertThat(result2.exitCode).isEqualTo(ExitCode.OK)
assertThat(result2.sourcesGeneratedBySymbolProcessor.map { it.name }.toList()).contains("GeneratedC2.kt")
assertThat(result2.sourcesGeneratedBySymbolProcessor.map { it.name }.toList()).doesNotContain("GeneratedC1.kt")
}
}