From 5490e4ece010c549ea527272a8e40039039484e7 Mon Sep 17 00:00:00 2001 From: Wojtek Zieba Date: Tue, 20 Feb 2024 18:52:40 +0100 Subject: [PATCH] refactor: rewrite `RemoteConfigProcessor` to ksp --- WordPress/build.gradle | 2 +- .../processor/RemoteConfigProcessor.kt | 203 ++++++++---------- .../RemoteConfigProcessorProvider.kt | 13 ++ ...ols.ksp.processing.SymbolProcessorProvider | 1 + 4 files changed, 109 insertions(+), 110 deletions(-) create mode 100644 libs/processors/src/main/java/org/wordpress/android/processor/RemoteConfigProcessorProvider.kt create mode 100644 libs/processors/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider diff --git a/WordPress/build.gradle b/WordPress/build.gradle index 26d2218b3db4..00a222bcd866 100644 --- a/WordPress/build.gradle +++ b/WordPress/build.gradle @@ -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' diff --git a/libs/processors/src/main/java/org/wordpress/android/processor/RemoteConfigProcessor.kt b/libs/processors/src/main/java/org/wordpress/android/processor/RemoteConfigProcessor.kt index a8217dd7084f..3a325a1b6b3e 100644 --- a/libs/processors/src/main/java/org/wordpress/android/processor/RemoteConfigProcessor.kt +++ b/libs/processors/src/main/java/org/wordpress/android/processor/RemoteConfigProcessor.kt @@ -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?, 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() - 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 - ) { - 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 { + 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 - ) { - 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) { + 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 - ) { - 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 - ) { - 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() + .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) { + RemoteFeatureConfigCheckBuilder( + remoteFeatures.filterIsInstance().map { it.asType(emptyList()).toTypeName() } + ).getContent().writeTo( + codeGenerator, + aggregating = true, + ) } } diff --git a/libs/processors/src/main/java/org/wordpress/android/processor/RemoteConfigProcessorProvider.kt b/libs/processors/src/main/java/org/wordpress/android/processor/RemoteConfigProcessorProvider.kt new file mode 100644 index 000000000000..81c317430a8b --- /dev/null +++ b/libs/processors/src/main/java/org/wordpress/android/processor/RemoteConfigProcessorProvider.kt @@ -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) + } +} diff --git a/libs/processors/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/libs/processors/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider new file mode 100644 index 000000000000..1f997f654fad --- /dev/null +++ b/libs/processors/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider @@ -0,0 +1 @@ +org.wordpress.android.processor.RemoteConfigProcessorProvider