Skip to content

Commit

Permalink
workaround for classpath snapshot issue
Browse files Browse the repository at this point in the history
  • Loading branch information
neetopia committed Apr 13, 2023
1 parent 96006ab commit 1cbfacf
Show file tree
Hide file tree
Showing 15 changed files with 606 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,10 @@ class KspGradleSubplugin @Inject internal constructor(private val registry: Tool
kotlinCompileTask.project.files(
Callable {
kotlinCompileTask.libraries.filter {
!kspOutputDir.isParentOf(it)
// manually exclude KAPT generated class folder from class path snapshot.
// TODO: remove in 1.9.0.

!kspOutputDir.isParentOf(it) && !(it.isDirectory && it.listFiles()?.isEmpty() == true)
}
}
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.google.devtools.ksp.test

import org.gradle.testkit.runner.BuildResult
import org.gradle.testkit.runner.GradleRunner
import org.gradle.testkit.runner.TaskOutcome
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
import java.io.File
import java.util.jar.JarFile

class KAPT3IT {
@Rule
@JvmField
val project: TemporaryTestProject = TemporaryTestProject("kapt3")

private fun GradleRunner.buildAndCheck(vararg args: String, extraCheck: (BuildResult) -> Unit = {}) =
buildAndCheckOutcome(*args, outcome = TaskOutcome.SUCCESS, extraCheck = extraCheck)

private fun GradleRunner.buildAndCheckOutcome(
vararg args: String,
outcome: TaskOutcome,
extraCheck: (BuildResult) -> Unit = {}
) {
val result = this.withArguments(*args).build()

Assert.assertEquals(outcome, result.task(":workload:build")?.outcome)

val artifact = File(project.root, "workload/build/libs/workload-1.0-SNAPSHOT.jar")
Assert.assertTrue(artifact.exists())

JarFile(artifact).use { jarFile ->
Assert.assertTrue(jarFile.getEntry("TestProcessor.log").size > 0)
Assert.assertTrue(jarFile.getEntry("hello/HELLO.class").size > 0)
Assert.assertTrue(jarFile.getEntry("com/example/AClassBuilder.class").size > 0)
}

extraCheck(result)
}

@Test
fun testWithKAPT3() {
val gradleRunner = GradleRunner.create().withProjectDir(project.root)
gradleRunner.buildAndCheck("--build-cache", "build")
val Akt = File(project.root, "workload/src/main/java/com/example/A.kt")
Akt.appendText("class Void")
gradleRunner.buildAndCheck("--build-cache", "build")
}
}
8 changes: 8 additions & 0 deletions integration-tests/src/test/resources/kapt3/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
plugins {
kotlin("jvm")
}

repositories {
mavenCentral()
maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/bootstrap/")
}
2 changes: 2 additions & 0 deletions integration-tests/src/test/resources/kapt3/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
org.gradle.jvmargs=-Xmx4096M -XX:MaxMetaspaceSize=1024m
org.gradle.unsafe.configuration-cache.max-problems=5000
19 changes: 19 additions & 0 deletions integration-tests/src/test/resources/kapt3/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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/")
}
}

rootProject.name = "kapt3"

include(":workload")
include(":test-processor")
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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.squareup:javapoet:1.12.1")
implementation("com.google.devtools.ksp:symbol-processing-api:$kspVersion")
}

