|
| 1 | +@file:OptIn(KspExperimental::class) |
| 2 | + |
| 3 | + |
1 | 4 | package org.wordpress.android.processor |
2 | 5 |
|
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 |
7 | 16 | import org.wordpress.android.annotation.Experiment |
8 | 17 | import org.wordpress.android.annotation.Feature |
9 | | -import org.wordpress.android.annotation.FeatureInDevelopment |
10 | 18 | 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() |
60 | 36 | } |
| 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() |
61 | 48 | } |
62 | 49 |
|
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 | + ) |
74 | 75 | } |
75 | 76 | } |
76 | 77 |
|
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 | + ) |
88 | 99 | } |
89 | 100 | } |
90 | 101 |
|
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 | + ) |
105 | 117 | } |
106 | 118 | } |
107 | 119 |
|
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!! } |
121 | 128 | ) |
122 | 129 | } |
123 | 130 | } |
124 | | - |
125 | | - companion object { |
126 | | - const val KAPT_KOTLIN_GENERATED_OPTION_NAME = "kapt.kotlin.generated" |
127 | | - } |
128 | 131 | } |
0 commit comments