diff --git a/build.gradle.kts b/build.gradle.kts index 2d3f520..def1e40 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,6 +7,7 @@ buildscript { classpath(Dependencies.androidPlugin) classpath(Dependencies.kotlinPlugin) classpath(Dependencies.dokkaPlugin) + classpath(Dependencies.kspPlugin) } } diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 899207d..d7ff7a6 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -26,8 +26,11 @@ object Versions { const val targetSdk = 30 const val kotlin = "1.5.30" + const val kotlinSymbolProcessingApi = "1.5.30-1.0.0" const val androidTools = "7.0.2" + const val kspPlugin = "1.5.30-1.0.0" + const val coroutines = "1.5.2" const val androidxCore = "1.6.0" @@ -57,6 +60,7 @@ object Dependencies { const val kotlinPlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}" const val androidPlugin = "com.android.tools.build:gradle:${Versions.androidTools}" const val dokkaPlugin = "org.jetbrains.dokka:dokka-gradle-plugin:${Versions.kotlin}" + const val kspPlugin = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:${Versions.kspPlugin}" const val kotlinReflect = "org.jetbrains.kotlin:kotlin-reflect:${Versions.kotlin}" @@ -76,6 +80,9 @@ object Dependencies { const val agConnectRemoteConfig = "com.huawei.agconnect:agconnect-remoteconfig:${Versions.agConnectRemoteConfig}" const val kotlinpoet = "com.squareup:kotlinpoet:${Versions.kotlinpoet}" + const val kotlinpoetKsp = "com.squareup:kotlinpoet-ksp:${Versions.kotlinpoet}" + + const val kotlinSymbolProcessingApi = "com.google.devtools.ksp:symbol-processing-api:${Versions.kotlinSymbolProcessingApi}" const val junit = "junit:junit:${Versions.junit}" const val mockito = "org.mockito:mockito-core:${Versions.mockito}" diff --git a/compiler/build.gradle.kts b/compiler/build.gradle.kts index 2d2a808..3362155 100644 --- a/compiler/build.gradle.kts +++ b/compiler/build.gradle.kts @@ -20,6 +20,8 @@ tasks.withType { dependencies { api(project(":core")) implementation(Dependencies.kotlinpoet) + implementation(Dependencies.kotlinpoetKsp) + implementation(Dependencies.kotlinSymbolProcessingApi) } setupJavaLibraryPublishing() \ No newline at end of file diff --git a/compiler/src/main/java/com/qiwi/featuretoggle/compiler/FeatureFactoryRegistryProcessor.kt b/compiler/src/main/java/com/qiwi/featuretoggle/compiler/FeatureFactoryRegistryProcessor.kt index 8b3f24e..8928175 100644 --- a/compiler/src/main/java/com/qiwi/featuretoggle/compiler/FeatureFactoryRegistryProcessor.kt +++ b/compiler/src/main/java/com/qiwi/featuretoggle/compiler/FeatureFactoryRegistryProcessor.kt @@ -23,6 +23,7 @@ package com.qiwi.featuretoggle.compiler import com.qiwi.featuretoggle.annotation.Factory import com.qiwi.featuretoggle.annotation.FeatureFlag +import com.qiwi.featuretoggle.compiler.generate.createFeatureFactoryRegistryFileSpec import com.qiwi.featuretoggle.factory.FeatureFactory import com.qiwi.featuretoggle.registry.FeatureFactoryRegistry import com.squareup.kotlinpoet.* @@ -39,19 +40,6 @@ import javax.lang.model.type.DeclaredType */ class FeatureFactoryRegistryProcessor: AbstractProcessor() { - companion object { - - /** - * Package name where generated implementation of [FeatureFactoryRegistry] will be placed. - */ - const val GENERATED_PACKAGE_NAME = "com.qiwi.featuretoggle.registry" - - /** - * Name for generated implementation of [FeatureFactoryRegistry]. - */ - const val GENERATED_CLASS_NAME = "FeatureFactoryRegistryGenerated" - } - override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latestSupported() override fun getSupportedAnnotationTypes(): Set = setOf(Factory::class.java.canonicalName) @@ -73,53 +61,10 @@ class FeatureFactoryRegistryProcessor: AbstractProcessor() { featureClass to Pair(featureKey, featureFactoryClass) }.toMap().toSortedMap() if(featureFactoryMap.isNotEmpty()) { - createFeatureFactoryRegistry(featureFactoryMap) + createFeatureFactoryRegistryFileSpec(featureFactoryMap) + .writeTo(processingEnv.filer) } return false } - private fun createFeatureFactoryRegistry(featureFactoryMap: Map>) { - val factoryMapType = MAP.parameterizedBy( - Class::class.asTypeName().parameterizedBy(WildcardTypeName.producerOf(Any::class)), - Pair::class.asTypeName().parameterizedBy( - String::class.asTypeName(), Class::class.asTypeName().parameterizedBy( - WildcardTypeName.producerOf(Any::class) - ) - ) - ) - val registry = TypeSpec.classBuilder(GENERATED_CLASS_NAME) - .addSuperinterface(FeatureFactoryRegistry::class) - .addProperty( - PropertySpec.builder("factoryMap", factoryMapType, KModifier.PRIVATE) - .initializer(getFactoryMapInitializer(featureFactoryMap)) - .build()) - .addFunction( - FunSpec.builder("getFactoryMap") - .addModifiers(KModifier.OVERRIDE) - .returns(factoryMapType) - .addCode( - CodeBlock.Builder() - .addStatement("return factoryMap") - .build()) - .build()) - .build() - FileSpec.builder(GENERATED_PACKAGE_NAME, GENERATED_CLASS_NAME) - .addType(registry) - .build() - .writeTo(processingEnv.filer) - } - - private fun getFactoryMapInitializer(featureFactoryMap: Map>): CodeBlock { - val builder = CodeBlock.Builder().addStatement("mapOf(") - featureFactoryMap.entries.forEachIndexed { index, entry -> - val featureClassName = entry.key - val featureKey = entry.value.first - val featureFactoryClassName = entry.value.second - val endOfStatement = if(index == featureFactoryMap.size-1) "" else "," - builder.addStatement("%T::class.java to Pair(%S, %T::class.java)$endOfStatement", featureClassName, featureKey, featureFactoryClassName) - } - return builder - .addStatement(")") - .build() - } } \ No newline at end of file diff --git a/compiler/src/main/java/com/qiwi/featuretoggle/compiler/FeatureFlagRegistryProcessor.kt b/compiler/src/main/java/com/qiwi/featuretoggle/compiler/FeatureFlagRegistryProcessor.kt index a435ee4..df1f497 100644 --- a/compiler/src/main/java/com/qiwi/featuretoggle/compiler/FeatureFlagRegistryProcessor.kt +++ b/compiler/src/main/java/com/qiwi/featuretoggle/compiler/FeatureFlagRegistryProcessor.kt @@ -22,6 +22,7 @@ package com.qiwi.featuretoggle.compiler import com.qiwi.featuretoggle.annotation.FeatureFlag +import com.qiwi.featuretoggle.compiler.generate.createFeatureFlagRegistryFileSpec import com.qiwi.featuretoggle.registry.FeatureFlagRegistry import com.squareup.kotlinpoet.* import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy @@ -36,19 +37,6 @@ import javax.lang.model.element.TypeElement */ class FeatureFlagRegistryProcessor: AbstractProcessor() { - companion object { - - /** - * Package name where generated implementation of [FeatureFlagRegistry] will be placed. - */ - const val GENERATED_PACKAGE_NAME = "com.qiwi.featuretoggle.registry" - - /** - * Name for generated implementation of [FeatureFlagRegistry]. - */ - const val GENERATED_CLASS_NAME = "FeatureFlagRegistryGenerated" - } - override fun getSupportedSourceVersion(): SourceVersion = SourceVersion.latestSupported() override fun getSupportedAnnotationTypes(): Set = setOf(FeatureFlag::class.java.canonicalName) @@ -68,78 +56,10 @@ class FeatureFlagRegistryProcessor: AbstractProcessor() { }.toMap().toSortedMap() if(featureFlagMap.isNotEmpty()) { val featureKeyMap = featureFlagMap.entries.associateBy({ it.value }) { it.key }.toSortedMap() - createFeatureFlagRegistry(featureFlagMap, featureKeyMap) + createFeatureFlagRegistryFileSpec(featureFlagMap, featureKeyMap) + .writeTo(processingEnv.filer) } return false } - private fun createFeatureFlagRegistry(featureFlagsMap: Map, featureKeysMap: Map) { - val flagsMapType = MAP.parameterizedBy( - String::class.asTypeName(), - Class::class.asTypeName().parameterizedBy(WildcardTypeName.producerOf(Any::class)) - ) - val keysMapType = MAP.parameterizedBy( - Class::class.asTypeName().parameterizedBy(WildcardTypeName.producerOf(Any::class)), - String::class.asTypeName() - ) - val registry = TypeSpec.classBuilder(GENERATED_CLASS_NAME) - .addSuperinterface(FeatureFlagRegistry::class) - .addProperty( - PropertySpec.builder("flagsMap", flagsMapType, KModifier.PRIVATE) - .initializer(getFlagsMapInitializer(featureFlagsMap)) - .build()) - .addProperty( - PropertySpec.builder("keysMap", keysMapType, KModifier.PRIVATE) - .initializer(getKeysMapInitializer(featureKeysMap)) - .build()) - .addFunction( - FunSpec.builder("getFeatureFlagsMap") - .addModifiers(KModifier.OVERRIDE) - .returns(flagsMapType) - .addCode( - CodeBlock.Builder() - .addStatement("return flagsMap") - .build()) - .build()) - .addFunction( - FunSpec.builder("getFeatureKeysMap") - .addModifiers(KModifier.OVERRIDE) - .returns(keysMapType) - .addCode( - CodeBlock.Builder() - .addStatement("return keysMap") - .build()) - .build()) - .build() - FileSpec.builder(GENERATED_PACKAGE_NAME, GENERATED_CLASS_NAME) - .addType(registry) - .build() - .writeTo(processingEnv.filer) - } - - private fun getFlagsMapInitializer(featureFlagMap: Map): CodeBlock { - val builder = CodeBlock.Builder().addStatement("mapOf(") - featureFlagMap.entries.forEachIndexed { index, entry -> - val key = entry.key - val className = entry.value - val endOfStatement = if(index == featureFlagMap.size-1) "" else "," - builder.addStatement("%S to %T::class.java$endOfStatement", key, className) - } - return builder - .addStatement(")") - .build() - } - - private fun getKeysMapInitializer(featureKeysMap: Map): CodeBlock { - val builder = CodeBlock.Builder().addStatement("mapOf(") - featureKeysMap.entries.forEachIndexed { index, entry -> - val className = entry.key - val key = entry.value - val endOfStatement = if(index == featureKeysMap.size-1) "" else "," - builder.addStatement("%T::class.java to %S$endOfStatement", className, key) - } - return builder - .addStatement(")") - .build() - } } \ No newline at end of file diff --git a/compiler/src/main/java/com/qiwi/featuretoggle/compiler/generate/FeatureFactoryRegistry.kt b/compiler/src/main/java/com/qiwi/featuretoggle/compiler/generate/FeatureFactoryRegistry.kt new file mode 100644 index 0000000..8512fc7 --- /dev/null +++ b/compiler/src/main/java/com/qiwi/featuretoggle/compiler/generate/FeatureFactoryRegistry.kt @@ -0,0 +1,76 @@ +package com.qiwi.featuretoggle.compiler.generate + +import com.qiwi.featuretoggle.registry.FeatureFactoryRegistry +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.MAP +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.WildcardTypeName +import com.squareup.kotlinpoet.asTypeName + +/** + * Package name where generated implementation of [FeatureFactoryRegistry] will be placed. + */ +private const val GENERATED_PACKAGE_NAME = "com.qiwi.featuretoggle.registry" + +/** + * Name for generated implementation of [FeatureFactoryRegistry]. + */ +private const val GENERATED_CLASS_NAME = "FeatureFactoryRegistryGenerated" + +fun createFeatureFactoryRegistryFileSpec(featureFactoryMap: Map>): FileSpec { + val factoryMapType = MAP.parameterizedBy( + Class::class.asTypeName().parameterizedBy(WildcardTypeName.producerOf(Any::class)), + Pair::class.asTypeName().parameterizedBy( + String::class.asTypeName(), Class::class.asTypeName().parameterizedBy( + WildcardTypeName.producerOf(Any::class) + ) + ) + ) + val registry = TypeSpec.classBuilder(GENERATED_CLASS_NAME) + .addSuperinterface(FeatureFactoryRegistry::class) + .addProperty( + PropertySpec.builder("factoryMap", factoryMapType, KModifier.PRIVATE) + .initializer(getFactoryMapInitializer(featureFactoryMap)) + .build() + ) + .addFunction( + FunSpec.builder("getFactoryMap") + .addModifiers(KModifier.OVERRIDE) + .returns(factoryMapType) + .addCode( + CodeBlock.Builder() + .addStatement("return factoryMap") + .build() + ) + .build() + ) + .build() + return FileSpec.builder(GENERATED_PACKAGE_NAME, GENERATED_CLASS_NAME) + .addType(registry) + .build() +} + +private fun getFactoryMapInitializer(featureFactoryMap: Map>): CodeBlock { + val builder = CodeBlock.Builder().addStatement("mapOf(") + featureFactoryMap.entries.forEachIndexed { index, entry -> + val featureClassName = entry.key + val featureKey = entry.value.first + val featureFactoryClassName = entry.value.second + val endOfStatement = if (index == featureFactoryMap.size - 1) "" else "," + builder.addStatement( + "%T::class.java to Pair(%S, %T::class.java)$endOfStatement", + featureClassName, + featureKey, + featureFactoryClassName + ) + } + return builder + .addStatement(")") + .build() +} \ No newline at end of file diff --git a/compiler/src/main/java/com/qiwi/featuretoggle/compiler/generate/FeatureFlagRegistry.kt b/compiler/src/main/java/com/qiwi/featuretoggle/compiler/generate/FeatureFlagRegistry.kt new file mode 100644 index 0000000..5ab6eee --- /dev/null +++ b/compiler/src/main/java/com/qiwi/featuretoggle/compiler/generate/FeatureFlagRegistry.kt @@ -0,0 +1,103 @@ +package com.qiwi.featuretoggle.compiler.generate + +import com.qiwi.featuretoggle.compiler.FeatureFlagRegistryProcessor +import com.qiwi.featuretoggle.registry.FeatureFlagRegistry +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.MAP +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.WildcardTypeName +import com.squareup.kotlinpoet.asTypeName + +/** + * Package name where generated implementation of [FeatureFlagRegistry] will be placed. + */ +private const val GENERATED_PACKAGE_NAME = "com.qiwi.featuretoggle.registry" + +/** + * Name for generated implementation of [FeatureFlagRegistry]. + */ +private const val GENERATED_CLASS_NAME = "FeatureFlagRegistryGenerated" + +fun createFeatureFlagRegistryFileSpec( + featureFlagsMap: Map, + featureKeysMap: Map +): FileSpec { + val flagsMapType = MAP.parameterizedBy( + String::class.asTypeName(), + Class::class.asTypeName().parameterizedBy(WildcardTypeName.producerOf(Any::class)) + ) + val keysMapType = MAP.parameterizedBy( + Class::class.asTypeName().parameterizedBy(WildcardTypeName.producerOf(Any::class)), + String::class.asTypeName() + ) + val registry = TypeSpec.classBuilder(GENERATED_CLASS_NAME) + .addSuperinterface(FeatureFlagRegistry::class) + .addProperty( + PropertySpec.builder("flagsMap", flagsMapType, KModifier.PRIVATE) + .initializer(getFlagsMapInitializer(featureFlagsMap)) + .build() + ) + .addProperty( + PropertySpec.builder("keysMap", keysMapType, KModifier.PRIVATE) + .initializer(getKeysMapInitializer(featureKeysMap)) + .build() + ) + .addFunction( + FunSpec.builder("getFeatureFlagsMap") + .addModifiers(KModifier.OVERRIDE) + .returns(flagsMapType) + .addCode( + CodeBlock.Builder() + .addStatement("return flagsMap") + .build() + ) + .build() + ) + .addFunction( + FunSpec.builder("getFeatureKeysMap") + .addModifiers(KModifier.OVERRIDE) + .returns(keysMapType) + .addCode( + CodeBlock.Builder() + .addStatement("return keysMap") + .build() + ) + .build() + ) + .build() + return FileSpec.builder(GENERATED_PACKAGE_NAME, GENERATED_CLASS_NAME) + .addType(registry) + .build() +} + +private fun getFlagsMapInitializer(featureFlagMap: Map): CodeBlock { + val builder = CodeBlock.Builder().addStatement("mapOf(") + featureFlagMap.entries.forEachIndexed { index, entry -> + val key = entry.key + val className = entry.value + val endOfStatement = if (index == featureFlagMap.size - 1) "" else "," + builder.addStatement("%S to %T::class.java$endOfStatement", key, className) + } + return builder + .addStatement(")") + .build() +} + +private fun getKeysMapInitializer(featureKeysMap: Map): CodeBlock { + val builder = CodeBlock.Builder().addStatement("mapOf(") + featureKeysMap.entries.forEachIndexed { index, entry -> + val className = entry.key + val key = entry.value + val endOfStatement = if (index == featureKeysMap.size - 1) "" else "," + builder.addStatement("%T::class.java to %S$endOfStatement", className, key) + } + return builder + .addStatement(")") + .build() +} \ No newline at end of file diff --git a/compiler/src/main/java/com/qiwi/featuretoggle/compiler/ksp/ClassDeclarationAnnotationVisitor.kt b/compiler/src/main/java/com/qiwi/featuretoggle/compiler/ksp/ClassDeclarationAnnotationVisitor.kt new file mode 100644 index 0000000..bf08435 --- /dev/null +++ b/compiler/src/main/java/com/qiwi/featuretoggle/compiler/ksp/ClassDeclarationAnnotationVisitor.kt @@ -0,0 +1,18 @@ +package com.qiwi.featuretoggle.compiler.ksp + +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSNode +import com.google.devtools.ksp.visitor.KSEmptyVisitor + +class ClassDeclarationAnnotationVisitor( + private val visitClassDeclarationBlock: (KSClassDeclaration) -> R +) : KSEmptyVisitor() { + + override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit): R { + return visitClassDeclarationBlock(classDeclaration) + } + + override fun defaultHandler(node: KSNode, data: Unit): R { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/compiler/src/main/java/com/qiwi/featuretoggle/compiler/ksp/FeatureFactoryRegistrySymbolProcessor.kt b/compiler/src/main/java/com/qiwi/featuretoggle/compiler/ksp/FeatureFactoryRegistrySymbolProcessor.kt new file mode 100644 index 0000000..3ef64fc --- /dev/null +++ b/compiler/src/main/java/com/qiwi/featuretoggle/compiler/ksp/FeatureFactoryRegistrySymbolProcessor.kt @@ -0,0 +1,64 @@ +package com.qiwi.featuretoggle.compiler.ksp + +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.KSPLogger +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.qiwi.featuretoggle.annotation.Factory +import com.qiwi.featuretoggle.annotation.FeatureFlag +import com.qiwi.featuretoggle.compiler.generate.createFeatureFactoryRegistryFileSpec +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.ksp.KotlinPoetKspPreview +import com.squareup.kotlinpoet.ksp.writeTo + +class FeatureFactoryRegistrySymbolProcessor( + private val codeGenerator: CodeGenerator, + private val logger: KSPLogger +) : SymbolProcessor { + + @OptIn(KspExperimental::class, KotlinPoetKspPreview::class) + override fun process(resolver: Resolver): List { + val featureFactorySymbols = resolver + .getSymbolsWithAnnotation(Factory::class.qualifiedName!!) + .filterIsInstance() + .toList() + + val originFiles = featureFactorySymbols.map { it.containingFile!! } + + val featureFactorySymbolsVisitor = + ClassDeclarationAnnotationVisitor { classDeclaration -> + val factoryClassName = ClassName( + classDeclaration.packageName.asString(), + classDeclaration.simpleName.asString() + ) + val superTypeFeatureFactory = classDeclaration.superTypes.first().resolve() + + val featureKSType = superTypeFeatureFactory.arguments[0].type!! + val featureFlagKSType = superTypeFeatureFactory.arguments[1].type!! + + val featureClassName = ClassName( + featureKSType.resolve().declaration.packageName.asString(), + featureKSType.resolve().declaration.simpleName.asString() + ) + val featureKey = + featureFlagKSType.resolve().declaration.getAnnotationsByType(FeatureFlag::class) + .first().key + + featureClassName to Pair(featureKey, factoryClassName) + } + + val featureFactoryMap = + featureFactorySymbols.associate { it.accept(featureFactorySymbolsVisitor, Unit) } + .toSortedMap() + + if (featureFactoryMap.isNotEmpty()) { + createFeatureFactoryRegistryFileSpec(featureFactoryMap) + .writeTo(codeGenerator, aggregating = true, originFiles) + } + return emptyList() + } +} \ No newline at end of file diff --git a/compiler/src/main/java/com/qiwi/featuretoggle/compiler/ksp/FeatureFactoryRegistrySymbolProcessorProvider.kt b/compiler/src/main/java/com/qiwi/featuretoggle/compiler/ksp/FeatureFactoryRegistrySymbolProcessorProvider.kt new file mode 100644 index 0000000..63d724a --- /dev/null +++ b/compiler/src/main/java/com/qiwi/featuretoggle/compiler/ksp/FeatureFactoryRegistrySymbolProcessorProvider.kt @@ -0,0 +1,14 @@ +package com.qiwi.featuretoggle.compiler.ksp + +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.processing.SymbolProcessorProvider + +class FeatureFactoryRegistrySymbolProcessorProvider : SymbolProcessorProvider { + + override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor = + FeatureFactoryRegistrySymbolProcessor( + environment.codeGenerator, + environment.logger + ) +} \ No newline at end of file diff --git a/compiler/src/main/java/com/qiwi/featuretoggle/compiler/ksp/FeatureFlagRegistrySymbolProcessor.kt b/compiler/src/main/java/com/qiwi/featuretoggle/compiler/ksp/FeatureFlagRegistrySymbolProcessor.kt new file mode 100644 index 0000000..55032b6 --- /dev/null +++ b/compiler/src/main/java/com/qiwi/featuretoggle/compiler/ksp/FeatureFlagRegistrySymbolProcessor.kt @@ -0,0 +1,52 @@ +package com.qiwi.featuretoggle.compiler.ksp + +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.qiwi.featuretoggle.annotation.FeatureFlag +import com.qiwi.featuretoggle.compiler.generate.createFeatureFlagRegistryFileSpec +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.ksp.KotlinPoetKspPreview +import com.squareup.kotlinpoet.ksp.writeTo + +class FeatureFlagRegistrySymbolProcessor( + private val codeGenerator: CodeGenerator +) : SymbolProcessor { + + @OptIn(KspExperimental::class, KotlinPoetKspPreview::class) + override fun process(resolver: Resolver): List { + val featureFlagSymbols = resolver + .getSymbolsWithAnnotation(FeatureFlag::class.qualifiedName!!) + .filterIsInstance() + .toList() + + val originFiles = featureFlagSymbols.map { it.containingFile!! } + + val featureFlagSymbolsVisitor = + ClassDeclarationAnnotationVisitor { classDeclaration -> + val flagClassName = ClassName( + classDeclaration.packageName.asString(), + classDeclaration.simpleName.asString() + ) + val featureKey = classDeclaration.getAnnotationsByType(FeatureFlag::class) + .first().key + featureKey to flagClassName + } + + val featureFlagMap = + featureFlagSymbols.associate { it.accept(featureFlagSymbolsVisitor, Unit) } + .toSortedMap() + + if (featureFlagMap.isNotEmpty()) { + val featureKeyMap = featureFlagMap.entries.associate { it.value to it.key } + .toSortedMap() + createFeatureFlagRegistryFileSpec(featureFlagMap, featureKeyMap) + .writeTo(codeGenerator, aggregating = true, originFiles) + } + return emptyList() + } +} \ No newline at end of file diff --git a/compiler/src/main/java/com/qiwi/featuretoggle/compiler/ksp/FeatureFlagRegistrySymbolProcessorProvider.kt b/compiler/src/main/java/com/qiwi/featuretoggle/compiler/ksp/FeatureFlagRegistrySymbolProcessorProvider.kt new file mode 100644 index 0000000..75c8560 --- /dev/null +++ b/compiler/src/main/java/com/qiwi/featuretoggle/compiler/ksp/FeatureFlagRegistrySymbolProcessorProvider.kt @@ -0,0 +1,11 @@ +package com.qiwi.featuretoggle.compiler.ksp + +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.processing.SymbolProcessorProvider + +class FeatureFlagRegistrySymbolProcessorProvider : SymbolProcessorProvider { + + override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor = + FeatureFlagRegistrySymbolProcessor(environment.codeGenerator) +} \ No newline at end of file diff --git a/compiler/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/compiler/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider new file mode 100644 index 0000000..82aa178 --- /dev/null +++ b/compiler/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider @@ -0,0 +1,2 @@ +com.qiwi.featuretoggle.compiler.ksp.FeatureFactoryRegistrySymbolProcessorProvider +com.qiwi.featuretoggle.compiler.ksp.FeatureFlagRegistrySymbolProcessorProvider \ No newline at end of file diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts index 3304122..8f1e952 100644 --- a/sample/build.gradle.kts +++ b/sample/build.gradle.kts @@ -2,6 +2,7 @@ plugins { id("com.android.application") kotlin("android") kotlin("kapt") + id("com.google.devtools.ksp") } android { @@ -33,10 +34,15 @@ dependencies { implementation(project(":feature-manager-singleton")) implementation(project(":converter-jackson")) implementation(project(":datasource-remote")) - kapt(project(":compiler")) + ksp(project(":compiler")) implementation(Dependencies.androidxCore) implementation(Dependencies.androidxAppcompat) implementation(Dependencies.androidxLifecycleRuntime) implementation(Dependencies.googleMaterial) } +kotlin { + sourceSets.all { + kotlin.srcDir("build/generated/ksp/$name/kotlin") + } +}