Skip to content

Commit

Permalink
Merge branch 'main' into z/k2
Browse files Browse the repository at this point in the history
  • Loading branch information
ZacSweers authored May 23, 2024
2 parents aa3b8eb + 2c787e2 commit 19eaf57
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 21 deletions.
6 changes: 4 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ kct = "0.5.0-alpha07"
kotlin = "2.0.0"
jdk = "21"
jvmTarget = "11"
kotlinpoet = "1.16.0"
ksp = "2.0.0-1.0.21"
ktfmt = "0.49"

Expand All @@ -24,7 +25,8 @@ guava = "com.google.guava:guava:33.2.0-jre"

kotlin-compilerEmbeddable = { module = "org.jetbrains.kotlin:kotlin-compiler-embeddable", version.ref = "kotlin" }

kotlinpoet = "com.squareup:kotlinpoet:1.16.0"
kotlinpoet = { module = "com.squareup:kotlinpoet", version.ref = "kotlinpoet" }
kotlinpoet-ksp = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlinpoet" }

ksp-api = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }

Expand All @@ -35,4 +37,4 @@ ktfmt = { module = "com.facebook:ktfmt", version.ref = "ktfmt" }
junit = { module = "junit:junit", version = "4.13.2" }
truth = { module = "com.google.truth:truth", version = "1.4.2" }
kct-core = { module = "dev.zacsweers.kctfork:core", version.ref = "kct" }
kct-ksp = { module = "dev.zacsweers.kctfork:ksp", version.ref ="kct" }
kct-ksp = { module = "dev.zacsweers.kctfork:ksp", version.ref = "kct" }
1 change: 1 addition & 0 deletions processor/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ dependencies {
testImplementation(libs.kct.ksp)
testImplementation(libs.ksp.api)
testImplementation(libs.truth)
testImplementation(libs.kotlinpoet.ksp)
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ public class AutoServiceSymbolProcessor(environment: SymbolProcessorEnvironment)
return emptyList()
}

val deferred = mutableListOf<KSAnnotated>()

resolver
.getSymbolsWithAnnotation(AUTO_SERVICE_NAME)
.filterIsInstance<KSClassDeclaration>()
Expand Down Expand Up @@ -119,34 +121,51 @@ public class AutoServiceSymbolProcessor(environment: SymbolProcessorEnvironment)
}

for (providerType in providerInterfaces) {
val providerDecl = providerType.declaration.closestClassDeclaration()!!
if (checkImplementer(providerImplementer, providerType)) {
providers.put(
providerDecl.toBinaryName(),
providerImplementer.toBinaryName() to providerImplementer.containingFile!!)
} else {
val message =
"ServiceProviders must implement their service provider interface. " +
providerImplementer.qualifiedName +
" does not implement " +
providerDecl.qualifiedName
logger.error(message, providerImplementer)
if (providerType.isError) {
deferred += providerImplementer
return@forEach
}
val providerDecl = providerType.declaration.closestClassDeclaration()!!
when (checkImplementer(providerImplementer, providerType)) {
ValidationResult.VALID -> {
providers.put(
providerDecl.toBinaryName(),
providerImplementer.toBinaryName() to providerImplementer.containingFile!!,
)
}
ValidationResult.INVALID -> {
val message =
"ServiceProviders must implement their service provider interface. " +
providerImplementer.qualifiedName +
" does not implement " +
providerDecl.qualifiedName
logger.error(message, providerImplementer)
}
ValidationResult.DEFERRED -> {
deferred += providerImplementer
}
}
}
}
generateAndClearConfigFiles()
return emptyList()
return deferred
}

private fun checkImplementer(
providerImplementer: KSClassDeclaration,
providerType: KSType
): Boolean {
providerType: KSType,
): ValidationResult {
if (!verify) {
return true
return ValidationResult.VALID
}
for (superType in providerImplementer.getAllSuperTypes()) {
if (superType.isAssignableFrom(providerType)) {
return ValidationResult.VALID
} else if (superType.isError) {
return ValidationResult.DEFERRED
}
}
return providerImplementer.getAllSuperTypes().any { it.isAssignableFrom(providerType) }
return ValidationResult.INVALID
}

