Skip to content

Commit

Permalink
refactor: rewrite RemoteConfigProcessor to ksp
Browse files Browse the repository at this point in the history
  • Loading branch information
wzieba committed Feb 20, 2024
1 parent 53a4dd0 commit 5490e4e
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 110 deletions.
2 changes: 1 addition & 1 deletion WordPress/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ dependencies {
implementation 'androidx.webkit:webkit:1.10.0'
implementation "androidx.navigation:navigation-compose:$androidxComposeNavigationVersion"
compileOnly project(path: ':libs:annotations')
kapt project(':libs:processors')
ksp project(':libs:processors')
implementation (project(path:':libs:networking')) {
exclude group: "com.android.volley"
exclude group: 'org.wordpress', module: 'utils'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,128 +1,113 @@
@file:OptIn(KspExperimental::class)


package org.wordpress.android.processor

import com.google.auto.service.AutoService
import com.squareup.kotlinpoet.DelicateKotlinPoetApi
import com.squareup.kotlinpoet.TypeName
import com.squareup.kotlinpoet.asTypeName
import com.google.devtools.ksp.KspExperimental
import com.google.devtools.ksp.getAnnotationsByType
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.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.squareup.kotlinpoet.ksp.toTypeName
import com.squareup.kotlinpoet.ksp.writeTo
import org.wordpress.android.annotation.Experiment
import org.wordpress.android.annotation.Feature
import org.wordpress.android.annotation.FeatureInDevelopment
import org.wordpress.android.annotation.RemoteFieldDefaultGenerater
import java.io.File
import javax.annotation.processing.AbstractProcessor
import javax.annotation.processing.Processor
import javax.annotation.processing.RoundEnvironment
import javax.annotation.processing.SupportedAnnotationTypes
import javax.annotation.processing.SupportedSourceVersion
import javax.lang.model.SourceVersion
import javax.lang.model.element.TypeElement
import javax.tools.Diagnostic.Kind

@AutoService(Processor::class) // For registering the service
@SupportedSourceVersion(SourceVersion.RELEASE_8) // to support Java 8
@SupportedAnnotationTypes(
"org.wordpress.android.annotation.Experiment",
"org.wordpress.android.annotation.Feature",
"org.wordpress.android.annotation.FeatureInDevelopment",
"org.wordpress.android.annotation.RemoteFieldDefaultGenerater"
)
class RemoteConfigProcessor : AbstractProcessor() {
@OptIn(DelicateKotlinPoetApi::class)
@Suppress("DEPRECATION")
override fun process(p0: MutableSet<out TypeElement>?, roundEnvironment: RoundEnvironment?): Boolean {
val experiments = roundEnvironment?.getElementsAnnotatedWith(Experiment::class.java)?.map { element ->
val annotation = element.getAnnotation(Experiment::class.java)
annotation.remoteField to annotation.defaultVariant
} ?: listOf()
val remoteFeatureNames = mutableListOf<TypeName>()
val features = roundEnvironment?.getElementsAnnotatedWith(Feature::class.java)?.map { element ->
val annotation = element.getAnnotation(Feature::class.java)
remoteFeatureNames.add(element.asType().asTypeName())
annotation.remoteField to annotation.defaultValue.toString()
} ?: listOf()
val remoteFields = roundEnvironment?.getElementsAnnotatedWith(RemoteFieldDefaultGenerater::class.java)
?.map { element ->
val annotation = element.getAnnotation(RemoteFieldDefaultGenerater::class.java)
annotation.remoteField to annotation.defaultValue
} ?: listOf()
val featuresInDevelopment = roundEnvironment?.getElementsAnnotatedWith(FeatureInDevelopment::class.java)
?.map { element ->
element.asType().toString()
} ?: listOf()
return if (experiments.isNotEmpty() || features.isNotEmpty()) {
generateRemoteFieldConfigDefaults(remoteFields.toMap())
generateRemoteFeatureConfigDefaults((experiments + features).toMap())
generateRemoteFeatureConfigCheck(remoteFeatureNames)
generateFeaturesInDevelopment(featuresInDevelopment)
true
} else {
false
}
}

@Suppress("TooGenericExceptionCaught", "SwallowedException")
private fun generateRemoteFeatureConfigDefaults(
remoteConfigDefaults: Map<String, String>
) {
try {
val fileContent = RemoteFeatureConfigDefaultsBuilder(remoteConfigDefaults).getContent()

val kaptKotlinGeneratedDir = processingEnv.options[KAPT_KOTLIN_GENERATED_OPTION_NAME]
fileContent.writeTo(File(kaptKotlinGeneratedDir))
} catch (e: Exception) {
processingEnv.messager.printMessage(Kind.ERROR, "Failed to generate remote feature config defaults")
@OptIn(KspExperimental::class)
class RemoteConfigProcessor(
private val codeGenerator: CodeGenerator,
) : SymbolProcessor {
/**
* See: https://github.com/google/ksp/issues/797#issuecomment-1041127747
* Also: https://github.com/google/ksp/blob/a0cd7774a7f65cec45a50ecc8960ef5e4d47fc21/examples/playground/test-processor/src/main/kotlin/TestProcessor.kt#L20
*/
private var invoked = false

override fun process(resolver: Resolver): List<KSAnnotated> {
if (invoked) {
return emptyList()
}

val remoteFeatures = resolver.getSymbolsWithAnnotation("org.wordpress.android.annotation.Feature")
.toList()

generateRemoteFeatureConfigDefaults(resolver, remoteFeatures)
generateRemoteFieldsConfigDefaults(resolver)
generateFeaturesInDevelopment(resolver)
generateRemoteFeatureConfigCheck(remoteFeatures)

invoked = true
return emptyList()
}

@Suppress("TooGenericExceptionCaught", "SwallowedException")
private fun generateRemoteFieldConfigDefaults(
remoteConfigDefaults: Map<String, String>
) {
try {
val fileContent = RemoteFieldConfigDefaultsBuilder(remoteConfigDefaults).getContent()

val kaptKotlinGeneratedDir = processingEnv.options[KAPT_KOTLIN_GENERATED_OPTION_NAME]
fileContent.writeTo(File(kaptKotlinGeneratedDir))
} catch (e: Exception) {
processingEnv.messager.printMessage(Kind.ERROR, "Failed to generate remote feature config defaults")
}
private fun generateRemoteFeatureConfigDefaults(resolver: Resolver, remoteFeatures: List<KSAnnotated>) {
val experiments = resolver.getSymbolsWithAnnotation("org.wordpress.android.annotation.Experiment")
.toList()

val defaults = (remoteFeatures + experiments)
.map { element: KSAnnotated ->
val featuresDefaults = element.getAnnotationsByType(Feature::class)
.toList().associate { annotation ->
annotation.remoteField to annotation.defaultValue.toString()
}
val experimentsDefaults = element.getAnnotationsByType(Experiment::class).toList()
.toList().associate { annotation ->
annotation.remoteField to annotation.defaultVariant
}
featuresDefaults + experimentsDefaults
}.flatMap { it.toList() }
.toMap()

RemoteFeatureConfigDefaultsBuilder(defaults).getContent()
.writeTo(
codeGenerator,
aggregating = true,
)
}

@Suppress("TooGenericExceptionCaught")
private fun generateRemoteFeatureConfigCheck(
remoteFeatureNames: List<TypeName>
) {
try {
val fileContent = RemoteFeatureConfigCheckBuilder(remoteFeatureNames).getContent()

val kaptKotlinGeneratedDir = processingEnv.options[KAPT_KOTLIN_GENERATED_OPTION_NAME]
fileContent.writeTo(File(kaptKotlinGeneratedDir))
} catch (e: Exception) {
processingEnv.messager.printMessage(
Kind.ERROR,
"Failed to generate remote feature config check: $e"
private fun generateRemoteFieldsConfigDefaults(resolver: Resolver) {
val remoteFieldDefaults =
resolver.getSymbolsWithAnnotation("org.wordpress.android.annotation.RemoteFieldDefaultGenerater")
.toList()
.associate { element: KSAnnotated ->
element.getAnnotationsByType(RemoteFieldDefaultGenerater::class)
.toList()
.first()
.let { annotation ->
annotation.remoteField to annotation.defaultValue
}
}

RemoteFieldConfigDefaultsBuilder(remoteFieldDefaults).getContent()
.writeTo(
codeGenerator,
aggregating = true,
)
}
}

@Suppress("TooGenericExceptionCaught")
private fun generateFeaturesInDevelopment(
remoteFeatureNames: List<String>
) {
try {
val fileContent = FeaturesInDevelopmentDefaultsBuilder(remoteFeatureNames).getContent()

val kaptKotlinGeneratedDir = processingEnv.options[KAPT_KOTLIN_GENERATED_OPTION_NAME]
fileContent.writeTo(File(kaptKotlinGeneratedDir))
} catch (e: Exception) {
processingEnv.messager.printMessage(
Kind.ERROR,
"Failed to generate remote config check: $e"
private fun generateFeaturesInDevelopment(resolver: Resolver) {
val featuresInDevelopmentDefaults =
resolver.getSymbolsWithAnnotation("org.wordpress.android.annotation.FeatureInDevelopment")
.filterIsInstance<KSClassDeclaration>()
.toList()
.map { it.simpleName.asString() }

FeaturesInDevelopmentDefaultsBuilder(featuresInDevelopmentDefaults).getContent()
.writeTo(
codeGenerator,
aggregating = true,
)
}
}

companion object {
const val KAPT_KOTLIN_GENERATED_OPTION_NAME = "kapt.kotlin.generated"
private fun generateRemoteFeatureConfigCheck(remoteFeatures: List<KSAnnotated>) {
RemoteFeatureConfigCheckBuilder(
remoteFeatures.filterIsInstance<KSClassDeclaration>().map { it.asType(emptyList()).toTypeName() }
).getContent().writeTo(
codeGenerator,
aggregating = true,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.wordpress.android.processor

import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.processing.SymbolProcessorProvider

class RemoteConfigProcessorProvider : SymbolProcessorProvider {
override fun create(
environment: SymbolProcessorEnvironment
): SymbolProcessor {
return RemoteConfigProcessor(environment.codeGenerator)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.wordpress.android.processor.RemoteConfigProcessorProvider

0 comments on commit 5490e4e

Please sign in to comment.