Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 6 additions & 10 deletions WordPress/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ 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"
id "se.bjurr.violations.violation-comments-to-github-gradle-plugin"
id "com.google.gms.google-services"
id "com.google.dagger.hilt.android"
id "org.jetbrains.kotlinx.kover"
id "com.google.devtools.ksp"
}

sentry {
Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -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"
Expand All @@ -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'
Expand Down Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions config/lint/lint.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<!-- TODO: https://github.com/wordpress-mobile/WordPress-Android/issues/18185 -->
<issue id="MissingFirebaseInstanceTokenRefresh">
<!-- GCM -->
<ignore path="**/generated/source/kapt/**/org/wordpress/android/push/Hilt_GCMMessageService.java" />
<ignore path="**/generated/ksp/**/org/wordpress/android/push/Hilt_GCMMessageService.java" />
</issue>
<issue id="GradleDependency" severity="ignore" /> <!-- Dependabot will take care of this -->
<issue id="MissingNullAnnotationOnField">
Expand Down Expand Up @@ -63,7 +63,7 @@
<ignore path="**/src/wordpress/res" /> <!-- drawable-hdpi, drawable-xxhdpi -->
</issue>
<issue id="ObsoleteSdkInt">
<ignore path="**/generated/source/kapt/**/org/wordpress/android/ui/main/Hilt_WPMainNavigationView.java" />
<ignore path="**/generated/ksp/**/org/wordpress/android/ui/main/Hilt_WPMainNavigationView.java" />
</issue>
<!-- INTEROPERABILITY -->
<issue id="UnknownNullness" severity="informational">
Expand Down
9 changes: 5 additions & 4 deletions libs/processors/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
plugins {
id "org.jetbrains.kotlin.jvm"
id "org.jetbrains.kotlin.kapt"
id "org.jetbrains.kotlinx.kover"
}

Expand All @@ -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"
Expand Down
Original file line number Diff line number Diff line change
@@ -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<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

@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<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 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")
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()

if (defaults.isNotEmpty()) {
RemoteFeatureConfigDefaultsBuilder(defaults).getContent()
.writeTo(
codeGenerator,
aggregating = true,
originatingKSFiles = remoteFeatures.map { it.containingFile!! }
)
}
}

@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 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<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 generateFeaturesInDevelopment(resolver: Resolver) {
val featuresInDevelopment =
resolver.getSymbolsWithAnnotation("org.wordpress.android.annotation.FeatureInDevelopment")
.filterIsInstance<KSClassDeclaration>()
.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<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 generateRemoteFeatureConfigCheck(remoteFeatures: List<KSAnnotated>) {
if(remoteFeatures.isNotEmpty()) {
RemoteFeatureConfigCheckBuilder(
remoteFeatures.filterIsInstance<KSClassDeclaration>().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"
}
}
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
Loading