Skip to content

Commit

Permalink
Incremental: Support top level callables in libs
Browse files Browse the repository at this point in the history
by treating outputs of them sensitive to any changes. The granular way
requires knowledge from the each backend and will be implemented in the
future.
  • Loading branch information
ting-yuan committed Feb 1, 2024
1 parent cd59900 commit 3adb158
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 0 deletions.
Expand Up @@ -138,6 +138,42 @@ interface CodeGenerator {
)

val generatedFile: Collection<File>

/**
* Associate [functions] to an output file.
*
* @param functions are [KSFunctionDeclaration]s from which this output is built. Only those that are obtained
* directly from [Resolver] are required.
* @param packageName corresponds to the relative path of the generated file; using either '.'or '/' as separator.
* @param fileName file name
* @param extensionName If "kt" or "java", this file will participate in subsequent compilation.
* Otherwise its creation is only considered in incremental processing.
* @see [CodeGenerator] for more details.
*/
fun associateWithFunctions(
functions: List<KSFunctionDeclaration>,
packageName: String,
fileName: String,
extensionName: String = "kt"
)

/**
* Associate [properties] to an output file.
*
* @param properties are [KSPropertyDeclaration]s from which this output is built. Only those that are obtained
* directly from [Resolver] are required.
* @param packageName corresponds to the relative path of the generated file; using either '.'or '/' as separator.
* @param fileName file name
* @param extensionName If "kt" or "java", this file will participate in subsequent compilation.
* Otherwise its creation is only considered in incremental processing.
* @see [CodeGenerator] for more details.
*/
fun associateWithProperties(
properties: List<KSPropertyDeclaration>,
packageName: String,
fileName: String,
extensionName: String = "kt"
)
}

