diff --git a/WordPress/build.gradle b/WordPress/build.gradle
index cda6cc9134f2..810ee938ee9d 100644
--- a/WordPress/build.gradle
+++ b/WordPress/build.gradle
@@ -4,7 +4,6 @@ import se.bjurr.violations.lib.model.SEVERITY
plugins {
id "com.android.application"
id "org.jetbrains.kotlin.android"
- id "org.jetbrains.kotlin.kapt"
id "org.jetbrains.kotlin.plugin.parcelize"
id "org.jetbrains.kotlin.plugin.allopen"
id "io.sentry.android.gradle"
@@ -12,6 +11,7 @@ plugins {
id "com.google.gms.google-services"
id "com.google.dagger.hilt.android"
id "org.jetbrains.kotlinx.kover"
+ id "com.google.devtools.ksp"
}
sentry {
@@ -336,16 +336,12 @@ static def addBuildConfigFieldsFromPrefixedProperties(variant, properties, prefi
variant.buildConfigField "String", it.key.toUpperCase(), "\"${it.value}\""
}
}
-kapt {
- // Enable to infer error types in stubs (see: https://kotlinlang.org/docs/kapt.html#non-existent-type-correction)
- correctErrorTypes true
-}
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'
@@ -447,7 +443,7 @@ dependencies {
exclude group: 'androidx.appcompat', module: 'appcompat'
}
implementation "com.github.bumptech.glide:glide:$glideVersion"
- kapt "com.github.bumptech.glide:compiler:$glideVersion"
+ ksp "com.github.bumptech.glide:ksp:$glideVersion"
implementation "com.github.bumptech.glide:volley-integration:$glideVersion"
implementation "com.github.indexos.media-for-mobile:domain:$indexosMediaForMobileVersion"
implementation "com.github.indexos.media-for-mobile:android:$indexosMediaForMobileVersion"
@@ -461,9 +457,9 @@ dependencies {
exclude group: 'com.android.support', module: 'support-annotations'
}
implementation "com.google.dagger:dagger-android-support:$gradle.ext.daggerVersion"
- kapt "com.google.dagger:dagger-android-processor:$gradle.ext.daggerVersion"
+ ksp "com.google.dagger:dagger-android-processor:$gradle.ext.daggerVersion"
implementation "com.google.dagger:hilt-android:$gradle.ext.daggerVersion"
- kapt "com.google.dagger:hilt-compiler:$gradle.ext.daggerVersion"
+ ksp "com.google.dagger:hilt-compiler:$gradle.ext.daggerVersion"
testImplementation("androidx.arch.core:core-testing:$androidxArchCoreVersion", {
exclude group: 'com.android.support', module: 'support-compat'
@@ -518,7 +514,7 @@ dependencies {
androidTestImplementation (name:'cloudtestingscreenshotter_lib', ext:'aar') // Screenshots on Firebase Cloud Testing
androidTestImplementation "androidx.work:work-testing:$androidxWorkManagerVersion"
androidTestImplementation "com.google.dagger:hilt-android-testing:$gradle.ext.daggerVersion"
- kaptAndroidTest "com.google.dagger:hilt-android-compiler:$gradle.ext.daggerVersion"
+ kspAndroidTest "com.google.dagger:hilt-android-compiler:$gradle.ext.daggerVersion"
// Enables Java 8+ API desugaring support
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:$androidDesugarVersion"
lintChecks "org.wordpress:lint:$wordPressLintVersion"
diff --git a/build.gradle b/build.gradle
index 700f9c8724e7..208be6e589f3 100644
--- a/build.gradle
+++ b/build.gradle
@@ -9,6 +9,7 @@ plugins {
id "com.android.library" apply false
id 'com.google.gms.google-services' apply false
id "org.jetbrains.kotlin.plugin.parcelize" apply false
+ id "com.google.devtools.ksp" apply false
}
ext {
diff --git a/config/lint/lint.xml b/config/lint/lint.xml
index e779334f1bba..3985b167379f 100644
--- a/config/lint/lint.xml
+++ b/config/lint/lint.xml
@@ -19,7 +19,7 @@
-
+
@@ -63,7 +63,7 @@
-
+
diff --git a/libs/processors/build.gradle b/libs/processors/build.gradle
index 77eb4965cfe2..6baeb1bb411d 100644
--- a/libs/processors/build.gradle
+++ b/libs/processors/build.gradle
@@ -1,6 +1,5 @@
plugins {
id "org.jetbrains.kotlin.jvm"
- id "org.jetbrains.kotlin.kapt"
id "org.jetbrains.kotlinx.kover"
}
@@ -10,11 +9,13 @@ targetCompatibility = JavaVersion.VERSION_1_8
dependencies {
implementation project(":libs:annotations")
- implementation "com.google.auto.service:auto-service:$googleAutoServiceVersion"
- kapt "com.google.auto.service:auto-service:$googleAutoServiceVersion"
implementation "com.squareup:kotlinpoet:$squareupKotlinPoetVersion"
+ implementation "com.squareup:kotlinpoet-ksp:$squareupKotlinPoetVersion"
+ implementation "com.google.devtools.ksp:symbol-processing-api:$gradle.ext.kspVersion"
- testImplementation "com.github.tschuchortdev:kotlin-compile-testing:1.5.0"
+ def kctVersion = "1.5.0"
+ testImplementation "com.github.tschuchortdev:kotlin-compile-testing:$kctVersion"
+ testImplementation "com.github.tschuchortdev:kotlin-compile-testing-ksp:$kctVersion"
testImplementation "junit:junit:$junitVersion"
testImplementation "org.assertj:assertj-core:$assertjVersion"
testImplementation "org.jetbrains.kotlin:kotlin-reflect:$gradle.ext.kotlinVersion"
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..2c5c57acd01b 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,131 @@
+@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.containingFile
+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
+
+@OptIn(KspExperimental::class)
+class RemoteConfigProcessor(
+ private val codeGenerator: CodeGenerator,
+) : SymbolProcessor {
+ /**
+ * In the case of this processor, we only one need round. Generated files do not depend on each other
+ * or any other processor.
+ *
+ * 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 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")
+ 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()
+
+ if (defaults.isNotEmpty()) {
+ RemoteFeatureConfigDefaultsBuilder(defaults).getContent()
+ .writeTo(
+ codeGenerator,
+ aggregating = true,
+ originatingKSFiles = remoteFeatures.map { it.containingFile!! }
+ )
}
}
- @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 generateRemoteFieldsConfigDefaults(resolver: Resolver) {
+ val remoteFields =
+ resolver.getSymbolsWithAnnotation("org.wordpress.android.annotation.RemoteFieldDefaultGenerater")
+ .toList()
+ val remoteFieldDefaults = remoteFields
+ .associate { element: KSAnnotated ->
+ element.getAnnotationsByType(RemoteFieldDefaultGenerater::class)
+ .toList()
+ .first()
+ .let { annotation ->
+ annotation.remoteField to annotation.defaultValue
+ }
+ }
+
+ if(remoteFieldDefaults.isNotEmpty()) {
+ RemoteFieldConfigDefaultsBuilder(remoteFieldDefaults).getContent()
+ .writeTo(
+ codeGenerator,
+ aggregating = true,
+ originatingKSFiles = remoteFields.map { it.containingFile!! }
+ )
}
}
- @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 generateFeaturesInDevelopment(resolver: Resolver) {
+ val featuresInDevelopment =
+ resolver.getSymbolsWithAnnotation("org.wordpress.android.annotation.FeatureInDevelopment")
+ .filterIsInstance()
+ .toList()
+ val featuresInDevelopmentDefaults = featuresInDevelopment
+ .map { it.simpleName.asString() }
+
+ if(featuresInDevelopmentDefaults.isNotEmpty()) {
+ FeaturesInDevelopmentDefaultsBuilder(featuresInDevelopmentDefaults).getContent()
+ .writeTo(
+ codeGenerator,
+ aggregating = true,
+ originatingKSFiles = featuresInDevelopment.map { it.containingFile!! }
+ )
}
}
- @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 generateRemoteFeatureConfigCheck(remoteFeatures: List) {
+ if(remoteFeatures.isNotEmpty()) {
+ RemoteFeatureConfigCheckBuilder(
+ remoteFeatures.filterIsInstance().map { it.asType(emptyList()).toTypeName() }
+ ).getContent().writeTo(
+ codeGenerator,
+ aggregating = true,
+ originatingKSFiles = remoteFeatures.map { it.containingFile!! }
)
}
}
-
- companion object {
- const val KAPT_KOTLIN_GENERATED_OPTION_NAME = "kapt.kotlin.generated"
- }
}
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
diff --git a/libs/processors/src/test/kotlin/org/wordpress/android/processor/RemoteConfigProcessorTest.kt b/libs/processors/src/test/kotlin/org/wordpress/android/processor/RemoteConfigProcessorTest.kt
index b4ee3385c125..aa99781c26ac 100644
--- a/libs/processors/src/test/kotlin/org/wordpress/android/processor/RemoteConfigProcessorTest.kt
+++ b/libs/processors/src/test/kotlin/org/wordpress/android/processor/RemoteConfigProcessorTest.kt
@@ -2,6 +2,8 @@ package org.wordpress.android.processor
import com.tschuchort.compiletesting.KotlinCompilation
import com.tschuchort.compiletesting.SourceFile
+import com.tschuchort.compiletesting.kspWithCompilation
+import com.tschuchort.compiletesting.symbolProcessorProviders
import org.assertj.core.api.Assertions.assertThat
import org.jetbrains.kotlin.utils.addToStdlib.cast
import org.junit.Test
@@ -32,12 +34,7 @@ class RemoteConfigProcessorTest {
)
// when
- val result = compile(
- listOf(
- remoteFieldA,
- featureA, /* adding a feature, as without it, annotation processor won't start */
- )
- )
+ val result = compile(listOf(remoteFieldA))
// then
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.OK)
@@ -102,12 +99,7 @@ class RemoteConfigProcessorTest {
)
// when
- val result = compile(
- listOf(
- experiment,
- featureA, /* adding a feature, as without it, annotation processor won't start */
- )
- )
+ val result = compile(listOf(experiment))
// then
@@ -124,7 +116,8 @@ class RemoteConfigProcessorTest {
private fun compile(src: List) = KotlinCompilation().apply {
sources = src + fakeAppConfig
- annotationProcessors = listOf(RemoteConfigProcessor())
+ symbolProcessorProviders = listOf(RemoteConfigProcessorProvider())
+ kspWithCompilation = true
inheritClassPath = true
messageOutputStream = System.out
}.compile()
diff --git a/settings.gradle b/settings.gradle
index 575a105c1e45..e9b039575b1f 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,5 +1,6 @@
pluginManagement {
gradle.ext.kotlinVersion = '1.9.22'
+ gradle.ext.kspVersion = '1.9.22-1.0.17'
gradle.ext.agpVersion = '8.1.0'
gradle.ext.googleServicesVersion = '4.3.15'
gradle.ext.navigationVersion = '2.7.7'
@@ -13,7 +14,6 @@ pluginManagement {
plugins {
id "org.jetbrains.kotlin.android" version gradle.ext.kotlinVersion
id "org.jetbrains.kotlin.jvm" version gradle.ext.kotlinVersion
- id "org.jetbrains.kotlin.kapt" version gradle.ext.kotlinVersion
id "org.jetbrains.kotlin.plugin.serialization" version gradle.ext.kotlinVersion
id "org.jetbrains.kotlin.plugin.parcelize" version gradle.ext.kotlinVersion
id "org.jetbrains.kotlin.plugin.allopen" version gradle.ext.kotlinVersion
@@ -27,6 +27,7 @@ pluginManagement {
id 'com.automattic.android.measure-builds' version gradle.ext.measureBuildsVersion
id "org.jetbrains.kotlinx.kover" version gradle.ext.koverVersion
id "com.google.dagger.hilt.android" version gradle.ext.daggerVersion
+ id "com.google.devtools.ksp" version gradle.ext.kspVersion
}
repositories {
maven {