Use Anvil KSP instead of the Square KAPT one (#3564)

* Use Anvil KSP instead of the Square KAPT one

* Fix several configuration cache, lint and test issues

* Allow incremental kotlin compilation in the CI

* Workaround Robolectric + Compose issue that caused `AppNotIdleException`

* Update the `enterprise` commit hash

* Update screenshots

---------

Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
Jorge Martin Espinosa 2024-10-02 13:52:17 +02:00 committed by GitHub
parent f344a1282c
commit 79c17f714f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
54 changed files with 463 additions and 348 deletions

View file

@ -16,22 +16,40 @@ import org.gradle.plugin.use.PluginDependency
/**
* Setup Anvil plugin with the given configuration.
* @param generateDaggerCode whether to enable general Dagger code generation using Kapt
* @param generateDaggerFactoriesUsingAnvil whether to generate Dagger factories using Anvil instead of Kapt
* @param generateDaggerCode whether to enable general Dagger code generation using Kapt. `false` by default.
* @param generateDaggerFactoriesUsingAnvil whether to generate Dagger factories using Anvil instead of Kapt. `true` by default.
* @param componentMergingStrategy how to perform component merging. This is `ComponentMergingStrategy.NONE` by default, which will prevent component merging
* from running.
*/
fun Project.setupAnvil(
generateDaggerCode: Boolean = false,
generateDaggerFactoriesUsingAnvil: Boolean = true,
componentMergingStrategy: ComponentMergingStrategy = ComponentMergingStrategy.NONE,
) {
val libs = the<LibrariesForLibs>()
// Apply plugins and dependencies
// Add dagger dependency, needed for generated code
dependencies.implementation(libs.dagger)
// Apply Anvil plugin and configure it
applyPluginIfNeeded(libs.plugins.anvil)
project.pluginManager.withPlugin(libs.plugins.anvil.get().pluginId) {
// Setup extension
extensions.configure(AnvilExtension::class.java) {
this.generateDaggerFactories.set(generateDaggerFactoriesUsingAnvil)
this.disableComponentMerging.set(componentMergingStrategy == ComponentMergingStrategy.NONE)
useKsp(
contributesAndFactoryGeneration = true,
componentMerging = componentMergingStrategy == ComponentMergingStrategy.KSP,
)
}
}
if (generateDaggerCode) {
applyPluginIfNeeded(libs.plugins.kapt)
// Needed at the top level since dagger code should be generated at a single point for performance
dependencies.implementation(libs.dagger)
dependencies.add("kapt", libs.dagger.compiler)
// Needed at the top level since dagger code should be generated at a single point for performance reasons
dependencies.add("ksp", libs.dagger.compiler)
}
// These dependencies are only needed for compose library or application modules
@ -40,14 +58,7 @@ fun Project.setupAnvil(
// Annotations to generate DI code for Appyx nodes
dependencies.implementation(project.project(":anvilannotations"))
// Code generator for the annotations above
dependencies.add("anvil", project.project(":anvilcodegen"))
}
project.pluginManager.withPlugin(libs.plugins.anvil.get().pluginId) {
// Setup extension
extensions.configure(AnvilExtension::class.java) {
this.generateDaggerFactories.set(generateDaggerFactoriesUsingAnvil)
}
dependencies.add("ksp", project.project(":anvilcodegen"))
}
}
@ -57,3 +68,9 @@ private fun Project.applyPluginIfNeeded(plugin: Provider<PluginDependency>) {
pluginManager.apply(pluginId)
}
}
enum class ComponentMergingStrategy {
NONE,
KAPT,
KSP
}

View file

