Skip to content

Commit f44e5a6

Browse files
authored
Merge pull request #20217 from wordpress-mobile/migrate_processors_to_ksp
Migrate `:libs:processors` to `ksp`
2 parents efb739f + 3df71cf commit f44e5a6

File tree

8 files changed

+142
-127
lines changed

8 files changed

+142
-127
lines changed

WordPress/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ plugins {
1212
id "com.google.gms.google-services"
1313
id "com.google.dagger.hilt.android"
1414
id "org.jetbrains.kotlinx.kover"
15+
id "com.google.devtools.ksp"
1516
}
1617

1718
sentry {
@@ -345,7 +346,7 @@ dependencies {
345346
implementation 'androidx.webkit:webkit:1.10.0'
346347
implementation "androidx.navigation:navigation-compose:$androidxComposeNavigationVersion"
347348
compileOnly project(path: ':libs:annotations')
348-
kapt project(':libs:processors')
349+
ksp project(':libs:processors')
349350
implementation (project(path:':libs:networking')) {
350351
exclude group: "com.android.volley"
351352
exclude group: 'org.wordpress', module: 'utils'

build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ plugins {
99
id "com.android.library" apply false
1010
id 'com.google.gms.google-services' apply false
1111
id "org.jetbrains.kotlin.plugin.parcelize" apply false
12+
id "com.google.devtools.ksp" apply false
1213
}
1314

1415
ext {
@@ -43,7 +44,7 @@ ext {
4344
androidxArchCoreVersion = '2.2.0'
4445
androidxCameraVersion = '1.2.3'
4546
androidxComposeBomVersion = '2023.10.00'
46-
androidxComposeCompilerVersion = '1.5.3'
47+
androidxComposeCompilerVersion = '1.5.9'
4748
androidxComposeNavigationVersion = '2.7.6'
4849
androidxCardviewVersion = '1.0.0'
4950
androidxConstraintlayoutVersion = '2.1.4'

libs/processors/build.gradle

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
plugins {
22
id "org.jetbrains.kotlin.jvm"
3-
id "org.jetbrains.kotlin.kapt"
43
id "org.jetbrains.kotlinx.kover"
54
}
65

@@ -10,11 +9,13 @@ targetCompatibility = JavaVersion.VERSION_1_8
109
dependencies {
1110
implementation project(":libs:annotations")
1211

13-
implementation "com.google.auto.service:auto-service:$googleAutoServiceVersion"
14-
kapt "com.google.auto.service:auto-service:$googleAutoServiceVersion"
1512
implementation "com.squareup:kotlinpoet:$squareupKotlinPoetVersion"
13+
implementation "com.squareup:kotlinpoet-ksp:$squareupKotlinPoetVersion"
14+
implementation "com.google.devtools.ksp:symbol-processing-api:$gradle.ext.kspVersion"
1615

17-
testImplementation "com.github.tschuchortdev:kotlin-compile-testing:1.5.0"
16+
def kctVersion = "1.5.0"
17+
testImplementation "com.github.tschuchortdev:kotlin-compile-testing:$kctVersion"
18+
testImplementation "com.github.tschuchortdev:kotlin-compile-testing-ksp:$kctVersion"
1819
testImplementation "junit:junit:$junitVersion"
1920
testImplementation "org.assertj:assertj-core:$assertjVersion"
2021
testImplementation "org.jetbrains.kotlin:kotlin-reflect:$gradle.ext.kotlinVersion"
Lines changed: 110 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,128 +1,131 @@
1+
@file:OptIn(KspExperimental::class)
2+
3+
14
package org.wordpress.android.processor
25

3-
import com.google.auto.service.AutoService
4-
import com.squareup.kotlinpoet.DelicateKotlinPoetApi
5-
import com.squareup.kotlinpoet.TypeName
6-
import com.squareup.kotlinpoet.asTypeName
6+
import com.google.devtools.ksp.KspExperimental
7+
import com.google.devtools.ksp.containingFile
8+
import com.google.devtools.ksp.getAnnotationsByType
9+
import com.google.devtools.ksp.processing.CodeGenerator
10+
import com.google.devtools.ksp.processing.Resolver
11+
import com.google.devtools.ksp.processing.SymbolProcessor
12+
import com.google.devtools.ksp.symbol.KSAnnotated
13+
import com.google.devtools.ksp.symbol.KSClassDeclaration
14+
import com.squareup.kotlinpoet.ksp.toTypeName
15+
import com.squareup.kotlinpoet.ksp.writeTo
716
import org.wordpress.android.annotation.Experiment
817
import org.wordpress.android.annotation.Feature
9-
import org.wordpress.android.annotation.FeatureInDevelopment
1018
import org.wordpress.android.annotation.RemoteFieldDefaultGenerater
11-
import java.io.File
12-
import javax.annotation.processing.AbstractProcessor
13-
import javax.annotation.processing.Processor
14-
import javax.annotation.processing.RoundEnvironment
15-
import javax.annotation.processing.SupportedAnnotationTypes
16-
import javax.annotation.processing.SupportedSourceVersion
17-
import javax.lang.model.SourceVersion
18-
import javax.lang.model.element.TypeElement
19-
import javax.tools.Diagnostic.Kind
20-
21-
@AutoService(Processor::class) // For registering the service
22-
@SupportedSourceVersion(SourceVersion.RELEASE_8) // to support Java 8
23-
@SupportedAnnotationTypes(
24-
"org.wordpress.android.annotation.Experiment",
25-
"org.wordpress.android.annotation.Feature",
26-
"org.wordpress.android.annotation.FeatureInDevelopment",
27-
"org.wordpress.android.annotation.RemoteFieldDefaultGenerater"
28-
)
29-
class RemoteConfigProcessor : AbstractProcessor() {
30-
@OptIn(DelicateKotlinPoetApi::class)
31-
@Suppress("DEPRECATION")
32-
override fun process(p0: MutableSet<out TypeElement>?, roundEnvironment: RoundEnvironment?): Boolean {
33-
val experiments = roundEnvironment?.getElementsAnnotatedWith(Experiment::class.java)?.map { element ->
34-
val annotation = element.getAnnotation(Experiment::class.java)
35-
annotation.remoteField to annotation.defaultVariant
36-
} ?: listOf()
37-
val remoteFeatureNames = mutableListOf<TypeName>()
38-
val features = roundEnvironment?.getElementsAnnotatedWith(Feature::class.java)?.map { element ->
39-
val annotation = element.getAnnotation(Feature::class.java)
40-
remoteFeatureNames.add(element.asType().asTypeName())
41-
annotation.remoteField to annotation.defaultValue.toString()
42-
} ?: listOf()
43-
val remoteFields = roundEnvironment?.getElementsAnnotatedWith(RemoteFieldDefaultGenerater::class.java)
44-
?.map { element ->
45-
val annotation = element.getAnnotation(RemoteFieldDefaultGenerater::class.java)
46-
annotation.remoteField to annotation.defaultValue
47-
} ?: listOf()
48-
val featuresInDevelopment = roundEnvironment?.getElementsAnnotatedWith(FeatureInDevelopment::class.java)
49-
?.map { element ->
50-
element.asType().toString()
51-
} ?: listOf()
52-
return if (experiments.isNotEmpty() || features.isNotEmpty()) {
53-
generateRemoteFieldConfigDefaults(remoteFields.toMap())
54-
generateRemoteFeatureConfigDefaults((experiments + features).toMap())
55-
generateRemoteFeatureConfigCheck(remoteFeatureNames)
56-
generateFeaturesInDevelopment(featuresInDevelopment)
57-
true
58-
} else {
59-
false
19+
20+
@OptIn(KspExperimental::class)
21+
class RemoteConfigProcessor(
22+
private val codeGenerator: CodeGenerator,
23+
) : SymbolProcessor {
24+
/**
25+
* In the case of this processor, we only one need round. Generated files do not depend on each other
26+
* or any other processor.
27+
*
28+
* See: https://github.com/google/ksp/issues/797#issuecomment-1041127747
29+
* Also: https://github.com/google/ksp/blob/a0cd7774a7f65cec45a50ecc8960ef5e4d47fc21/examples/playground/test-processor/src/main/kotlin/TestProcessor.kt#L20
30+
*/
31+
private var invoked = false
32+
33+
override fun process(resolver: Resolver): List<KSAnnotated> {
34+
if (invoked) {
35+
return emptyList()
6036
}
37+
38+
val remoteFeatures = resolver.getSymbolsWithAnnotation("org.wordpress.android.annotation.Feature")
39+
.toList()
40+
41+
generateRemoteFeatureConfigDefaults(resolver, remoteFeatures)
42+
generateRemoteFieldsConfigDefaults(resolver)
43+
generateFeaturesInDevelopment(resolver)
44+
generateRemoteFeatureConfigCheck(remoteFeatures)
45+
46+
invoked = true
47+
return emptyList()
6148
}
6249

63-
@Suppress("TooGenericExceptionCaught", "SwallowedException")
64-
private fun generateRemoteFeatureConfigDefaults(
65-
remoteConfigDefaults: Map<String, String>
66-
) {
67-
try {
68-
val fileContent = RemoteFeatureConfigDefaultsBuilder(remoteConfigDefaults).getContent()
69-
70-
val kaptKotlinGeneratedDir = processingEnv.options[KAPT_KOTLIN_GENERATED_OPTION_NAME]
71-
fileContent.writeTo(File(kaptKotlinGeneratedDir))
72-
} catch (e: Exception) {
73-
processingEnv.messager.printMessage(Kind.ERROR, "Failed to generate remote feature config defaults")
50+
private fun generateRemoteFeatureConfigDefaults(resolver: Resolver, remoteFeatures: List<KSAnnotated>) {
51+
val experiments = resolver.getSymbolsWithAnnotation("org.wordpress.android.annotation.Experiment")
52+
.toList()
53+
54+
val defaults = (remoteFeatures + experiments)
55+
.map { element: KSAnnotated ->
56+
val featuresDefaults = element.getAnnotationsByType(Feature::class)
57+
.toList().associate { annotation ->
58+
annotation.remoteField to annotation.defaultValue.toString()
59+
}
60+
val experimentsDefaults = element.getAnnotationsByType(Experiment::class).toList()
61+
.toList().associate { annotation ->
62+
annotation.remoteField to annotation.defaultVariant
63+
}
64+
featuresDefaults + experimentsDefaults
65+
}.flatMap { it.toList() }
66+
.toMap()
67+
68+
if (defaults.isNotEmpty()) {
69+
RemoteFeatureConfigDefaultsBuilder(defaults).getContent()
70+
.writeTo(
71+
codeGenerator,
72+
aggregating = true,
73+
originatingKSFiles = remoteFeatures.map { it.containingFile!! }
74+
)
7475
}
7576
}
7677

77-
@Suppress("TooGenericExceptionCaught", "SwallowedException")
78-
private fun generateRemoteFieldConfigDefaults(
79-
remoteConfigDefaults: Map<String, String>
80-
) {
81-
try {
82-
val fileContent = RemoteFieldConfigDefaultsBuilder(remoteConfigDefaults).getContent()
83-
84-
val kaptKotlinGeneratedDir = processingEnv.options[KAPT_KOTLIN_GENERATED_OPTION_NAME]
85-
fileContent.writeTo(File(kaptKotlinGeneratedDir))
86-
} catch (e: Exception) {
87-
processingEnv.messager.printMessage(Kind.ERROR, "Failed to generate remote feature config defaults")
78+
private fun generateRemoteFieldsConfigDefaults(resolver: Resolver) {
79+
val remoteFields =
80+
resolver.getSymbolsWithAnnotation("org.wordpress.android.annotation.RemoteFieldDefaultGenerater")
81+
.toList()
82+
val remoteFieldDefaults = remoteFields
83+
.associate { element: KSAnnotated ->
84+
element.getAnnotationsByType(RemoteFieldDefaultGenerater::class)
85+
.toList()
86+
.first()
87+
.let { annotation ->
88+
annotation.remoteField to annotation.defaultValue
89+
}
90+
}
91+
92+
if(remoteFieldDefaults.isNotEmpty()) {
93+
RemoteFieldConfigDefaultsBuilder(remoteFieldDefaults).getContent()
94+
.writeTo(
95+
codeGenerator,
96+
aggregating = true,
97+
originatingKSFiles = remoteFields.map { it.containingFile!! }
98+
)
8899
}
89100
}
90101

91-
@Suppress("TooGenericExceptionCaught")
92-
private fun generateRemoteFeatureConfigCheck(
93-
remoteFeatureNames: List<TypeName>
94-
) {
95-
try {
96-
val fileContent = RemoteFeatureConfigCheckBuilder(remoteFeatureNames).getContent()
97-
98-
val kaptKotlinGeneratedDir = processingEnv.options[KAPT_KOTLIN_GENERATED_OPTION_NAME]
99-
fileContent.writeTo(File(kaptKotlinGeneratedDir))
100-
} catch (e: Exception) {
101-
processingEnv.messager.printMessage(
102-
Kind.ERROR,
103-
"Failed to generate remote feature config check: $e"
104-
)
102+
private fun generateFeaturesInDevelopment(resolver: Resolver) {
103+
val featuresInDevelopment =
104+
resolver.getSymbolsWithAnnotation("org.wordpress.android.annotation.FeatureInDevelopment")
105+
.filterIsInstance<KSClassDeclaration>()
106+
.toList()
107+
val featuresInDevelopmentDefaults = featuresInDevelopment
108+
.map { it.simpleName.asString() }
109+
110+
if(featuresInDevelopmentDefaults.isNotEmpty()) {
111+
FeaturesInDevelopmentDefaultsBuilder(featuresInDevelopmentDefaults).getContent()
112+
.writeTo(
113+
codeGenerator,
114+
aggregating = true,
115+
originatingKSFiles = featuresInDevelopment.map { it.containingFile!! }
116+
)
105117
}
106118
}
107119

108-
@Suppress("TooGenericExceptionCaught")
109-
private fun generateFeaturesInDevelopment(
110-
remoteFeatureNames: List<String>
111-
) {
112-
try {
113-
val fileContent = FeaturesInDevelopmentDefaultsBuilder(remoteFeatureNames).getContent()
114-
115-
val kaptKotlinGeneratedDir = processingEnv.options[KAPT_KOTLIN_GENERATED_OPTION_NAME]
116-
fileContent.writeTo(File(kaptKotlinGeneratedDir))
117-
} catch (e: Exception) {
118-
processingEnv.messager.printMessage(
119-
Kind.ERROR,
120-
"Failed to generate remote config check: $e"
120+
private fun generateRemoteFeatureConfigCheck(remoteFeatures: List<KSAnnotated>) {
121+
if(remoteFeatures.isNotEmpty()) {
122+
RemoteFeatureConfigCheckBuilder(
123+
remoteFeatures.filterIsInstance<KSClassDeclaration>().map { it.asType(emptyList()).toTypeName() }
124+
).getContent().writeTo(
125+
codeGenerator,
126+
aggregating = true,
127+
originatingKSFiles = remoteFeatures.map { it.containingFile!! }
121128
)
122129
}
123130
}
124-
125-
companion object {
126-
const val KAPT_KOTLIN_GENERATED_OPTION_NAME = "kapt.kotlin.generated"
127-
}
128131
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.wordpress.android.processor
2+
3+
import com.google.devtools.ksp.processing.SymbolProcessor
4+
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
5+
import com.google.devtools.ksp.processing.SymbolProcessorProvider
6+
7+
class RemoteConfigProcessorProvider : SymbolProcessorProvider {
8+
override fun create(
9+
environment: SymbolProcessorEnvironment
10+
): SymbolProcessor {
11+
return RemoteConfigProcessor(environment.codeGenerator)
12+
}
13+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
org.wordpress.android.processor.RemoteConfigProcessorProvider

libs/processors/src/test/kotlin/org/wordpress/android/processor/RemoteConfigProcessorTest.kt

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package org.wordpress.android.processor
22

33
import com.tschuchort.compiletesting.KotlinCompilation
44
import com.tschuchort.compiletesting.SourceFile
5+
import com.tschuchort.compiletesting.kspWithCompilation
6+
import com.tschuchort.compiletesting.symbolProcessorProviders
57
import org.assertj.core.api.Assertions.assertThat
68
import org.jetbrains.kotlin.utils.addToStdlib.cast
79
import org.junit.Test
@@ -32,12 +34,7 @@ class RemoteConfigProcessorTest {
3234
)
3335

3436
// when
35-
val result = compile(
36-
listOf(
37-
remoteFieldA,
38-
featureA, /* adding a feature, as without it, annotation processor won't start */
39-
)
40-
)
37+
val result = compile(listOf(remoteFieldA))
4138

4239
// then
4340
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
@@ -102,12 +99,7 @@ class RemoteConfigProcessorTest {
10299
)
103100

104101
// when
105-
val result = compile(
106-
listOf(
107-
experiment,
108-
featureA, /* adding a feature, as without it, annotation processor won't start */
109-
)
110-
)
102+
val result = compile(listOf(experiment))
111103

112104
// then
113105

@@ -124,7 +116,8 @@ class RemoteConfigProcessorTest {
124116

125117
private fun compile(src: List<SourceFile>) = KotlinCompilation().apply {
126118
sources = src + fakeAppConfig
127-
annotationProcessors = listOf(RemoteConfigProcessor())
119+
symbolProcessorProviders = listOf(RemoteConfigProcessorProvider())
120+
kspWithCompilation = true
128121
inheritClassPath = true
129122
messageOutputStream = System.out
130123
}.compile()

settings.gradle

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pluginManagement {
2-
gradle.ext.kotlinVersion = '1.9.10'
2+
gradle.ext.kotlinVersion = '1.9.22'
3+
gradle.ext.kspVersion = '1.9.22-1.0.17'
34
gradle.ext.agpVersion = '8.1.0'
45
gradle.ext.googleServicesVersion = '4.3.15'
56
gradle.ext.navigationVersion = '2.5.3'
@@ -27,6 +28,7 @@ pluginManagement {
2728
id 'com.automattic.android.measure-builds' version gradle.ext.measureBuildsVersion
2829
id "org.jetbrains.kotlinx.kover" version gradle.ext.koverVersion
2930
id "com.google.dagger.hilt.android" version gradle.ext.daggerVersion
31+
id "com.google.devtools.ksp" version gradle.ext.kspVersion
3032
}
3133
repositories {
3234
maven {

0 commit comments

Comments
 (0)