private fun generateAndClearConfigFiles() {
Expand Down Expand Up @@ -200,6 +219,12 @@ public class AutoServiceSymbolProcessor(environment: SymbolProcessorEnvironment)
return ClassName(pkgName, simpleNames)
}

private enum class ValidationResult {
VALID,
INVALID,
DEFERRED,
}

@AutoService(SymbolProcessorProvider::class)
public class Provider : SymbolProcessorProvider {
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,29 @@
package dev.zacsweers.autoservice.ksp

import com.google.common.truth.Truth.assertThat
import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.processing.SymbolProcessorProvider
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.ksp.addOriginatingKSFile
import com.squareup.kotlinpoet.ksp.writeTo
import com.tschuchort.compiletesting.KotlinCompilation
import com.tschuchort.compiletesting.KotlinCompilation.ExitCode
import com.tschuchort.compiletesting.SourceFile
import com.tschuchort.compiletesting.configureKsp
import com.tschuchort.compiletesting.SourceFile.Companion.kotlin
import com.tschuchort.compiletesting.kspIncremental
import com.tschuchort.compiletesting.kspSourcesDir
import com.tschuchort.compiletesting.symbolProcessorProviders
import com.tschuchort.compiletesting.useKsp2
import java.io.File
import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
Expand Down Expand Up @@ -65,7 +79,7 @@ class AutoServiceSymbolProcessorTest(
@Test
fun smokeTest() {
val source =
SourceFile.kotlin(
kotlin(
"CustomCallable.kt",
"""
package test
Expand Down Expand Up @@ -119,7 +133,7 @@ class AutoServiceSymbolProcessorTest(
@Test
fun errorOnNoServiceInterfacesProvided() {
val source =
SourceFile.kotlin(
kotlin(
"CustomCallable.kt",
"""
package test
Expand All @@ -145,4 +159,77 @@ class AutoServiceSymbolProcessorTest(
"""
.trimIndent())
}

class TacoGenerator(private val codeGenerator: CodeGenerator) : SymbolProcessor {
class Provider : SymbolProcessorProvider {
override fun create(environment: SymbolProcessorEnvironment) =
TacoGenerator(environment.codeGenerator)
}

override fun process(resolver: Resolver): List<KSAnnotated> {
resolver
.getSymbolsWithAnnotation("test.GenerateInterface")
.filterIsInstance<KSClassDeclaration>()
.forEach { annotated ->
FileSpec.get(
annotated.packageName.asString(),
TypeSpec.interfaceBuilder("I${annotated.simpleName.asString()}")
.addOriginatingKSFile(annotated.containingFile!!)
.build(),
)
.writeTo(codeGenerator, aggregating = false)
}
return emptyList()
}
}

@Ignore("https://github.com/google/ksp/issues/1916")
@Test
fun deferredTypes() {
// This tests handling error types in multiple rounds to ensure we defer correctly when
// encountering error types. This works by having a simple processor that generates an
// interface with a given name from an annotated class, then an AutoService-annotated
// class that implements that interface.
val generateAnnotation =
kotlin(
"GenerateInterface.kt",
"""
package test
annotation class GenerateInterface
""",
)
val source =
kotlin(
"CustomCallable.kt",
"""
package test
import com.google.auto.service.AutoService
import java.util.concurrent.Callable
import test.IExample
@GenerateInterface
class Example
@AutoService(IExample::class, Callable::class)
class ExampleImpl : IExample, Callable<String> {
override fun call(): String = "Hello world!"
}
""",
)

val compilation =
KotlinCompilation().apply {
sources = listOf(generateAnnotation, source)
inheritClassPath = true
symbolProcessorProviders =
listOf(AutoServiceSymbolProcessor.Provider(), TacoGenerator.Provider())
kspIncremental = incremental
}
val result = compilation.compile()
assertThat(result.exitCode).isEqualTo(ExitCode.OK)
val generatedSourcesDir = compilation.kspSourcesDir
val generatedFile = File(generatedSourcesDir, "resources/META-INF/services/test.IExample")
assertThat(generatedFile.exists()).isTrue()
assertThat(generatedFile.readText()).isEqualTo("test.ExampleImpl\n")
}
}

0 comments on commit 19eaf57

Please sign in to comment.