From 460d9bf2c2dd6b19953026350da4b9bb8f1201ed Mon Sep 17 00:00:00 2001 From: Goooler Date: Sat, 11 Oct 2025 16:14:42 +0800 Subject: [PATCH 01/10] Convert RetrofitResponseTypeKeepProcessorTest to parametermized tests --- retrofit-response-type-keeper/build.gradle | 1 + .../RetrofitResponseTypeKeepProcessorTest.kt | 138 +++--------------- .../resources/all-http-methods/Service.java | 24 +++ .../resources/all-http-methods/Service.pro | 10 ++ .../resources/kotlin-suspend/Service.java | 11 ++ .../test/resources/kotlin-suspend/Service.pro | 3 + .../src/test/resources/nesting/Service.java | 12 ++ .../src/test/resources/nesting/Service.pro | 5 + 8 files changed, 88 insertions(+), 116 deletions(-) create mode 100644 retrofit-response-type-keeper/src/test/resources/all-http-methods/Service.java create mode 100644 retrofit-response-type-keeper/src/test/resources/all-http-methods/Service.pro create mode 100644 retrofit-response-type-keeper/src/test/resources/kotlin-suspend/Service.java create mode 100644 retrofit-response-type-keeper/src/test/resources/kotlin-suspend/Service.pro create mode 100644 retrofit-response-type-keeper/src/test/resources/nesting/Service.java create mode 100644 retrofit-response-type-keeper/src/test/resources/nesting/Service.pro diff --git a/retrofit-response-type-keeper/build.gradle b/retrofit-response-type-keeper/build.gradle index c9015f23f0..00fd0c6d26 100644 --- a/retrofit-response-type-keeper/build.gradle +++ b/retrofit-response-type-keeper/build.gradle @@ -11,5 +11,6 @@ dependencies { testImplementation libs.junit testImplementation libs.compileTesting testImplementation libs.truth + testImplementation libs.testParameterInjector testImplementation projects.retrofit } diff --git a/retrofit-response-type-keeper/src/test/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepProcessorTest.kt b/retrofit-response-type-keeper/src/test/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepProcessorTest.kt index 23cc38bb9c..6c97f063c4 100644 --- a/retrofit-response-type-keeper/src/test/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepProcessorTest.kt +++ b/retrofit-response-type-keeper/src/test/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepProcessorTest.kt @@ -18,86 +18,29 @@ package retrofit2.keeper import com.google.common.truth.Truth.assertAbout import com.google.testing.compile.JavaFileObjects import com.google.testing.compile.JavaSourceSubjectFactory.javaSource +import com.google.testing.junit.testparameterinjector.TestParameter +import com.google.testing.junit.testparameterinjector.TestParameterInjector import java.nio.charset.StandardCharsets.UTF_8 +import java.nio.file.NoSuchFileException import javax.tools.StandardLocation.CLASS_OUTPUT +import kotlin.io.path.readText +import kotlin.io.path.toPath import org.junit.Test +import org.junit.runner.RunWith +@RunWith(TestParameterInjector::class) class RetrofitResponseTypeKeepProcessorTest { @Test - fun allHttpMethods() { + fun process( + @TestParameter( + "all-http-methods", + "nesting", + "kotlin-suspend", + ) name: String + ) { val service = JavaFileObjects.forSourceString( "test.Service", - """ - package test; - import retrofit2.*; - import retrofit2.http.*; - - class DeleteUser {} - class GetUser {} - class HeadUser {} - class HttpUser {} - class OptionsUser {} - class PatchUser {} - class PostUser {} - class PutUser {} - - interface Service { - @DELETE("/") Call delete(); - @GET("/") Call get(); - @HEAD("/") Call head(); - @HTTP(method = "CUSTOM", path = "/") Call http(); - @OPTIONS("/") Call options(); - @PATCH("/") Call patch(); - @POST("/") Call post(); - @PUT("/") Call put(); - } - """.trimIndent(), - ) - - assertAbout(javaSource()) - .that(service) - .processedWith(RetrofitResponseTypeKeepProcessor()) - .compilesWithoutError() - .and() - .generatesFileNamed( - CLASS_OUTPUT, - "", - "META-INF/proguard/retrofit-response-type-keeper-test.Service.pro", - ).withStringContents( - UTF_8, - """ - |# test.Service - |-keep,allowoptimization,allowshrinking,allowobfuscation class retrofit2.Call - |-keep,allowoptimization,allowshrinking,allowobfuscation class test.DeleteUser - |-keep,allowoptimization,allowshrinking,allowobfuscation class test.GetUser - |-keep,allowoptimization,allowshrinking,allowobfuscation class test.HeadUser - |-keep,allowoptimization,allowshrinking,allowobfuscation class test.HttpUser - |-keep,allowoptimization,allowshrinking,allowobfuscation class test.OptionsUser - |-keep,allowoptimization,allowshrinking,allowobfuscation class test.PatchUser - |-keep,allowoptimization,allowshrinking,allowobfuscation class test.PostUser - |-keep,allowoptimization,allowshrinking,allowobfuscation class test.PutUser - | - """.trimMargin(), - ) - } - - @Test - fun nesting() { - val service = JavaFileObjects.forSourceString( - "test.Service", - """ - package test; - import retrofit2.*; - import retrofit2.http.*; - - class One {} - class Two {} - class Three {} - - interface Service { - @GET("/") Call>> get(); - } - """.trimIndent(), + readResourceAsText("$name/Service.java"), ) assertAbout(javaSource()) @@ -111,52 +54,15 @@ class RetrofitResponseTypeKeepProcessorTest { "META-INF/proguard/retrofit-response-type-keeper-test.Service.pro", ).withStringContents( UTF_8, - """ - |# test.Service - |-keep,allowoptimization,allowshrinking,allowobfuscation class retrofit2.Call - |-keep,allowoptimization,allowshrinking,allowobfuscation class test.One - |-keep,allowoptimization,allowshrinking,allowobfuscation class test.Three - |-keep,allowoptimization,allowshrinking,allowobfuscation class test.Two - | - """.trimMargin(), + readResourceAsText("$name/Service.pro"), ) } - @Test - fun kotlinSuspend() { - val service = JavaFileObjects.forSourceString( - "test.Service", - """ - package test; - import kotlin.coroutines.Continuation; - import retrofit2.*; - import retrofit2.http.*; - - class Body {} - - interface Service { - @GET("/") Object get(Continuation c); - } - """.trimIndent(), - ) - - assertAbout(javaSource()) - .that(service) - .processedWith(RetrofitResponseTypeKeepProcessor()) - .compilesWithoutError() - .and() - .generatesFileNamed( - CLASS_OUTPUT, - "", - "META-INF/proguard/retrofit-response-type-keeper-test.Service.pro", - ).withStringContents( - UTF_8, - """ - |# test.Service - |-keep,allowoptimization,allowshrinking,allowobfuscation class java.lang.Object - |-keep,allowoptimization,allowshrinking,allowobfuscation class test.Body - | - """.trimMargin(), - ) + private companion object { + fun readResourceAsText(name: String): String { + val resource = this::class.java.classLoader.getResource(name) + ?: throw NoSuchFileException("Resource $name not found.") + return resource.toURI().toPath().readText() + } } } diff --git a/retrofit-response-type-keeper/src/test/resources/all-http-methods/Service.java b/retrofit-response-type-keeper/src/test/resources/all-http-methods/Service.java new file mode 100644 index 0000000000..93ad79b7b5 --- /dev/null +++ b/retrofit-response-type-keeper/src/test/resources/all-http-methods/Service.java @@ -0,0 +1,24 @@ +package test; + +import retrofit2.*; +import retrofit2.http.*; + +class DeleteUser {} +class GetUser {} +class HeadUser {} +class HttpUser {} +class OptionsUser {} +class PatchUser {} +class PostUser {} +class PutUser {} + +interface Service { + @DELETE("/") Call delete(); + @GET("/") Call get(); + @HEAD("/") Call head(); + @HTTP(method = "CUSTOM", path = "/") Call http(); + @OPTIONS("/") Call options(); + @PATCH("/") Call patch(); + @POST("/") Call post(); + @PUT("/") Call put(); +} diff --git a/retrofit-response-type-keeper/src/test/resources/all-http-methods/Service.pro b/retrofit-response-type-keeper/src/test/resources/all-http-methods/Service.pro new file mode 100644 index 0000000000..bc42931349 --- /dev/null +++ b/retrofit-response-type-keeper/src/test/resources/all-http-methods/Service.pro @@ -0,0 +1,10 @@ +# test.Service +-keep,allowoptimization,allowshrinking,allowobfuscation class retrofit2.Call +-keep,allowoptimization,allowshrinking,allowobfuscation class test.DeleteUser +-keep,allowoptimization,allowshrinking,allowobfuscation class test.GetUser +-keep,allowoptimization,allowshrinking,allowobfuscation class test.HeadUser +-keep,allowoptimization,allowshrinking,allowobfuscation class test.HttpUser +-keep,allowoptimization,allowshrinking,allowobfuscation class test.OptionsUser +-keep,allowoptimization,allowshrinking,allowobfuscation class test.PatchUser +-keep,allowoptimization,allowshrinking,allowobfuscation class test.PostUser +-keep,allowoptimization,allowshrinking,allowobfuscation class test.PutUser diff --git a/retrofit-response-type-keeper/src/test/resources/kotlin-suspend/Service.java b/retrofit-response-type-keeper/src/test/resources/kotlin-suspend/Service.java new file mode 100644 index 0000000000..6cd8250020 --- /dev/null +++ b/retrofit-response-type-keeper/src/test/resources/kotlin-suspend/Service.java @@ -0,0 +1,11 @@ +package test; + +import kotlin.coroutines.Continuation; +import retrofit2.*; +import retrofit2.http.*; + +class Body {} + +interface Service { + @GET("/") Object get(Continuation c); +} diff --git a/retrofit-response-type-keeper/src/test/resources/kotlin-suspend/Service.pro b/retrofit-response-type-keeper/src/test/resources/kotlin-suspend/Service.pro new file mode 100644 index 0000000000..f16efdb512 --- /dev/null +++ b/retrofit-response-type-keeper/src/test/resources/kotlin-suspend/Service.pro @@ -0,0 +1,3 @@ +# test.Service +-keep,allowoptimization,allowshrinking,allowobfuscation class java.lang.Object +-keep,allowoptimization,allowshrinking,allowobfuscation class test.Body diff --git a/retrofit-response-type-keeper/src/test/resources/nesting/Service.java b/retrofit-response-type-keeper/src/test/resources/nesting/Service.java new file mode 100644 index 0000000000..7c4f2e5959 --- /dev/null +++ b/retrofit-response-type-keeper/src/test/resources/nesting/Service.java @@ -0,0 +1,12 @@ +package test; + +import retrofit2.*; +import retrofit2.http.*; + +class One {} +class Two {} +class Three {} + +interface Service { + @GET("/") Call>> get(); +} diff --git a/retrofit-response-type-keeper/src/test/resources/nesting/Service.pro b/retrofit-response-type-keeper/src/test/resources/nesting/Service.pro new file mode 100644 index 0000000000..331a97c51b --- /dev/null +++ b/retrofit-response-type-keeper/src/test/resources/nesting/Service.pro @@ -0,0 +1,5 @@ +# test.Service +-keep,allowoptimization,allowshrinking,allowobfuscation class retrofit2.Call +-keep,allowoptimization,allowshrinking,allowobfuscation class test.One +-keep,allowoptimization,allowshrinking,allowobfuscation class test.Three +-keep,allowoptimization,allowshrinking,allowobfuscation class test.Two From cb2c809c033bdce10681cb8b72ae22029ffbfd32 Mon Sep 17 00:00:00 2001 From: Goooler Date: Sun, 28 Sep 2025 17:49:27 +0800 Subject: [PATCH 02/10] Support using response type keeper with KSP --- .github/renovate.json5 | 7 ++ CHANGELOG.md | 1 + gradle/libs.versions.toml | 5 ++ retrofit-response-type-keeper/build.gradle | 4 + .../RetrofitResponseTypeKeepProcessor.kt | 16 +--- ...RetrofitResponseTypeKeepSymbolProcessor.kt | 80 +++++++++++++++++++ .../src/main/kotlin/retrofit2/keeper/Utils.kt | 17 ++++ .../RetrofitResponseTypeKeepProcessorTest.kt | 68 +++++++++++----- .../resources/all-http-methods/Service.kt | 39 +++++++++ .../test/resources/kotlin-suspend/Service.kt | 11 +++ .../src/test/resources/nesting/Service.kt | 13 +++ 11 files changed, 230 insertions(+), 31 deletions(-) create mode 100644 retrofit-response-type-keeper/src/main/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepSymbolProcessor.kt create mode 100644 retrofit-response-type-keeper/src/main/kotlin/retrofit2/keeper/Utils.kt create mode 100644 retrofit-response-type-keeper/src/test/resources/all-http-methods/Service.kt create mode 100644 retrofit-response-type-keeper/src/test/resources/kotlin-suspend/Service.kt create mode 100644 retrofit-response-type-keeper/src/test/resources/nesting/Service.kt diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 5fd1a7750e..592f220e0b 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -22,5 +22,12 @@ // Only write the major version. extractVersionTemplate: '^(?\\d+)', }, + { + groupName: 'Kotlin and KSP', + matchPackageNames: [ + 'com.google.devtools.ksp{/,}**', + 'org.jetbrains.kotlin:kotlin{/,}**', + ], + }, ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 757cef7734..0ab11d0853 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Add explicit keep rules for RxJava `Result` types to prevent their generic information from being removed. - Add `allowoptimization` flags for most kept types. + - Support using response type keeper with KSP. **Changed** diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index be8f3d7ad0..b81417f637 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,6 +18,7 @@ okhttp = "5.2.1" protobuf = "3.25.8" robovm = "2.3.14" kotlinx-serialization = "1.9.0" +kct = "0.10.0" autoService = "1.1.1" incap = "1.0.0" jackson = "2.20.0" @@ -33,6 +34,8 @@ kotlin-stdLib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = " kotlin-gradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } kotlin-serializationPlugin = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" } +ksp-api = "com.google.devtools.ksp:symbol-processing-api:2.2.20-2.0.3" + errorpronePlugin = "net.ltgt.gradle:gradle-errorprone-plugin:4.3.0" errorproneCore = { module = "com.google.errorprone:error_prone_core", version = "2.10.0" } errorproneJavac = { module = "com.google.errorprone:javac", version = "9+181-r4173-1" } @@ -84,3 +87,5 @@ googleJavaFormat = "com.google.googlejavaformat:google-java-format:1.28.0" ktlint = "com.pinterest.ktlint:ktlint-cli:1.7.1" compileTesting = "com.google.testing.compile:compile-testing:0.23.0" testParameterInjector = "com.google.testparameterinjector:test-parameter-injector:1.19" +kct-core = { module = "dev.zacsweers.kctfork:core", version.ref = "kct" } +kct-ksp = { module = "dev.zacsweers.kctfork:ksp", version.ref = "kct" } diff --git a/retrofit-response-type-keeper/build.gradle b/retrofit-response-type-keeper/build.gradle index 00fd0c6d26..7dbea9aff8 100644 --- a/retrofit-response-type-keeper/build.gradle +++ b/retrofit-response-type-keeper/build.gradle @@ -5,11 +5,15 @@ apply plugin: 'com.vanniktech.maven.publish' dependencies { compileOnly libs.autoService.annotations compileOnly libs.incap.runtime + compileOnly libs.ksp.api kapt libs.autoService.compiler kapt libs.incap.processor testImplementation libs.junit testImplementation libs.compileTesting + testImplementation libs.kct.core + testImplementation libs.kct.ksp + testImplementation libs.ksp.api testImplementation libs.truth testImplementation libs.testParameterInjector testImplementation projects.retrofit diff --git a/retrofit-response-type-keeper/src/main/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepProcessor.kt b/retrofit-response-type-keeper/src/main/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepProcessor.kt index 6d208d4248..910290ffe8 100644 --- a/retrofit-response-type-keeper/src/main/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepProcessor.kt +++ b/retrofit-response-type-keeper/src/main/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepProcessor.kt @@ -33,16 +33,7 @@ import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType.ISOLATING @IncrementalAnnotationProcessor(ISOLATING) class RetrofitResponseTypeKeepProcessor : AbstractProcessor() { override fun getSupportedSourceVersion() = SourceVersion.latestSupported() - override fun getSupportedAnnotationTypes() = setOf( - "retrofit2.http.DELETE", - "retrofit2.http.GET", - "retrofit2.http.HEAD", - "retrofit2.http.HTTP", - "retrofit2.http.OPTIONS", - "retrofit2.http.PATCH", - "retrofit2.http.POST", - "retrofit2.http.PUT", - ) + override fun getSupportedAnnotationTypes() = annotationNames override fun process( annotations: Set, @@ -77,12 +68,11 @@ class RetrofitResponseTypeKeepProcessor : AbstractProcessor() { for ((element, referencedTypes) in elementToReferencedTypes) { val typeName = element.qualifiedName.toString() - val outputFile = "META-INF/proguard/retrofit-response-type-keeper-$typeName.pro" - val rules = processingEnv.filer.createResource(CLASS_OUTPUT, "", outputFile, element) + val rules = processingEnv.filer.createResource(CLASS_OUTPUT, "", outputFile(typeName), element) rules.openWriter().buffered().use { w -> w.write("# $typeName\n") for (referencedType in referencedTypes.sorted()) { - w.write("-keep,allowoptimization,allowshrinking,allowobfuscation class $referencedType\n") + w.write("$KEEP_RULE_PREFIX $referencedType\n") } } } diff --git a/retrofit-response-type-keeper/src/main/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepSymbolProcessor.kt b/retrofit-response-type-keeper/src/main/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepSymbolProcessor.kt new file mode 100644 index 0000000000..85827d34b7 --- /dev/null +++ b/retrofit-response-type-keeper/src/main/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepSymbolProcessor.kt @@ -0,0 +1,80 @@ +package retrofit2.keeper + +import com.google.auto.service.AutoService +import com.google.devtools.ksp.processing.CodeGenerator +import com.google.devtools.ksp.processing.Dependencies +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.google.devtools.ksp.symbol.KSFunctionDeclaration +import com.google.devtools.ksp.symbol.KSType +import com.google.devtools.ksp.symbol.Modifier + +class RetrofitResponseTypeKeepSymbolProcessor( + environment: SymbolProcessorEnvironment, +) : SymbolProcessor { + private val codeGenerator: CodeGenerator = environment.codeGenerator + + override fun process(resolver: Resolver): List { + val elementToReferencedTypes = mutableMapOf>() + + annotationNames.flatMap { resolver.getSymbolsWithAnnotation(it) } + .filterIsInstance() + .forEach { function -> + val serviceType = function.parentDeclaration as? KSClassDeclaration ?: return@forEach + val referenced = elementToReferencedTypes.getOrPut(serviceType, ::LinkedHashSet) + + // Retrofit has special support for 'suspend fun' in Kotlin which manifests as a + // final Continuation parameter whose generic type is the declared return type. + if (function.modifiers.contains(Modifier.SUSPEND)) { + function.parameters.forEach { + it.type.resolve().recursiveParameterizedTypesTo(referenced) + } + } + + val returnType = function.returnType?.resolve() ?: return@forEach + returnType.recursiveParameterizedTypesTo(referenced) + } + + elementToReferencedTypes.forEach { (element, referencedTypes) -> + val containingFile = element.containingFile ?: return@forEach + val typeName = requireNotNull(element.qualifiedName).asString() + + val dependencies = Dependencies(aggregating = false, containingFile) + codeGenerator.createNewFile(dependencies, "", outputFile(typeName), "") + .bufferedWriter().use { writer -> + writer.write("# $typeName\n") + for (referencedType in referencedTypes.sorted()) { + writer.write("$KEEP_RULE_PREFIX $referencedType\n") + } + } + } + + return emptyList() + } + + private fun KSType.recursiveParameterizedTypesTo(types: MutableSet) { + val declaration = this.declaration + if (declaration is KSClassDeclaration) { + var qualifiedName = declaration.qualifiedName?.asString() + if (qualifiedName == "kotlin.Any") { + qualifiedName = "java.lang.Object" + } + qualifiedName?.let { types.add(it) } + } + + for (typeArgument in arguments) { + typeArgument.type?.resolve()?.recursiveParameterizedTypesTo(types) + } + } + + @Suppress("unused") // Used in service file. + @AutoService(SymbolProcessorProvider::class) + class Provider : SymbolProcessorProvider { + override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor = + RetrofitResponseTypeKeepSymbolProcessor(environment) + } +} diff --git a/retrofit-response-type-keeper/src/main/kotlin/retrofit2/keeper/Utils.kt b/retrofit-response-type-keeper/src/main/kotlin/retrofit2/keeper/Utils.kt new file mode 100644 index 0000000000..62e3a57494 --- /dev/null +++ b/retrofit-response-type-keeper/src/main/kotlin/retrofit2/keeper/Utils.kt @@ -0,0 +1,17 @@ +package retrofit2.keeper + +internal val annotationNames = setOf( + "retrofit2.http.DELETE", + "retrofit2.http.GET", + "retrofit2.http.HEAD", + "retrofit2.http.HTTP", + "retrofit2.http.OPTIONS", + "retrofit2.http.PATCH", + "retrofit2.http.POST", + "retrofit2.http.PUT", +) + +internal const val KEEP_RULE_PREFIX = "-keep,allowoptimization,allowshrinking,allowobfuscation class" + +internal fun outputFile(typeName: String) = + "META-INF/proguard/retrofit-response-type-keeper-$typeName.pro" diff --git a/retrofit-response-type-keeper/src/test/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepProcessorTest.kt b/retrofit-response-type-keeper/src/test/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepProcessorTest.kt index 6c97f063c4..0f39e26692 100644 --- a/retrofit-response-type-keeper/src/test/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepProcessorTest.kt +++ b/retrofit-response-type-keeper/src/test/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepProcessorTest.kt @@ -16,46 +16,78 @@ package retrofit2.keeper import com.google.common.truth.Truth.assertAbout +import com.google.common.truth.Truth.assertThat import com.google.testing.compile.JavaFileObjects import com.google.testing.compile.JavaSourceSubjectFactory.javaSource import com.google.testing.junit.testparameterinjector.TestParameter import com.google.testing.junit.testparameterinjector.TestParameterInjector +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.kspSourcesDir import java.nio.charset.StandardCharsets.UTF_8 import java.nio.file.NoSuchFileException import javax.tools.StandardLocation.CLASS_OUTPUT import kotlin.io.path.readText import kotlin.io.path.toPath +import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi import org.junit.Test import org.junit.runner.RunWith @RunWith(TestParameterInjector::class) -class RetrofitResponseTypeKeepProcessorTest { +class RetrofitResponseTypeKeepProcessorTest( + @param:TestParameter private val useKsp: Boolean, +) { + @OptIn(ExperimentalCompilerApi::class) @Test fun process( @TestParameter( "all-http-methods", "nesting", "kotlin-suspend", - ) name: String + ) name: String, ) { - val service = JavaFileObjects.forSourceString( - "test.Service", - readResourceAsText("$name/Service.java"), - ) + val rules = readResourceAsText("$name/Service.pro") + val generatedPath = "META-INF/proguard/retrofit-response-type-keeper-test.Service.pro" - assertAbout(javaSource()) - .that(service) - .processedWith(RetrofitResponseTypeKeepProcessor()) - .compilesWithoutError() - .and() - .generatesFileNamed( - CLASS_OUTPUT, - "", - "META-INF/proguard/retrofit-response-type-keeper-test.Service.pro", - ).withStringContents( - UTF_8, - readResourceAsText("$name/Service.pro"), + if (useKsp) { + val compilation = KotlinCompilation().apply { + configureKsp { + inheritClassPath = true + symbolProcessorProviders += RetrofitResponseTypeKeepSymbolProcessor.Provider() + sources = listOf( + SourceFile.kotlin( + "Service.kt", + readResourceAsText("$name/Service.kt"), + ), + ) + } + } + val result = compilation.compile() + + assertThat(result.exitCode).isEqualTo(ExitCode.OK) + assertThat(compilation.kspSourcesDir.resolve("resources/$generatedPath").readText()) + .isEqualTo(rules) + } else { + val service = JavaFileObjects.forSourceString( + "test.Service", + readResourceAsText("$name/Service.java"), ) + assertAbout(javaSource()) + .that(service) + .processedWith(RetrofitResponseTypeKeepProcessor()) + .compilesWithoutError() + .and() + .generatesFileNamed( + CLASS_OUTPUT, + "", + generatedPath, + ).withStringContents( + UTF_8, + rules, + ) + } } private companion object { diff --git a/retrofit-response-type-keeper/src/test/resources/all-http-methods/Service.kt b/retrofit-response-type-keeper/src/test/resources/all-http-methods/Service.kt new file mode 100644 index 0000000000..815a724db8 --- /dev/null +++ b/retrofit-response-type-keeper/src/test/resources/all-http-methods/Service.kt @@ -0,0 +1,39 @@ +package test + +import retrofit2.* +import retrofit2.http.* + +internal class DeleteUser +internal class GetUser +internal class HeadUser +internal class HttpUser +internal class OptionsUser +internal class PatchUser +internal class PostUser +internal class PutUser + +internal interface Service { + @DELETE("/") + fun delete(): Call + + @GET("/") + fun get(): Call + + @HEAD("/") + fun head(): Call + + @retrofit2.http.HTTP(method = "CUSTOM", path = "/") + fun http(): Call + + @OPTIONS("/") + fun options(): Call + + @PATCH("/") + fun patch(): Call + + @POST("/") + fun post(): Call + + @PUT("/") + fun put(): Call +} diff --git a/retrofit-response-type-keeper/src/test/resources/kotlin-suspend/Service.kt b/retrofit-response-type-keeper/src/test/resources/kotlin-suspend/Service.kt new file mode 100644 index 0000000000..25f73f42e1 --- /dev/null +++ b/retrofit-response-type-keeper/src/test/resources/kotlin-suspend/Service.kt @@ -0,0 +1,11 @@ +package test + +import retrofit2.* +import retrofit2.http.* + +internal class Body + +internal interface Service { + @GET("/") + suspend fun get(c: Body): Any +} diff --git a/retrofit-response-type-keeper/src/test/resources/nesting/Service.kt b/retrofit-response-type-keeper/src/test/resources/nesting/Service.kt new file mode 100644 index 0000000000..39daba25c4 --- /dev/null +++ b/retrofit-response-type-keeper/src/test/resources/nesting/Service.kt @@ -0,0 +1,13 @@ +package test + +import retrofit2.* +import retrofit2.http.* + +internal class One +internal class Two +internal class Three + +internal interface Service { + @GET("/") + fun get(): Call>> +} From 50e1b4568c3afae34b40ade7227b0b0c3f2b56e6 Mon Sep 17 00:00:00 2001 From: Jake Wharton Date: Thu, 23 Oct 2025 23:47:32 -0400 Subject: [PATCH 03/10] Update .github/renovate.json5 Co-authored-by: Zongle Wang --- .github/renovate.json5 | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 592f220e0b..5fd1a7750e 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -22,12 +22,5 @@ // Only write the major version. extractVersionTemplate: '^(?\\d+)', }, - { - groupName: 'Kotlin and KSP', - matchPackageNames: [ - 'com.google.devtools.ksp{/,}**', - 'org.jetbrains.kotlin:kotlin{/,}**', - ], - }, ] } From 1af22c587b5712b6676f24652075baad95c0ca8b Mon Sep 17 00:00:00 2001 From: Jake Wharton Date: Thu, 23 Oct 2025 23:47:41 -0400 Subject: [PATCH 04/10] Update gradle/libs.versions.toml Co-authored-by: Zongle Wang --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b81417f637..3313dfd2ac 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -34,7 +34,7 @@ kotlin-stdLib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = " kotlin-gradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } kotlin-serializationPlugin = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" } -ksp-api = "com.google.devtools.ksp:symbol-processing-api:2.2.20-2.0.3" +ksp-api = "com.google.devtools.ksp:symbol-processing-api:2.3.0" errorpronePlugin = "net.ltgt.gradle:gradle-errorprone-plugin:4.3.0" errorproneCore = { module = "com.google.errorprone:error_prone_core", version = "2.10.0" } From 4d95d221745b4c8814928895206f75e52f09e753 Mon Sep 17 00:00:00 2001 From: Goooler Date: Fri, 24 Oct 2025 12:23:55 +0800 Subject: [PATCH 05/10] Add headers and cleanups --- retrofit-response-type-keeper/build.gradle | 1 - .../RetrofitResponseTypeKeepProcessor.kt | 4 +-- ...RetrofitResponseTypeKeepSymbolProcessor.kt | 25 +++++++++++++++---- .../src/main/kotlin/retrofit2/keeper/Utils.kt | 20 +++++++++++++-- 4 files changed, 40 insertions(+), 10 deletions(-) diff --git a/retrofit-response-type-keeper/build.gradle b/retrofit-response-type-keeper/build.gradle index 7dbea9aff8..4754a75f6b 100644 --- a/retrofit-response-type-keeper/build.gradle +++ b/retrofit-response-type-keeper/build.gradle @@ -13,7 +13,6 @@ dependencies { testImplementation libs.compileTesting testImplementation libs.kct.core testImplementation libs.kct.ksp - testImplementation libs.ksp.api testImplementation libs.truth testImplementation libs.testParameterInjector testImplementation projects.retrofit diff --git a/retrofit-response-type-keeper/src/main/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepProcessor.kt b/retrofit-response-type-keeper/src/main/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepProcessor.kt index 910290ffe8..09c3468cfd 100644 --- a/retrofit-response-type-keeper/src/main/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepProcessor.kt +++ b/retrofit-response-type-keeper/src/main/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepProcessor.kt @@ -68,11 +68,11 @@ class RetrofitResponseTypeKeepProcessor : AbstractProcessor() { for ((element, referencedTypes) in elementToReferencedTypes) { val typeName = element.qualifiedName.toString() - val rules = processingEnv.filer.createResource(CLASS_OUTPUT, "", outputFile(typeName), element) + val rules = processingEnv.filer.createResource(CLASS_OUTPUT, "", proguardFilePath(typeName), element) rules.openWriter().buffered().use { w -> w.write("# $typeName\n") for (referencedType in referencedTypes.sorted()) { - w.write("$KEEP_RULE_PREFIX $referencedType\n") + w.write(keepRuleForType(referencedType)) } } } diff --git a/retrofit-response-type-keeper/src/main/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepSymbolProcessor.kt b/retrofit-response-type-keeper/src/main/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepSymbolProcessor.kt index 85827d34b7..e1bcfa3734 100644 --- a/retrofit-response-type-keeper/src/main/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepSymbolProcessor.kt +++ b/retrofit-response-type-keeper/src/main/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepSymbolProcessor.kt @@ -1,3 +1,18 @@ +/* + * Copyright (C) 2025 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package retrofit2.keeper import com.google.auto.service.AutoService @@ -41,14 +56,14 @@ class RetrofitResponseTypeKeepSymbolProcessor( elementToReferencedTypes.forEach { (element, referencedTypes) -> val containingFile = element.containingFile ?: return@forEach - val typeName = requireNotNull(element.qualifiedName).asString() + val typeName = element.qualifiedName?.asString() ?: return@forEach val dependencies = Dependencies(aggregating = false, containingFile) - codeGenerator.createNewFile(dependencies, "", outputFile(typeName), "") - .bufferedWriter().use { writer -> - writer.write("# $typeName\n") + codeGenerator.createNewFile(dependencies, "", proguardFilePath(typeName), "") + .bufferedWriter().use { w -> + w.write("# $typeName\n") for (referencedType in referencedTypes.sorted()) { - writer.write("$KEEP_RULE_PREFIX $referencedType\n") + w.write(keepRuleForType(referencedType)) } } } diff --git a/retrofit-response-type-keeper/src/main/kotlin/retrofit2/keeper/Utils.kt b/retrofit-response-type-keeper/src/main/kotlin/retrofit2/keeper/Utils.kt index 62e3a57494..4ae458c612 100644 --- a/retrofit-response-type-keeper/src/main/kotlin/retrofit2/keeper/Utils.kt +++ b/retrofit-response-type-keeper/src/main/kotlin/retrofit2/keeper/Utils.kt @@ -1,3 +1,18 @@ +/* + * Copyright (C) 2025 Square, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package retrofit2.keeper internal val annotationNames = setOf( @@ -11,7 +26,8 @@ internal val annotationNames = setOf( "retrofit2.http.PUT", ) -internal const val KEEP_RULE_PREFIX = "-keep,allowoptimization,allowshrinking,allowobfuscation class" +internal fun keepRuleForType(referencedType: String): String = + "-keep,allowoptimization,allowshrinking,allowobfuscation class $referencedType\n" -internal fun outputFile(typeName: String) = +internal fun proguardFilePath(typeName: String) = "META-INF/proguard/retrofit-response-type-keeper-$typeName.pro" From 362d9762110498dcc81b65d52ee58fc0eb2017c6 Mon Sep 17 00:00:00 2001 From: Goooler Date: Mon, 27 Oct 2025 12:18:16 +0800 Subject: [PATCH 06/10] Use `Generator` enum --- .../RetrofitResponseTypeKeepProcessorTest.kt | 88 +++++++++++-------- 1 file changed, 53 insertions(+), 35 deletions(-) diff --git a/retrofit-response-type-keeper/src/test/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepProcessorTest.kt b/retrofit-response-type-keeper/src/test/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepProcessorTest.kt index 0f39e26692..953c27fa84 100644 --- a/retrofit-response-type-keeper/src/test/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepProcessorTest.kt +++ b/retrofit-response-type-keeper/src/test/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepProcessorTest.kt @@ -37,9 +37,8 @@ import org.junit.runner.RunWith @RunWith(TestParameterInjector::class) class RetrofitResponseTypeKeepProcessorTest( - @param:TestParameter private val useKsp: Boolean, + @param:TestParameter private val generator: Generator, ) { - @OptIn(ExperimentalCompilerApi::class) @Test fun process( @TestParameter( @@ -49,44 +48,63 @@ class RetrofitResponseTypeKeepProcessorTest( ) name: String, ) { val rules = readResourceAsText("$name/Service.pro") - val generatedPath = "META-INF/proguard/retrofit-response-type-keeper-test.Service.pro" - if (useKsp) { - val compilation = KotlinCompilation().apply { - configureKsp { - inheritClassPath = true - symbolProcessorProviders += RetrofitResponseTypeKeepSymbolProcessor.Provider() - sources = listOf( - SourceFile.kotlin( - "Service.kt", - readResourceAsText("$name/Service.kt"), - ), + when (generator) { + Generator.Apt -> { + val source = readResourceAsText("$name/Service.java") + generator.validate(source, rules) + } + + Generator.Ksp -> { + val source = readResourceAsText("$name/Service.kt") + generator.validate(source, rules) + } + } + } + + enum class Generator { + Apt { + override fun validate(source: String, rules: String) { + val service = JavaFileObjects.forSourceString("test.Service", source) + assertAbout(javaSource()) + .that(service) + .processedWith(RetrofitResponseTypeKeepProcessor()) + .compilesWithoutError() + .and() + .generatesFileNamed( + CLASS_OUTPUT, + "", + GENERATED_PATH, + ).withStringContents( + UTF_8, + rules, ) + } + }, + + Ksp { + @OptIn(ExperimentalCompilerApi::class) + override fun validate(source: String, rules: String) { + val compilation = KotlinCompilation().apply { + configureKsp { + inheritClassPath = true + symbolProcessorProviders += RetrofitResponseTypeKeepSymbolProcessor.Provider() + sources = listOf(SourceFile.new("Service.kt", source)) + } } + val result = compilation.compile() + + assertThat(result.exitCode).isEqualTo(ExitCode.OK) + assertThat(compilation.kspSourcesDir.resolve("resources/$GENERATED_PATH").readText()) + .isEqualTo(rules) } - val result = compilation.compile() + }, + ; + + abstract fun validate(source: String, rules: String) - assertThat(result.exitCode).isEqualTo(ExitCode.OK) - assertThat(compilation.kspSourcesDir.resolve("resources/$generatedPath").readText()) - .isEqualTo(rules) - } else { - val service = JavaFileObjects.forSourceString( - "test.Service", - readResourceAsText("$name/Service.java"), - ) - assertAbout(javaSource()) - .that(service) - .processedWith(RetrofitResponseTypeKeepProcessor()) - .compilesWithoutError() - .and() - .generatesFileNamed( - CLASS_OUTPUT, - "", - generatedPath, - ).withStringContents( - UTF_8, - rules, - ) + private companion object { + const val GENERATED_PATH = "META-INF/proguard/retrofit-response-type-keeper-test.Service.pro" } } From 5b6c1b5216294ed6a5442cc1ed5df2b98ab1487b Mon Sep 17 00:00:00 2001 From: Goooler Date: Mon, 27 Oct 2025 12:44:16 +0800 Subject: [PATCH 07/10] Revert "Convert RetrofitResponseTypeKeepProcessorTest to parametermized tests" --- .../RetrofitResponseTypeKeepProcessorTest.kt | 197 ++++++++++++++++-- .../resources/all-http-methods/Service.java | 24 --- .../resources/all-http-methods/Service.kt | 39 ---- .../resources/all-http-methods/Service.pro | 10 - .../resources/kotlin-suspend/Service.java | 11 - .../test/resources/kotlin-suspend/Service.kt | 11 - .../test/resources/kotlin-suspend/Service.pro | 3 - .../src/test/resources/nesting/Service.java | 12 -- .../src/test/resources/nesting/Service.kt | 13 -- .../src/test/resources/nesting/Service.pro | 5 - 10 files changed, 176 insertions(+), 149 deletions(-) delete mode 100644 retrofit-response-type-keeper/src/test/resources/all-http-methods/Service.java delete mode 100644 retrofit-response-type-keeper/src/test/resources/all-http-methods/Service.kt delete mode 100644 retrofit-response-type-keeper/src/test/resources/all-http-methods/Service.pro delete mode 100644 retrofit-response-type-keeper/src/test/resources/kotlin-suspend/Service.java delete mode 100644 retrofit-response-type-keeper/src/test/resources/kotlin-suspend/Service.kt delete mode 100644 retrofit-response-type-keeper/src/test/resources/kotlin-suspend/Service.pro delete mode 100644 retrofit-response-type-keeper/src/test/resources/nesting/Service.java delete mode 100644 retrofit-response-type-keeper/src/test/resources/nesting/Service.kt delete mode 100644 retrofit-response-type-keeper/src/test/resources/nesting/Service.pro diff --git a/retrofit-response-type-keeper/src/test/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepProcessorTest.kt b/retrofit-response-type-keeper/src/test/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepProcessorTest.kt index 953c27fa84..7d65a7856b 100644 --- a/retrofit-response-type-keeper/src/test/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepProcessorTest.kt +++ b/retrofit-response-type-keeper/src/test/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepProcessorTest.kt @@ -27,10 +27,7 @@ import com.tschuchort.compiletesting.SourceFile import com.tschuchort.compiletesting.configureKsp import com.tschuchort.compiletesting.kspSourcesDir import java.nio.charset.StandardCharsets.UTF_8 -import java.nio.file.NoSuchFileException import javax.tools.StandardLocation.CLASS_OUTPUT -import kotlin.io.path.readText -import kotlin.io.path.toPath import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi import org.junit.Test import org.junit.runner.RunWith @@ -40,23 +37,189 @@ class RetrofitResponseTypeKeepProcessorTest( @param:TestParameter private val generator: Generator, ) { @Test - fun process( - @TestParameter( - "all-http-methods", - "nesting", - "kotlin-suspend", - ) name: String, - ) { - val rules = readResourceAsText("$name/Service.pro") + fun allHttpMethods() { + val rules = """ + |# test.Service + |-keep,allowoptimization,allowshrinking,allowobfuscation class retrofit2.Call + |-keep,allowoptimization,allowshrinking,allowobfuscation class test.DeleteUser + |-keep,allowoptimization,allowshrinking,allowobfuscation class test.GetUser + |-keep,allowoptimization,allowshrinking,allowobfuscation class test.HeadUser + |-keep,allowoptimization,allowshrinking,allowobfuscation class test.HttpUser + |-keep,allowoptimization,allowshrinking,allowobfuscation class test.OptionsUser + |-keep,allowoptimization,allowshrinking,allowobfuscation class test.PatchUser + |-keep,allowoptimization,allowshrinking,allowobfuscation class test.PostUser + |-keep,allowoptimization,allowshrinking,allowobfuscation class test.PutUser + | + """.trimMargin() when (generator) { Generator.Apt -> { - val source = readResourceAsText("$name/Service.java") + val source = """ + package test; + + import retrofit2.*; + import retrofit2.http.*; + + class DeleteUser {} + class GetUser {} + class HeadUser {} + class HttpUser {} + class OptionsUser {} + class PatchUser {} + class PostUser {} + class PutUser {} + + interface Service { + @DELETE("/") Call delete(); + @GET("/") Call get(); + @HEAD("/") Call head(); + @HTTP(method = "CUSTOM", path = "/") Call http(); + @OPTIONS("/") Call options(); + @PATCH("/") Call patch(); + @POST("/") Call post(); + @PUT("/") Call put(); + } + """.trimIndent() + generator.validate(source, rules) + } + + Generator.Ksp -> { + val source = """ + package test + + import retrofit2.* + import retrofit2.http.* + + class DeleteUser + class GetUser + class HeadUser + class HttpUser + class OptionsUser + class PatchUser + class PostUser + class PutUser + + interface Service { + @DELETE("/") + fun delete(): Call + + @GET("/") + fun get(): Call + + @HEAD("/") + fun head(): Call + + @retrofit2.http.HTTP(method = "CUSTOM", path = "/") + fun http(): Call + + @OPTIONS("/") + fun options(): Call + + @PATCH("/") + fun patch(): Call + + @POST("/") + fun post(): Call + + @PUT("/") + fun put(): Call + } + """.trimIndent() + generator.validate(source, rules) + } + } + } + + @Test + fun nesting() { + val rules = """ + |# test.Service + |-keep,allowoptimization,allowshrinking,allowobfuscation class retrofit2.Call + |-keep,allowoptimization,allowshrinking,allowobfuscation class test.One + |-keep,allowoptimization,allowshrinking,allowobfuscation class test.Three + |-keep,allowoptimization,allowshrinking,allowobfuscation class test.Two + | + """.trimMargin() + when (generator) { + Generator.Apt -> { + val source = """ + package test; + + import retrofit2.*; + import retrofit2.http.*; + + class One {} + class Two {} + class Three {} + + interface Service { + @GET("/") Call>> get(); + } + """.trimIndent() generator.validate(source, rules) } Generator.Ksp -> { - val source = readResourceAsText("$name/Service.kt") + val source = """ + package test + + import retrofit2.* + import retrofit2.http.* + + internal class One + internal class Two + internal class Three + + internal interface Service { + @GET("/") + fun get(): Call>> + } + """.trimIndent() + generator.validate(source, rules) + } + } + } + + @Test + fun kotlinSuspend() { + val rules = """ + |# test.Service + |-keep,allowoptimization,allowshrinking,allowobfuscation class java.lang.Object + |-keep,allowoptimization,allowshrinking,allowobfuscation class test.Body + | + """.trimMargin() + when (generator) { + Generator.Apt -> { + val source = """ + package test; + + import kotlin.coroutines.Continuation; + import retrofit2.*; + import retrofit2.http.*; + + class Body {} + + interface Service { + @GET("/") Object get(Continuation c); + } + """.trimIndent() + generator.validate(source, rules) + } + + Generator.Ksp -> { + val source = """ + package test + + import retrofit2.* + import retrofit2.http.* + + internal class Body + + internal interface Service { + @GET("/") + suspend fun get(c: Body): Any + } + """.trimIndent() generator.validate(source, rules) } } @@ -107,12 +270,4 @@ class RetrofitResponseTypeKeepProcessorTest( const val GENERATED_PATH = "META-INF/proguard/retrofit-response-type-keeper-test.Service.pro" } } - - private companion object { - fun readResourceAsText(name: String): String { - val resource = this::class.java.classLoader.getResource(name) - ?: throw NoSuchFileException("Resource $name not found.") - return resource.toURI().toPath().readText() - } - } } diff --git a/retrofit-response-type-keeper/src/test/resources/all-http-methods/Service.java b/retrofit-response-type-keeper/src/test/resources/all-http-methods/Service.java deleted file mode 100644 index 93ad79b7b5..0000000000 --- a/retrofit-response-type-keeper/src/test/resources/all-http-methods/Service.java +++ /dev/null @@ -1,24 +0,0 @@ -package test; - -import retrofit2.*; -import retrofit2.http.*; - -class DeleteUser {} -class GetUser {} -class HeadUser {} -class HttpUser {} -class OptionsUser {} -class PatchUser {} -class PostUser {} -class PutUser {} - -interface Service { - @DELETE("/") Call delete(); - @GET("/") Call get(); - @HEAD("/") Call head(); - @HTTP(method = "CUSTOM", path = "/") Call http(); - @OPTIONS("/") Call options(); - @PATCH("/") Call patch(); - @POST("/") Call post(); - @PUT("/") Call put(); -} diff --git a/retrofit-response-type-keeper/src/test/resources/all-http-methods/Service.kt b/retrofit-response-type-keeper/src/test/resources/all-http-methods/Service.kt deleted file mode 100644 index 815a724db8..0000000000 --- a/retrofit-response-type-keeper/src/test/resources/all-http-methods/Service.kt +++ /dev/null @@ -1,39 +0,0 @@ -package test - -import retrofit2.* -import retrofit2.http.* - -internal class DeleteUser -internal class GetUser -internal class HeadUser -internal class HttpUser -internal class OptionsUser -internal class PatchUser -internal class PostUser -internal class PutUser - -internal interface Service { - @DELETE("/") - fun delete(): Call - - @GET("/") - fun get(): Call - - @HEAD("/") - fun head(): Call - - @retrofit2.http.HTTP(method = "CUSTOM", path = "/") - fun http(): Call - - @OPTIONS("/") - fun options(): Call - - @PATCH("/") - fun patch(): Call - - @POST("/") - fun post(): Call - - @PUT("/") - fun put(): Call -} diff --git a/retrofit-response-type-keeper/src/test/resources/all-http-methods/Service.pro b/retrofit-response-type-keeper/src/test/resources/all-http-methods/Service.pro deleted file mode 100644 index bc42931349..0000000000 --- a/retrofit-response-type-keeper/src/test/resources/all-http-methods/Service.pro +++ /dev/null @@ -1,10 +0,0 @@ -# test.Service --keep,allowoptimization,allowshrinking,allowobfuscation class retrofit2.Call --keep,allowoptimization,allowshrinking,allowobfuscation class test.DeleteUser --keep,allowoptimization,allowshrinking,allowobfuscation class test.GetUser --keep,allowoptimization,allowshrinking,allowobfuscation class test.HeadUser --keep,allowoptimization,allowshrinking,allowobfuscation class test.HttpUser --keep,allowoptimization,allowshrinking,allowobfuscation class test.OptionsUser --keep,allowoptimization,allowshrinking,allowobfuscation class test.PatchUser --keep,allowoptimization,allowshrinking,allowobfuscation class test.PostUser --keep,allowoptimization,allowshrinking,allowobfuscation class test.PutUser diff --git a/retrofit-response-type-keeper/src/test/resources/kotlin-suspend/Service.java b/retrofit-response-type-keeper/src/test/resources/kotlin-suspend/Service.java deleted file mode 100644 index 6cd8250020..0000000000 --- a/retrofit-response-type-keeper/src/test/resources/kotlin-suspend/Service.java +++ /dev/null @@ -1,11 +0,0 @@ -package test; - -import kotlin.coroutines.Continuation; -import retrofit2.*; -import retrofit2.http.*; - -class Body {} - -interface Service { - @GET("/") Object get(Continuation c); -} diff --git a/retrofit-response-type-keeper/src/test/resources/kotlin-suspend/Service.kt b/retrofit-response-type-keeper/src/test/resources/kotlin-suspend/Service.kt deleted file mode 100644 index 25f73f42e1..0000000000 --- a/retrofit-response-type-keeper/src/test/resources/kotlin-suspend/Service.kt +++ /dev/null @@ -1,11 +0,0 @@ -package test - -import retrofit2.* -import retrofit2.http.* - -internal class Body - -internal interface Service { - @GET("/") - suspend fun get(c: Body): Any -} diff --git a/retrofit-response-type-keeper/src/test/resources/kotlin-suspend/Service.pro b/retrofit-response-type-keeper/src/test/resources/kotlin-suspend/Service.pro deleted file mode 100644 index f16efdb512..0000000000 --- a/retrofit-response-type-keeper/src/test/resources/kotlin-suspend/Service.pro +++ /dev/null @@ -1,3 +0,0 @@ -# test.Service --keep,allowoptimization,allowshrinking,allowobfuscation class java.lang.Object --keep,allowoptimization,allowshrinking,allowobfuscation class test.Body diff --git a/retrofit-response-type-keeper/src/test/resources/nesting/Service.java b/retrofit-response-type-keeper/src/test/resources/nesting/Service.java deleted file mode 100644 index 7c4f2e5959..0000000000 --- a/retrofit-response-type-keeper/src/test/resources/nesting/Service.java +++ /dev/null @@ -1,12 +0,0 @@ -package test; - -import retrofit2.*; -import retrofit2.http.*; - -class One {} -class Two {} -class Three {} - -interface Service { - @GET("/") Call>> get(); -} diff --git a/retrofit-response-type-keeper/src/test/resources/nesting/Service.kt b/retrofit-response-type-keeper/src/test/resources/nesting/Service.kt deleted file mode 100644 index 39daba25c4..0000000000 --- a/retrofit-response-type-keeper/src/test/resources/nesting/Service.kt +++ /dev/null @@ -1,13 +0,0 @@ -package test - -import retrofit2.* -import retrofit2.http.* - -internal class One -internal class Two -internal class Three - -internal interface Service { - @GET("/") - fun get(): Call>> -} diff --git a/retrofit-response-type-keeper/src/test/resources/nesting/Service.pro b/retrofit-response-type-keeper/src/test/resources/nesting/Service.pro deleted file mode 100644 index 331a97c51b..0000000000 --- a/retrofit-response-type-keeper/src/test/resources/nesting/Service.pro +++ /dev/null @@ -1,5 +0,0 @@ -# test.Service --keep,allowoptimization,allowshrinking,allowobfuscation class retrofit2.Call --keep,allowoptimization,allowshrinking,allowobfuscation class test.One --keep,allowoptimization,allowshrinking,allowobfuscation class test.Three --keep,allowoptimization,allowshrinking,allowobfuscation class test.Two From 8a085073246ec8e855b26a6eb3e9d5f01269805b Mon Sep 17 00:00:00 2001 From: Goooler Date: Mon, 27 Oct 2025 12:51:41 +0800 Subject: [PATCH 08/10] Rename `Generator` to `Processor` --- .../RetrofitResponseTypeKeepProcessorTest.kt | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/retrofit-response-type-keeper/src/test/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepProcessorTest.kt b/retrofit-response-type-keeper/src/test/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepProcessorTest.kt index 7d65a7856b..7e2a1ab5b8 100644 --- a/retrofit-response-type-keeper/src/test/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepProcessorTest.kt +++ b/retrofit-response-type-keeper/src/test/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepProcessorTest.kt @@ -34,7 +34,7 @@ import org.junit.runner.RunWith @RunWith(TestParameterInjector::class) class RetrofitResponseTypeKeepProcessorTest( - @param:TestParameter private val generator: Generator, + @param:TestParameter private val processor: Processor, ) { @Test fun allHttpMethods() { @@ -52,8 +52,8 @@ class RetrofitResponseTypeKeepProcessorTest( | """.trimMargin() - when (generator) { - Generator.Apt -> { + when (processor) { + Processor.Apt -> { val source = """ package test; @@ -80,10 +80,10 @@ class RetrofitResponseTypeKeepProcessorTest( @PUT("/") Call put(); } """.trimIndent() - generator.validate(source, rules) + processor.validate(source, rules) } - Generator.Ksp -> { + Processor.Ksp -> { val source = """ package test @@ -125,7 +125,7 @@ class RetrofitResponseTypeKeepProcessorTest( fun put(): Call } """.trimIndent() - generator.validate(source, rules) + processor.validate(source, rules) } } } @@ -140,8 +140,8 @@ class RetrofitResponseTypeKeepProcessorTest( |-keep,allowoptimization,allowshrinking,allowobfuscation class test.Two | """.trimMargin() - when (generator) { - Generator.Apt -> { + when (processor) { + Processor.Apt -> { val source = """ package test; @@ -156,10 +156,10 @@ class RetrofitResponseTypeKeepProcessorTest( @GET("/") Call>> get(); } """.trimIndent() - generator.validate(source, rules) + processor.validate(source, rules) } - Generator.Ksp -> { + Processor.Ksp -> { val source = """ package test @@ -175,7 +175,7 @@ class RetrofitResponseTypeKeepProcessorTest( fun get(): Call>> } """.trimIndent() - generator.validate(source, rules) + processor.validate(source, rules) } } } @@ -188,8 +188,8 @@ class RetrofitResponseTypeKeepProcessorTest( |-keep,allowoptimization,allowshrinking,allowobfuscation class test.Body | """.trimMargin() - when (generator) { - Generator.Apt -> { + when (processor) { + Processor.Apt -> { val source = """ package test; @@ -203,10 +203,10 @@ class RetrofitResponseTypeKeepProcessorTest( @GET("/") Object get(Continuation c); } """.trimIndent() - generator.validate(source, rules) + processor.validate(source, rules) } - Generator.Ksp -> { + Processor.Ksp -> { val source = """ package test @@ -220,12 +220,12 @@ class RetrofitResponseTypeKeepProcessorTest( suspend fun get(c: Body): Any } """.trimIndent() - generator.validate(source, rules) + processor.validate(source, rules) } } } - enum class Generator { + enum class Processor { Apt { override fun validate(source: String, rules: String) { val service = JavaFileObjects.forSourceString("test.Service", source) From b55af1d2cd61f2bc5e2513bc8dcb5cc64fed03c9 Mon Sep 17 00:00:00 2001 From: Goooler Date: Mon, 27 Oct 2025 15:57:53 +0800 Subject: [PATCH 09/10] Update description --- retrofit-response-type-keeper/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/retrofit-response-type-keeper/README.md b/retrofit-response-type-keeper/README.md index 099d5ee1e8..7d786dca2b 100644 --- a/retrofit-response-type-keeper/README.md +++ b/retrofit-response-type-keeper/README.md @@ -27,21 +27,21 @@ annotationProcessor 'com.squareup.retrofit2:response-type-keeper:' ``` Or Gradle Kotlin projects with ```groovy -kapt 'com.squareup.retrofit2:response-type-keeper:' +ksp 'com.squareup.retrofit2:response-type-keeper:' ``` For other build systems, the `com.squareup.retrofit2:response-type-keeper` needs added to the Java compiler `-processor` classpath. For the example above, the annotation processor's generated file would contain -``` --keep com.example.User +```proguard +-keep,allowoptimization,allowshrinking,allowobfuscation class com.example.User ``` This works for nested generics, such as `Call>`, which would produce: -``` --keep com.example.ApiResponse --keep com.example.User +```proguard +-keep,allowoptimization,allowshrinking,allowobfuscation class com.example.ApiResponse +-keep,allowoptimization,allowshrinking,allowobfuscation class com.example.User ``` It also works on Kotlin `suspend` functions which turn into a type like From 97bf8f8a6d4bd008e0325990ce015be41e0b0ee4 Mon Sep 17 00:00:00 2001 From: Goooler Date: Thu, 6 Nov 2025 13:29:29 +0800 Subject: [PATCH 10/10] Fix merge --- .../keeper/RetrofitResponseTypeKeepSymbolProcessor.kt | 3 --- .../com.google.devtools.ksp.processing.SymbolProcessorProvider | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) create mode 100644 retrofit-response-type-keeper/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider diff --git a/retrofit-response-type-keeper/src/main/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepSymbolProcessor.kt b/retrofit-response-type-keeper/src/main/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepSymbolProcessor.kt index e1bcfa3734..d33123ecad 100644 --- a/retrofit-response-type-keeper/src/main/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepSymbolProcessor.kt +++ b/retrofit-response-type-keeper/src/main/kotlin/retrofit2/keeper/RetrofitResponseTypeKeepSymbolProcessor.kt @@ -15,7 +15,6 @@ */ package retrofit2.keeper -import com.google.auto.service.AutoService import com.google.devtools.ksp.processing.CodeGenerator import com.google.devtools.ksp.processing.Dependencies import com.google.devtools.ksp.processing.Resolver @@ -86,8 +85,6 @@ class RetrofitResponseTypeKeepSymbolProcessor( } } - @Suppress("unused") // Used in service file. - @AutoService(SymbolProcessorProvider::class) class Provider : SymbolProcessorProvider { override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor = RetrofitResponseTypeKeepSymbolProcessor(environment) diff --git a/retrofit-response-type-keeper/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/retrofit-response-type-keeper/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider new file mode 100644 index 0000000000..4d7bb0d66a --- /dev/null +++ b/retrofit-response-type-keeper/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider @@ -0,0 +1 @@ +retrofit2.keeper.RetrofitResponseTypeKeepSymbolProcessor$Provider