Skip to content

Commit d96c00d

Browse files
ting-yuanKSP Auto Pick
authored and
KSP Auto Pick
committed
Incremental: include AnyChangesWildcard to initial dirty set
in case Gradle or processors don't calculate / provide dirty set / dependencies correctly. (cherry picked from commit 553adb6)
1 parent 80d0a92 commit d96c00d

File tree

5 files changed

+136
-13
lines changed

5 files changed

+136
-13
lines changed

compiler-plugin/src/main/kotlin/com/google/devtools/ksp/common/IncrementalContextBase.kt

+1-6
Original file line numberDiff line numberDiff line change
@@ -260,12 +260,7 @@ abstract class IncrementalContextBase(
260260
initialSet.addAll(dirtyFilesBySealed)
261261
initialSet.addAll(noSourceFiles)
262262

263-
// modified can be seen as removed + new. Therefore the following check doesn't work:
264-
// if (modified.any { it !in sourceToOutputsMap.keys }) ...
265-
// Removed files affect aggregating outputs if they were generated unconditionally.
266-
if (modified.isNotEmpty() || changedClasses.isNotEmpty() || removed.isNotEmpty()) {
267-
initialSet.add(anyChangesWildcard)
268-
}
263+
initialSet.add(anyChangesWildcard)
269264

270265
val dirtyFiles = DirtinessPropagator(
271266
symbolLookupCache,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.google.devtools.ksp.test
2+
3+
import org.gradle.testkit.runner.GradleRunner
4+
import org.gradle.testkit.runner.TaskOutcome
5+
import org.junit.Assert
6+
import org.junit.Rule
7+
import org.junit.Test
8+
import org.junit.runner.RunWith
9+
import org.junit.runners.Parameterized
10+
import java.io.File
11+
12+
@RunWith(Parameterized::class)
13+
class IncrementalEmptyCPIT(val useKSP2: Boolean) {
14+
@Rule
15+
@JvmField
16+
val project: TemporaryTestProject =
17+
TemporaryTestProject("incremental-classpath2", "incremental-classpath", useKSP2 = useKSP2)
18+
19+
private val emptyMessage = setOf("w: [ksp] processing done")
20+
21+
private val prop2Dirty = listOf(
22+
"l1/src/main/kotlin/p1/TopProp1.kt" to setOf(
23+
"w: [ksp] p1/K1.kt",
24+
"w: [ksp] p1/K2.kt",
25+
"w: [ksp] p1/K3.kt",
26+
),
27+
)
28+
29+
@Test
30+
fun testCPChangesForProperties() {
31+
val gradleRunner = GradleRunner.create().withProjectDir(project.root)
32+
33+
gradleRunner.withArguments("clean", "assemble").build().let { result ->
34+
Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:assemble")?.outcome)
35+
}
36+
37+
// Dummy changes
38+
prop2Dirty.forEach { (src, _) ->
39+
File(project.root, src).appendText("\n\n")
40+
gradleRunner.withArguments("assemble").build().let { result ->
41+
// Trivial changes should not result in re-processing.
42+
Assert.assertEquals(TaskOutcome.UP_TO_DATE, result.task(":workload:kspKotlin")?.outcome)
43+
}
44+
}
45+
46+
// Value changes. It is not really used but should still trigger reprocessing of aggregating outputs.
47+
prop2Dirty.forEach { (src, expectedDirties) ->
48+
File(project.root, src).writeText("package p1\n\nval MyTopProp1: Int = 1")
49+
gradleRunner.withArguments("assemble").build().let { result ->
50+
// Value changes will result in re-processing of aggregating outputs.
51+
Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:kspKotlin")?.outcome)
52+
val dirties = result.output.lines().filter { it.startsWith("w: [ksp]") }.toSet()
53+
Assert.assertEquals(expectedDirties, dirties)
54+
}
55+
}
56+
}
57+
58+
companion object {
59+
@JvmStatic
60+
@Parameterized.Parameters(name = "KSP2={0}")
61+
fun params() = listOf(arrayOf(true), arrayOf(false))
62+
}
63+
}

integration-tests/src/test/resources/incremental-classpath/validator/src/main/kotlin/Validator.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ class Validator : SymbolProcessor {
6969
codeGenerator.createNewFile(Dependencies(false), "p1", "MyTopFunc1", "log")
7070
codeGenerator.associateWithFunctions(listOf(myTopFunc1), "p1", "MyTopFunc1", "log")
7171

72-
// create an output from MyTopProp1, declared in TopFunc1.kt
72+
// create an output from MyTopProp1, declared in TopProp1.kt
7373
val myTopProp1 = resolver.getPropertyDeclarationByName("p1.MyTopProp1", true)!!
7474
codeGenerator.createNewFile(Dependencies(false), "p1", "MyTopProp1", "log")
7575
codeGenerator.associateWithProperties(listOf(myTopProp1), "p1", "MyTopProp1", "log")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import com.google.devtools.ksp.getClassDeclarationByName
2+
import com.google.devtools.ksp.getFunctionDeclarationsByName
3+
import com.google.devtools.ksp.getPropertyDeclarationByName
4+
import com.google.devtools.ksp.processing.*
5+
import com.google.devtools.ksp.symbol.*
6+
import com.google.devtools.ksp.validate
7+
import com.google.devtools.ksp.visitor.KSDefaultVisitor
8+
import java.io.OutputStreamWriter
9+
10+
class Validator : SymbolProcessor {
11+
lateinit var codeGenerator: CodeGenerator
12+
lateinit var logger: KSPLogger
13+
var processed = false
14+
15+
fun init(
16+
options: Map<String, String>,
17+
kotlinVersion: KotlinVersion,
18+
codeGenerator: CodeGenerator,
19+
logger: KSPLogger,
20+
) {
21+
this.codeGenerator = codeGenerator
22+
this.logger = logger
23+
}
24+
25+
override fun process(resolver: Resolver): List<KSAnnotated> {
26+
if (processed) {
27+
return emptyList()
28+
}
29+
30+
val validator = object : KSDefaultVisitor<OutputStreamWriter, Unit>() {
31+
override fun defaultHandler(node: KSNode, data: OutputStreamWriter) = Unit
32+
33+
override fun visitDeclaration(declaration: KSDeclaration, data: OutputStreamWriter) {
34+
data.write(declaration.qualifiedName?.asString() ?: declaration.simpleName.asString())
35+
declaration.validate()
36+
}
37+
}
38+
39+
// for each file, create an output from it and everything reachable from it.
40+
val files = resolver.getAllFiles()
41+
files.forEach { file ->
42+
logger.warn("${file.packageName.asString()}/${file.fileName}")
43+
val output = OutputStreamWriter(
44+
codeGenerator.createNewFile(
45+
Dependencies(true, file),
46+
file.packageName.asString(),
47+
file.fileName,
48+
"log"
49+
)
50+
)
51+
file.declarations.forEach {
52+
it.accept(validator, output)
53+
}
54+
output.close()
55+
}
56+
57+
processed = true
58+
return emptyList()
59+
}
60+
}
61+
62+
class TestProcessorProvider : SymbolProcessorProvider {
63+
override fun create(
64+
env: SymbolProcessorEnvironment,
65+
): SymbolProcessor {
66+
return Validator().apply {
67+
init(env.options, env.kotlinVersion, env.codeGenerator, env.logger)
68+
}
69+
}
70+
}

kotlin-analysis-api/src/main/kotlin/com/google/devtools/ksp/common/IncrementalContextBase.kt

+1-6
Original file line numberDiff line numberDiff line change
@@ -261,12 +261,7 @@ abstract class IncrementalContextBase(
261261
initialSet.addAll(dirtyFilesBySealed)
262262
initialSet.addAll(noSourceFiles)
263263

264-
// modified can be seen as removed + new. Therefore the following check doesn't work:
265-
// if (modified.any { it !in sourceToOutputsMap.keys }) ...
266-
// Removed files affect aggregating outputs if they were generated unconditionally.
267-
if (modified.isNotEmpty() || changedClasses.isNotEmpty() || removed.isNotEmpty()) {
268-
initialSet.add(anyChangesWildcard)
269-
}
264+
initialSet.add(anyChangesWildcard)
270265

271266
val dirtyFiles = DirtinessPropagator(
272267
symbolLookupCache,

0 commit comments

Comments
 (0)