@ -7,17 +7,16 @@
package extension
import config.AnalyticsConfig
import ModulesConfig
import config.AnalyticsConfig
import org.gradle.accessors.dm.LibrariesForLibs
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.artifacts.ExternalModuleDependency
import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.api.logging.Logger
import org.gradle.kotlin.dsl.DependencyHandlerScope
import org.gradle.kotlin.dsl.closureOf
import org.gradle.kotlin.dsl.project
import java.io.File
private fun DependencyHandlerScope.implementation(dependency: Any) = dependencies.add("implementation", dependency)
internal fun DependencyHandler.implementation(dependency: Any) = add("implementation", dependency)
@ -26,7 +25,7 @@ internal fun DependencyHandler.implementation(dependency: Any) = add("implementa
private fun DependencyHandlerScope.implementation(
dependency: Any,
config: Action<ExternalModuleDependency>
) = dependencies.add("implementation", dependency, closureOf<ExternalModuleDependency> { config.execute(this) })
) = dependencies.add("implementation", dependency, closureOf<ExternalModuleDependency> { config.execute(this) })
private fun DependencyHandlerScope.androidTestImplementation(dependency: Any) = dependencies.add("androidTestImplementation", dependency)
@ -58,26 +57,6 @@ fun DependencyHandlerScope.composeDependencies(libs: LibrariesForLibs) {
implementation(libs.kotlinx.collections.immutable)
}
private fun DependencyHandlerScope.addImplementationProjects(
directory: File,
path: String,
nameFilter: String,
logger: Logger,
) {
directory.listFiles().orEmpty().also { it.sort() }.forEach { file ->
if (file.isDirectory) {
val newPath = "$path:${file.name}"
val buildFile = File(file, "build.gradle.kts")
if (buildFile.exists() && file.name == nameFilter) {
implementation(project(newPath))
logger.lifecycle("Added implementation(project($newPath))")
} else {
addImplementationProjects(file, newPath, nameFilter, logger)
}
}
}
}
fun DependencyHandlerScope.allLibrariesImpl() {
implementation(project(":libraries:androidutils"))
implementation(project(":libraries:deeplink"))
@ -128,22 +107,21 @@ fun DependencyHandlerScope.allServicesImpl() {
}
}
}
implementation(project(":services:apperror:impl"))
implementation(project(":services:appnavstate:impl"))
implementation(project(":services:toolbox:impl"))
}
fun DependencyHandlerScope.allEnterpriseImpl(rootDir: File, logger: Logger) {
val enterpriseDir = File(rootDir, "enterprise")
addImplementationProjects(enterpriseDir, ":enterprise", "impl", logger)
}
fun DependencyHandlerScope.allEnterpriseImpl(project: Project) = addAll(project, "enterprise", "impl")
fun DependencyHandlerScope.allFeaturesApi(rootDir: File, logger: Logger) {
val featuresDir = File(rootDir, "features")
addImplementationProjects(featuresDir, ":features", "api", logger)
}
fun DependencyHandlerScope.allFeaturesImpl(project: Project) = addAll(project, "features", "impl")
fun DependencyHandlerScope.allFeaturesImpl(rootDir: File, logger: Logger) {
val featuresDir = File(rootDir, "features")
addImplementationProjects(featuresDir, ":features", "impl", logger)
fun DependencyHandlerScope.allFeaturesApi(project: Project) = addAll(project, "features", "api")
private fun DependencyHandlerScope.addAll(project: Project, prefix: String, suffix: String) {
val subProjects = project.rootProject.subprojects.filter { it.path.startsWith(":$prefix") && it.path.endsWith(":$suffix") }
for (p in subProjects) {
add("implementation", p)
}
}

View file

@ -14,7 +14,6 @@ import kotlinx.kover.gradle.plugin.dsl.KoverProjectExtension
import kotlinx.kover.gradle.plugin.dsl.KoverVariantCreateConfig
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.configurationcache.extensions.capitalized
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.assign
@ -142,24 +141,18 @@ fun Project.setupKover() {
}
}
filters {
includes {
classes(
"*Presenter",
)
}
excludes {
classes(
"*Fake*Presenter*",
"io.element.android.appnav.loggedin.LoggedInPresenter$*",
// Some options can't be tested at the moment
"io.element.android.features.preferences.impl.developer.DeveloperSettingsPresenter$*",
// Need an Activity to use rememberMultiplePermissionsState
"io.element.android.features.location.impl.common.permissions.DefaultPermissionsPresenter",
"*Presenter\$present\$*",
// Too small to be > 85% tested
"io.element.android.libraries.fullscreenintent.impl.DefaultFullScreenIntentPermissionsPresenter",
)
}
excludes.classes(
"*Fake*Presenter*",
"io.element.android.appnav.loggedin.LoggedInPresenter$*",
// Some options can't be tested at the moment
"io.element.android.features.preferences.impl.developer.DeveloperSettingsPresenter$*",
// Need an Activity to use rememberMultiplePermissionsState
"io.element.android.features.location.impl.common.permissions.DefaultPermissionsPresenter",
"*Presenter\$present\$*",
// Too small to be > 85% tested
"io.element.android.libraries.fullscreenintent.impl.DefaultFullScreenIntentPermissionsPresenter",
)
includes.inheritedFrom("io.element.android.libraries.architecture.Presenter")
}
}
variant(KoverVariant.States.variantName) {
@ -175,33 +168,31 @@ fun Project.setupKover() {
}
}
filters {
includes {
classes(
"^*State$",
)
}
excludes {
classes(
"io.element.android.appnav.root.RootNavState*",
"io.element.android.libraries.matrix.api.timeline.item.event.OtherState$*",
"io.element.android.libraries.matrix.api.timeline.item.event.EventSendState$*",
"io.element.android.libraries.matrix.api.room.RoomMembershipState*",
"io.element.android.libraries.matrix.api.room.MatrixRoomMembersState*",
"io.element.android.libraries.push.impl.notifications.NotificationState*",
"io.element.android.features.messages.impl.media.local.pdf.PdfViewerState",
"io.element.android.features.messages.impl.media.local.LocalMediaViewState",
"io.element.android.features.location.impl.map.MapState*",
"io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState*",
"io.element.android.libraries.designsystem.swipe.SwipeableActionsState*",
"io.element.android.features.messages.impl.timeline.components.ExpandableState*",
"io.element.android.features.messages.impl.timeline.model.bubble.BubbleState*",
"io.element.android.libraries.maplibre.compose.CameraPositionState*",
"io.element.android.libraries.maplibre.compose.SaveableCameraPositionState",
"io.element.android.libraries.maplibre.compose.SymbolState*",
"io.element.android.features.ftue.api.state.*",
"io.element.android.features.ftue.impl.welcome.state.*",
)
}
excludes.classes(
"*State$*", // Exclude inner classes
"io.element.android.appnav.root.RootNavState*",
"io.element.android.libraries.matrix.api.timeline.item.event.OtherState$*",
"io.element.android.libraries.matrix.api.timeline.item.event.EventSendState$*",
"io.element.android.libraries.matrix.api.room.RoomMembershipState*",
"io.element.android.libraries.matrix.api.room.MatrixRoomMembersState*",
"io.element.android.libraries.push.impl.notifications.NotificationState*",
"io.element.android.features.messages.impl.media.local.pdf.PdfViewerState",
"io.element.android.features.messages.impl.media.local.LocalMediaViewState",
"io.element.android.features.location.impl.map.MapState*",
"io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState*",
"io.element.android.libraries.designsystem.swipe.SwipeableActionsState*",
"io.element.android.features.messages.impl.timeline.components.ExpandableState*",
"io.element.android.features.messages.impl.timeline.model.bubble.BubbleState*",
"io.element.android.libraries.maplibre.compose.CameraPositionState*",
"io.element.android.libraries.maplibre.compose.SaveableCameraPositionState",
"io.element.android.libraries.maplibre.compose.SymbolState*",
"io.element.android.features.ftue.api.state.*",
"io.element.android.features.ftue.impl.welcome.state.*",
"io.element.android.libraries.designsystem.theme.components.bottomsheet.CustomSheetState",
"io.element.android.libraries.mediaviewer.api.local.pdf.PdfViewerState",
"io.element.android.libraries.textcomposer.model.TextEditorState",
)
includes.classes("*State")
}
}
variant(KoverVariant.Views.variantName) {
@ -218,11 +209,8 @@ fun Project.setupKover() {
}
}
filters {
includes {
classes(
"*ViewKt",
)
}
excludes.classes("*ViewKt$*") // Exclude inner classes
includes.classes("*ViewKt")
}
}
}
@ -236,16 +224,31 @@ fun Project.applyKoverPluginToAllSubProjects() = rootProject.subprojects {
currentProject {
for (variant in koverVariants) {
createVariant(variant) {
defaultVariants()
defaultVariants(project)
}
}
}
}
project.afterEvaluate {
for (variant in koverVariants) {
// Using the cache for coverage verification seems to be flaky, so we disable it for now.
val taskName = "koverCachedVerify${variant.replaceFirstChar(Char::titlecase)}"
val cachedTask = project.tasks.findByName(taskName)
cachedTask?.let {
it.outputs.upToDateWhen { false }
}
}
}
}
}
fun KoverVariantCreateConfig.defaultVariants() {
addWithDependencies("gplayDebug", "debug", optional = true)
fun KoverVariantCreateConfig.defaultVariants(project: Project) {
if (project.name == "app") {
addWithDependencies("gplayDebug")
} else {
addWithDependencies("debug", "jvm", optional = true)
}
}
fun Project.koverSubprojects() = project.rootProject.subprojects