From 3fc773f10e23908f003e2c0c70ea18de69212263 Mon Sep 17 00:00:00 2001 From: Nicklas Ansman Date: Sat, 24 Aug 2024 15:06:52 -0400 Subject: [PATCH] Delete the ksp caches when the task is restored from cache Without this change, the task's caches will be in an incorrect state and the next incremental run can lead to compile errors. Now the caches have been marked as local state and Gradle will remove them when the task is restored from the build cache. This fixes #2042 (cherry picked from commit 3f31c104ef2a1f5be579eb941eff3ff7d0a8ae06) --- .../google/devtools/ksp/gradle/KspAATask.kt | 13 ++--- .../devtools/ksp/gradle/KspSubplugin.kt | 17 +++++-- .../ksp/test/BuildCacheIncrementalIT.kt | 47 +++++++++++++++++ .../buildcache-incremental/build.gradle.kts | 8 +++ .../buildcache-incremental/gradle.properties | 1 + .../settings.gradle.kts | 27 ++++++++++ .../test-processor/build.gradle.kts | 24 +++++++++ .../main/kotlin/TestBuildCacheProcessor.kt | 50 +++++++++++++++++++ ...ols.ksp.processing.SymbolProcessorProvider | 1 + .../workload/build.gradle.kts | 19 +++++++ .../workload/src/main/kotlin/p1/K1.kt | 4 ++ .../src/main/kotlin/p1/MyAnnotation.kt | 3 ++ 12 files changed, 203 insertions(+), 11 deletions(-) create mode 100644 integration-tests/src/test/kotlin/com/google/devtools/ksp/test/BuildCacheIncrementalIT.kt create mode 100644 integration-tests/src/test/resources/buildcache-incremental/build.gradle.kts create mode 100644 integration-tests/src/test/resources/buildcache-incremental/gradle.properties create mode 100644 integration-tests/src/test/resources/buildcache-incremental/settings.gradle.kts create mode 100644 integration-tests/src/test/resources/buildcache-incremental/test-processor/build.gradle.kts create mode 100644 integration-tests/src/test/resources/buildcache-incremental/test-processor/src/main/kotlin/TestBuildCacheProcessor.kt create mode 100644 integration-tests/src/test/resources/buildcache-incremental/test-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider create mode 100644 integration-tests/src/test/resources/buildcache-incremental/workload/build.gradle.kts create mode 100644 integration-tests/src/test/resources/buildcache-incremental/workload/src/main/kotlin/p1/K1.kt create mode 100644 integration-tests/src/test/resources/buildcache-incremental/workload/src/main/kotlin/p1/MyAnnotation.kt diff --git a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspAATask.kt b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspAATask.kt index b9cc427e74..a06cfb7b4b 100644 --- a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspAATask.kt +++ b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspAATask.kt @@ -22,6 +22,7 @@ import com.google.devtools.ksp.processing.* import org.gradle.api.DefaultTask import org.gradle.api.artifacts.Configuration import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.DirectoryProperty import org.gradle.api.logging.LogLevel import org.gradle.api.provider.MapProperty import org.gradle.api.provider.Property @@ -92,7 +93,7 @@ abstract class KspAATask @Inject constructor( kspConfig.commonSourceRoots, kspConfig.libraries ), - kspConfig.cachesDir.get(), + kspConfig.cachesDir.asFile.get(), kspConfig.classpathStructure, kspConfig.libraries, kspConfig.processorClasspath, @@ -102,11 +103,11 @@ abstract class KspAATask @Inject constructor( !inputChanges.isIncremental || inputChanges.getFileChanges(kspConfig.libraries).iterator().hasNext() ) - kspConfig.cachesDir.get().deleteRecursively() + kspConfig.cachesDir.get().asFile.deleteRecursively() emptyList() } } else { - kspConfig.cachesDir.get().deleteRecursively() + kspConfig.cachesDir.get().asFile.deleteRecursively() emptyList() } @@ -319,8 +320,8 @@ abstract class KspGradleConfig @Inject constructor() { @get:Internal abstract val outputBaseDir: Property - @get:Internal - abstract val cachesDir: Property + @get:LocalState + abstract val cachesDir: DirectoryProperty @get:OutputDirectory abstract val kotlinOutputDir: Property @@ -444,7 +445,7 @@ abstract class KspAAWorkerAction : WorkAction { libraries = gradleCfg.libraries.files.toList() projectBaseDir = gradleCfg.projectBaseDir.get() outputBaseDir = gradleCfg.outputBaseDir.get() - cachesDir = gradleCfg.cachesDir.get() + cachesDir = gradleCfg.cachesDir.get().asFile kotlinOutputDir = gradleCfg.kotlinOutputDir.get() classOutputDir = gradleCfg.classOutputDir.get() resourceOutputDir = gradleCfg.resourceOutputDir.get() diff --git a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspSubplugin.kt b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspSubplugin.kt index bdd4caffcf..bfe8b30be2 100644 --- a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspSubplugin.kt +++ b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspSubplugin.kt @@ -24,6 +24,7 @@ import org.gradle.api.UnknownTaskException import org.gradle.api.artifacts.Configuration import org.gradle.api.attributes.Attribute import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.Directory import org.gradle.api.file.FileCollection import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Provider @@ -97,7 +98,7 @@ class KspGradleSubplugin @Inject internal constructor(private val registry: Tool @JvmStatic fun getKspCachesDir(project: Project, sourceSetName: String, target: String) = - File(project.project.buildDir, "kspCaches/$target/$sourceSetName") + project.layout.buildDirectory.dir("kspCaches/$target/$sourceSetName") @JvmStatic private fun getSubpluginOptions( @@ -109,6 +110,7 @@ class KspGradleSubplugin @Inject internal constructor(private val registry: Tool allWarningsAsErrors: Provider, commandLineArgumentProviders: ListProperty, commonSources: Provider>, + cachesDir: Provider ): Provider> { val options = project.objects.listProperty(SubpluginOption::class.java) options.add( @@ -127,7 +129,9 @@ class KspGradleSubplugin @Inject internal constructor(private val registry: Tool ) ) options.add( - InternalSubpluginOption("cachesDir", getKspCachesDir(project, sourceSetName, target).path) + cachesDir.map { + InternalSubpluginOption("cachesDir", it.asFile.path) + } ) options.add( InternalSubpluginOption("kspOutputDir", getKspOutputDir(project, sourceSetName, target).path) @@ -292,10 +296,12 @@ class KspGradleSubplugin @Inject internal constructor(private val registry: Tool val processorClasspath = project.configurations.maybeCreate("${kspTaskName}ProcessorClasspath") .extendsFrom(*nonEmptyKspConfigurations.toTypedArray()).markResolvable() + val kspCachesDir = getKspCachesDir(project, sourceSetName, target) fun configureAsKspTask(kspTask: KspTask, isIncremental: Boolean) { // depends on the processor; if the processor changes, it needs to be reprocessed. kspTask.dependsOn(processorClasspath.buildDependencies) kspTask.commandLineArgumentProviders.addAll(kspExtension.commandLineArgumentProviders) + kspTask.localState.register(kspCachesDir) kspTask.options.addAll( getSubpluginOptions( @@ -307,6 +313,7 @@ class KspGradleSubplugin @Inject internal constructor(private val registry: Tool allWarningsAsErrors = project.provider { kspExtension.allWarningsAsErrors }, commandLineArgumentProviders = kspTask.commandLineArgumentProviders, commonSources = project.provider { emptyList() }, + cachesDir = kspCachesDir ) ) kspTask.inputs.property("apOptions", kspExtension.apOptions) @@ -468,7 +475,7 @@ class KspGradleSubplugin @Inject internal constructor(private val registry: Tool createIncrementalChangesTransformer( isIncremental, isIntermoduleIncremental, - getKspCachesDir(project, sourceSetName, target), + kspCachesDir.get().asFile, project.provider { classStructureFiles }, project.provider { kspTask.libraries }, project.provider { processorClasspath } @@ -495,7 +502,7 @@ class KspGradleSubplugin @Inject internal constructor(private val registry: Tool createIncrementalChangesTransformer( isIncremental, false, - getKspCachesDir(project, sourceSetName, target), + kspCachesDir.get().asFile, project.provider { project.files() }, project.provider { project.files() }, project.provider { processorClasspath } @@ -518,7 +525,7 @@ class KspGradleSubplugin @Inject internal constructor(private val registry: Tool createIncrementalChangesTransformer( isIncremental, false, - getKspCachesDir(project, sourceSetName, target), + kspCachesDir.get().asFile, project.provider { project.files() }, project.provider { project.files() }, project.provider { processorClasspath } diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/BuildCacheIncrementalIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/BuildCacheIncrementalIT.kt new file mode 100644 index 0000000000..cc56f67d2a --- /dev/null +++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/BuildCacheIncrementalIT.kt @@ -0,0 +1,47 @@ +package com.google.devtools.ksp.test + +import org.gradle.testkit.runner.GradleRunner +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import java.io.File + +@RunWith(Parameterized::class) +class BuildCacheIncrementalIT(useKSP2: Boolean) { + @Rule + @JvmField + val project: TemporaryTestProject = TemporaryTestProject("buildcache-incremental", useKSP2 = useKSP2) + + // See https://github.com/google/ksp/issues/2042 for details + @Test + fun testIncrementalBuildCache() { + val buildCacheDir = File(project.root, "build-cache").absolutePath.replace(File.separatorChar, '/') + File(project.root, "gradle.properties").appendText("\nbuildCacheDir=$buildCacheDir") + + val gradleRunner = GradleRunner.create().withProjectDir(project.root) + val k1 = "workload/src/main/kotlin/p1/K1.kt" + val k2 = "workload/src/main/kotlin/p1/K2.kt" + + gradleRunner.withArguments("assemble").build() + + File(project.root, k2).writeText( + "package p1\n\n@MyAnnotation\nclass K2\n" + ) + gradleRunner.withArguments("assemble").build() + + File(project.root, k2).delete() + gradleRunner.withArguments("assemble").build() + + File(project.root, k1).writeText( + "package p1\n\nclass K1(val foo: String)\n" + ) + gradleRunner.withArguments("assemble").build() + } + + companion object { + @JvmStatic + @Parameterized.Parameters(name = "KSP2={0}") + fun params() = listOf(arrayOf(true), arrayOf(false)) + } +} diff --git a/integration-tests/src/test/resources/buildcache-incremental/build.gradle.kts b/integration-tests/src/test/resources/buildcache-incremental/build.gradle.kts new file mode 100644 index 0000000000..c5737a2e0d --- /dev/null +++ b/integration-tests/src/test/resources/buildcache-incremental/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + kotlin("jvm") +} + +repositories { + mavenCentral() + maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/bootstrap/") +} diff --git a/integration-tests/src/test/resources/buildcache-incremental/gradle.properties b/integration-tests/src/test/resources/buildcache-incremental/gradle.properties new file mode 100644 index 0000000000..160890028a --- /dev/null +++ b/integration-tests/src/test/resources/buildcache-incremental/gradle.properties @@ -0,0 +1 @@ +org.gradle.caching=true diff --git a/integration-tests/src/test/resources/buildcache-incremental/settings.gradle.kts b/integration-tests/src/test/resources/buildcache-incremental/settings.gradle.kts new file mode 100644 index 0000000000..24430bc3af --- /dev/null +++ b/integration-tests/src/test/resources/buildcache-incremental/settings.gradle.kts @@ -0,0 +1,27 @@ +pluginManagement { + val kotlinVersion: String by settings + val kspVersion: String by settings + val testRepo: String by settings + plugins { + id("com.google.devtools.ksp") version kspVersion + kotlin("jvm") version kotlinVersion + } + repositories { + maven(testRepo) + gradlePluginPortal() + maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/bootstrap/") + } +} + +buildCache { + val buildCacheDir: String by settings + local { + directory = File(buildCacheDir) + removeUnusedEntriesAfterDays = 30 + } +} + +rootProject.name = "playground" + +include(":workload") +include(":test-processor") diff --git a/integration-tests/src/test/resources/buildcache-incremental/test-processor/build.gradle.kts b/integration-tests/src/test/resources/buildcache-incremental/test-processor/build.gradle.kts new file mode 100644 index 0000000000..ab249cf06f --- /dev/null +++ b/integration-tests/src/test/resources/buildcache-incremental/test-processor/build.gradle.kts @@ -0,0 +1,24 @@ +val kspVersion: String by project +val testRepo: String by project + +plugins { + kotlin("jvm") +} + +group = "com.example" +version = "1.0-SNAPSHOT" + +repositories { + maven(testRepo) + mavenCentral() + maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/bootstrap/") +} + +dependencies { + implementation(kotlin("stdlib")) + implementation("com.google.devtools.ksp:symbol-processing-api:$kspVersion") +} + +sourceSets.main { + java.srcDirs("src/main/kotlin") +} diff --git a/integration-tests/src/test/resources/buildcache-incremental/test-processor/src/main/kotlin/TestBuildCacheProcessor.kt b/integration-tests/src/test/resources/buildcache-incremental/test-processor/src/main/kotlin/TestBuildCacheProcessor.kt new file mode 100644 index 0000000000..f5f7264369 --- /dev/null +++ b/integration-tests/src/test/resources/buildcache-incremental/test-processor/src/main/kotlin/TestBuildCacheProcessor.kt @@ -0,0 +1,50 @@ +import com.google.devtools.ksp.processing.* +import com.google.devtools.ksp.symbol.* +import com.google.devtools.ksp.validate +import java.io.OutputStreamWriter + +class TestBuildCacheProcessor : SymbolProcessor { + lateinit var codeGenerator: CodeGenerator + lateinit var logger: KSPLogger + + fun init( + options: Map, + kotlinVersion: KotlinVersion, + codeGenerator: CodeGenerator, + logger: KSPLogger, + ) { + this.codeGenerator = codeGenerator + this.logger = logger + } + + override fun process(resolver: Resolver): List { + resolver.getSymbolsWithAnnotation("p1.MyAnnotation").forEach { decl -> + decl as KSClassDeclaration + + val pkg = decl.packageName.asString() + val name = decl.simpleName.asString() + val generated = name + "Generated" + val output = codeGenerator.createNewFile( + Dependencies(false, decl.containingFile!!), + pkg, generated + ) + OutputStreamWriter(output).use { + it.write("package $pkg\n\nclass $generated(val className: String = $name::class.java.simpleName)\n") + } + } + resolver.getNewFiles().forEach { + it.validate() + } + return emptyList() + } +} + +class TestBuildCacheProcessorProvider : SymbolProcessorProvider { + override fun create( + env: SymbolProcessorEnvironment, + ): SymbolProcessor { + return TestBuildCacheProcessor().apply { + init(env.options, env.kotlinVersion, env.codeGenerator, env.logger) + } + } +} diff --git a/integration-tests/src/test/resources/buildcache-incremental/test-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/integration-tests/src/test/resources/buildcache-incremental/test-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider new file mode 100644 index 0000000000..0dfa00550f --- /dev/null +++ b/integration-tests/src/test/resources/buildcache-incremental/test-processor/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider @@ -0,0 +1 @@ +TestBuildCacheProcessorProvider diff --git a/integration-tests/src/test/resources/buildcache-incremental/workload/build.gradle.kts b/integration-tests/src/test/resources/buildcache-incremental/workload/build.gradle.kts new file mode 100644 index 0000000000..f6b92cd5d1 --- /dev/null +++ b/integration-tests/src/test/resources/buildcache-incremental/workload/build.gradle.kts @@ -0,0 +1,19 @@ +val testRepo: String by project + +plugins { + id("com.google.devtools.ksp") + kotlin("jvm") +} + +version = "1.0-SNAPSHOT" + +repositories { + maven(testRepo) + mavenCentral() + maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/bootstrap/") +} + +dependencies { + implementation(kotlin("stdlib")) + ksp(project(":test-processor")) +} diff --git a/integration-tests/src/test/resources/buildcache-incremental/workload/src/main/kotlin/p1/K1.kt b/integration-tests/src/test/resources/buildcache-incremental/workload/src/main/kotlin/p1/K1.kt new file mode 100644 index 0000000000..5b31cfdbf2 --- /dev/null +++ b/integration-tests/src/test/resources/buildcache-incremental/workload/src/main/kotlin/p1/K1.kt @@ -0,0 +1,4 @@ +package p1 + +@MyAnnotation +class K1 diff --git a/integration-tests/src/test/resources/buildcache-incremental/workload/src/main/kotlin/p1/MyAnnotation.kt b/integration-tests/src/test/resources/buildcache-incremental/workload/src/main/kotlin/p1/MyAnnotation.kt new file mode 100644 index 0000000000..004f7ff75e --- /dev/null +++ b/integration-tests/src/test/resources/buildcache-incremental/workload/src/main/kotlin/p1/MyAnnotation.kt @@ -0,0 +1,3 @@ +package p1 + +annotation class MyAnnotation