/**
Expand Down
Expand Up @@ -22,6 +22,8 @@ import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.processing.Dependencies
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSFile
import com.google.devtools.ksp.symbol.KSFunctionDeclaration
import com.google.devtools.ksp.symbol.KSPropertyDeclaration
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
Expand Down Expand Up @@ -98,6 +100,42 @@ class CodeGeneratorImpl(
associate(files, path, extensionToDirectory(extensionName))
}

// FIXME: associate with the containing FileKt.
// The containing FileKt is unfortunately a backend thing and is not available in Kotlin metadata.
// Therefore, the only way to get it seems to be traversing bytecode and find the functions of interest.
//
// This implementation associates anyChangesWildcard, which is an overkill.
override fun associateWithFunctions(
functions: List<KSFunctionDeclaration>,
packageName: String,
fileName: String,
extensionName: String
) {
val path = pathOf(packageName, fileName, extensionName)
val files = functions.map {
it.containingFile ?: anyChangesWildcard
}
associate(files, path, extensionToDirectory(extensionName))
}

// FIXME: associate with the containing FileKt.
// The containing FileKt is unfortunately a backend thing and is not available in Kotlin metadata.
// Therefore, the only way to get it seems to be traversing bytecode and find the functions of interest.
//
// This implementation associates anyChangesWildcard, which is an overkill.
override fun associateWithProperties(
properties: List<KSPropertyDeclaration>,
packageName: String,
fileName: String,
extensionName: String
) {
val path = pathOf(packageName, fileName, extensionName)
val files = properties.map {
it.containingFile ?: anyChangesWildcard
}
associate(files, path, extensionToDirectory(extensionName))
}

private fun extensionToDirectory(extensionName: String): File {
return when (extensionName) {
"class" -> classDir
Expand Down
Expand Up @@ -77,6 +77,100 @@ class IncrementalCPIT(val useKSP2: Boolean) {
}
}

val func2Dirty = listOf(
"l1/src/main/kotlin/p1/TopFunc1.kt" to setOf(
"w: [ksp] p1/K3.kt",
"w: [ksp] processing done",
),
)

@Test
fun testCPChangesForFunctions() {
val gradleRunner = GradleRunner.create().withProjectDir(project.root)

gradleRunner.withArguments("clean", "assemble").build().let { result ->
Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:assemble")?.outcome)
}

// Dummy changes
func2Dirty.forEach { (src, expectedDirties) ->
File(project.root, src).appendText("\n\n")
gradleRunner.withArguments("assemble").build().let { result ->
// Trivial changes should not result in re-processing.
Assert.assertEquals(TaskOutcome.UP_TO_DATE, result.task(":workload:kspKotlin")?.outcome)
}
}

// Value changes
func2Dirty.forEach { (src, expectedDirties) ->
File(project.root, src).writeText("package p1\n\nfun MyTopFunc1(): Int = 1")
gradleRunner.withArguments("assemble").withDebug(true).build().let { result ->
// Value changes should not result in re-processing.
Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:kspKotlin")?.outcome)
val dirties = result.output.lines().filter { it.startsWith("w: [ksp]") }.toSet()
// Non-signature changes should not affect anything.
Assert.assertEquals(emptyMessage, dirties)
}
}

// Signature changes
func2Dirty.forEach { (src, expectedDirties) ->
File(project.root, src).writeText("package p1\n\nfun MyTopFunc1(): Double = 1.0")
gradleRunner.withArguments("assemble").build().let { result ->
Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:kspKotlin")?.outcome)
val dirties = result.output.lines().filter { it.startsWith("w: [ksp]") }.toSet()
Assert.assertEquals(expectedDirties, dirties)
}
}
}

val prop2Dirty = listOf(
"l1/src/main/kotlin/p1/TopProp1.kt" to setOf(
"w: [ksp] p1/K3.kt",
"w: [ksp] processing done",
),
)

@Test
fun testCPChangesForProperties() {
val gradleRunner = GradleRunner.create().withProjectDir(project.root)

gradleRunner.withArguments("clean", "assemble").build().let { result ->
Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:assemble")?.outcome)
}

// Dummy changes
prop2Dirty.forEach { (src, expectedDirties) ->
File(project.root, src).appendText("\n\n")
gradleRunner.withArguments("assemble").build().let { result ->
// Trivial changes should not result in re-processing.
Assert.assertEquals(TaskOutcome.UP_TO_DATE, result.task(":workload:kspKotlin")?.outcome)
}
}

// Value changes
prop2Dirty.forEach { (src, expectedDirties) ->
File(project.root, src).writeText("package p1\n\nval MyTopProp1: Int = 1")
gradleRunner.withArguments("assemble").withDebug(true).build().let { result ->
// Value changes should not result in re-processing.
Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:kspKotlin")?.outcome)
val dirties = result.output.lines().filter { it.startsWith("w: [ksp]") }.toSet()
// Non-signature changes should not affect anything.
Assert.assertEquals(emptyMessage, dirties)
}
}

// Signature changes
prop2Dirty.forEach { (src, expectedDirties) ->
File(project.root, src).writeText("package p1\n\nval MyTopProp1: Double = 1.0")
gradleRunner.withArguments("assemble").build().let { result ->
Assert.assertEquals(TaskOutcome.SUCCESS, result.task(":workload:kspKotlin")?.outcome)
val dirties = result.output.lines().filter { it.startsWith("w: [ksp]") }.toSet()
Assert.assertEquals(expectedDirties, dirties)
}
}
}

private fun toggleFlags(vararg extras: String) {
val gradleRunner = GradleRunner.create().withProjectDir(project.root).withDebug(true)

Expand Down
@@ -0,0 +1,3 @@
package p1

fun MyTopFunc1(): Int = 0
@@ -0,0 +1,3 @@
package p1

val MyTopProp1: Int = 0
@@ -1,4 +1,6 @@
import com.google.devtools.ksp.getClassDeclarationByName
import com.google.devtools.ksp.getFunctionDeclarationsByName
import com.google.devtools.ksp.getPropertyDeclarationByName
import com.google.devtools.ksp.processing.*
import com.google.devtools.ksp.symbol.*
import com.google.devtools.ksp.validate
Expand Down Expand Up @@ -61,6 +63,18 @@ class Validator : SymbolProcessor {
val l5 = resolver.getClassDeclarationByName("p1.L5")!!
codeGenerator.createNewFile(Dependencies(false), "p1", "l5", "log")
codeGenerator.associateWithClasses(listOf(l5), "p1", "l5", "log")

// create an output from MyTopFunc1, declared in TopFunc1.kt, and k3
val myTopFunc1 = resolver.getFunctionDeclarationsByName("p1.MyTopFunc1", true).single()
codeGenerator.createNewFile(Dependencies(false), "p1", "MyTopFunc1", "log")
codeGenerator.associateWithFunctions(listOf(myTopFunc1), "p1", "MyTopFunc1", "log")
codeGenerator.associateWithClasses(listOf(k3), "p1", "MyTopFunc1", "log")

// create an output from MyTopFunc1, declared in TopFunc1.kt, and k3
val myTopProp1 = resolver.getPropertyDeclarationByName("p1.MyTopProp1", true)!!
codeGenerator.createNewFile(Dependencies(false), "p1", "MyTopProp1", "log")
codeGenerator.associateWithProperties(listOf(myTopProp1), "p1", "MyTopProp1", "log")
codeGenerator.associateWithClasses(listOf(k3), "p1", "MyTopProp1", "log")
logger.warn("processing done")

processed = true
Expand Down

0 comments on commit 3adb158

Please sign in to comment.