sourceSets.main {
java.srcDirs("src/main/kotlin")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.example.annotation

annotation class Builder
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@

import com.google.devtools.ksp.containingFile
import com.google.devtools.ksp.processing.*
import com.google.devtools.ksp.symbol.*
import com.google.devtools.ksp.validate
import java.io.OutputStream

fun OutputStream.appendText(str: String) {
this.write(str.toByteArray())
}

class BuilderProcessor : SymbolProcessor {
lateinit var codeGenerator: CodeGenerator
lateinit var logger: KSPLogger

fun init(
options: Map<String, String>,
kotlinVersion: KotlinVersion,
codeGenerator: CodeGenerator,
logger: KSPLogger,
) {
this.codeGenerator = codeGenerator
this.logger = logger
}

override fun process(resolver: Resolver): List<KSAnnotated> {
val symbols = resolver.getSymbolsWithAnnotation("com.example.annotation.Builder")
val ret = symbols.filter { !it.validate() }
symbols
.filter { it is KSClassDeclaration && it.validate() }
.forEach { it.accept(BuilderVisitor(), Unit) }
return ret.toList()
}

inner class BuilderVisitor : KSVisitorVoid() {
override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) {
classDeclaration.primaryConstructor?.accept(this, data)
}

override fun visitFunctionDeclaration(function: KSFunctionDeclaration, data: Unit) {
val parent = function.parentDeclaration as KSClassDeclaration
val packageName = parent.containingFile!!.packageName.asString()
val className = "${parent.simpleName.asString()}Builder"

// For regression testing https://github.com/google/ksp/pull/467
codeGenerator.createNewFile(
Dependencies(true, function.containingFile!!),
"",
"META-INF/proguard/builder-$className",
"pro"
).use { proguardFile ->
proguardFile.appendText("-keep class $packageName.$className { *; }")
}

val file = codeGenerator.createNewFile(
Dependencies(true, function.containingFile!!), packageName, className
)
file.appendText("package $packageName\n\n")
file.appendText("import hello.HELLO\n\n")
file.appendText("class $className{\n")
function.parameters.forEach {
val name = it.name!!.asString()
val typeName = StringBuilder(it.type.resolve().declaration.qualifiedName?.asString() ?: "<ERROR>")
val typeArgs = it.type.element!!.typeArguments
if (it.type.element!!.typeArguments.toList().isNotEmpty()) {
typeName.append("<")
typeName.append(
typeArgs.map {
val type = it.type?.resolve()
"${it.variance.label} ${type?.declaration?.qualifiedName?.asString() ?: "ERROR"}" +
if (type?.nullability == Nullability.NULLABLE) "?" else ""
}.joinToString(", ")
)
typeName.append(">")
}
file.appendText(" private var $name: $typeName? = null\n")
file.appendText(" internal fun with${name.capitalize()}($name: $typeName): $className {\n")
file.appendText(" this.$name = $name\n")
file.appendText(" return this\n")
file.appendText(" }\n\n")
}
file.appendText(" internal fun build(): ${parent.qualifiedName!!.asString()} {\n")
file.appendText(" return ${parent.qualifiedName!!.asString()}(")
file.appendText(
function.parameters.map {
"${it.name!!.asString()}!!"
}.joinToString(", ")
)
file.appendText(")\n")
file.appendText(" }\n")
file.appendText("}\n")
file.close()
}
}
}

class TestProcessorProvider : SymbolProcessorProvider {
override fun create(
env: SymbolProcessorEnvironment,
): SymbolProcessor {
return BuilderProcessor().apply {
init(env.options, env.kotlinVersion, env.codeGenerator, env.logger)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

import com.google.devtools.ksp.processing.*
import com.google.devtools.ksp.symbol.*

class RewriteProcessor : SymbolProcessor {
lateinit var codeGenerator: CodeGenerator

fun init(
options: Map<String, String>,
kotlinVersion: KotlinVersion,
codeGenerator: CodeGenerator,
logger: KSPLogger
) {
this.codeGenerator = codeGenerator
}

override fun process(resolver: Resolver): List<KSAnnotated> {
val fileKt = codeGenerator.createNewFile(Dependencies(false), "hello", "HELLO", "java")
return emptyList()
}
}

class RewriteProcessorProvider : SymbolProcessorProvider {
override fun create(
env: SymbolProcessorEnvironment
): SymbolProcessor {
return RewriteProcessor().apply {
init(env.options, env.kotlinVersion, env.codeGenerator, env.logger)
}
}
}
Loading

0 comments on commit 1cbfacf

Please sign in to comment.