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:
committed by
GitHub
parent
5fcc80a383
commit
4a43fcb69a
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -9,7 +9,7 @@ on:
|
||||
|
||||
# Enrich gradle.properties for CI/CD
|
||||
env:
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dkotlin.incremental=false
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true
|
||||
|
||||
jobs:
|
||||
|
||||
2
.github/workflows/build_enterprise.yml
vendored
2
.github/workflows/build_enterprise.yml
vendored
@@ -9,7 +9,7 @@ on:
|
||||
|
||||
# Enrich gradle.properties for CI/CD
|
||||
env:
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dkotlin.incremental=false
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true
|
||||
|
||||
jobs:
|
||||
|
||||
2
.github/workflows/maestro.yml
vendored
2
.github/workflows/maestro.yml
vendored
@@ -8,7 +8,7 @@ on:
|
||||
|
||||
# Enrich gradle.properties for CI/CD
|
||||
env:
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dkotlin.incremental=false
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true
|
||||
|
||||
jobs:
|
||||
|
||||
2
.github/workflows/nightly.yml
vendored
2
.github/workflows/nightly.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
- cron: "0 4 * * *"
|
||||
|
||||
env:
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dkotlin.incremental=false
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true
|
||||
|
||||
jobs:
|
||||
|
||||
2
.github/workflows/nightlyReports.yml
vendored
2
.github/workflows/nightlyReports.yml
vendored
@@ -8,7 +8,7 @@ on:
|
||||
|
||||
# Enrich gradle.properties for CI/CD
|
||||
env:
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dkotlin.incremental=false
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true
|
||||
|
||||
jobs:
|
||||
|
||||
2
.github/workflows/nightly_enterprise.yml
vendored
2
.github/workflows/nightly_enterprise.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
- cron: "0 4 * * *"
|
||||
|
||||
env:
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dkotlin.incremental=false
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true
|
||||
|
||||
jobs:
|
||||
|
||||
2
.github/workflows/quality.yml
vendored
2
.github/workflows/quality.yml
vendored
@@ -9,7 +9,7 @@ on:
|
||||
|
||||
# Enrich gradle.properties for CI/CD
|
||||
env:
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dkotlin.incremental=false
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true
|
||||
|
||||
jobs:
|
||||
|
||||
2
.github/workflows/recordScreenshots.yml
vendored
2
.github/workflows/recordScreenshots.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
# Enrich gradle.properties for CI/CD
|
||||
env:
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dkotlin.incremental=false -Dsonar.gradle.skipCompile=true
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dsonar.gradle.skipCompile=true
|
||||
|
||||
jobs:
|
||||
record:
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
|
||||
# Enrich gradle.properties for CI/CD
|
||||
env:
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dkotlin.incremental=false
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true
|
||||
|
||||
jobs:
|
||||
|
||||
2
.github/workflows/sonar.yml
vendored
2
.github/workflows/sonar.yml
vendored
@@ -9,7 +9,7 @@ on:
|
||||
|
||||
# Enrich gradle.properties for CI/CD
|
||||
env:
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dkotlin.incremental=false
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace --warn -Dsonar.gradle.skipCompile=true
|
||||
GROUP: ${{ format('sonar-{0}', github.ref) }}
|
||||
|
||||
|
||||
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@@ -9,7 +9,7 @@ on:
|
||||
|
||||
# Enrich gradle.properties for CI/CD
|
||||
env:
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx7g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.incremental=false -Dkotlin.daemon.jvm.options=-Xmx2g -XX:+UseG1GC
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx7g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options=-Xmx2g -XX:+UseG1GC
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.jvm)
|
||||
alias(libs.plugins.kapt)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -16,6 +15,6 @@ dependencies {
|
||||
implementation(libs.anvil.compiler.utils)
|
||||
implementation(libs.kotlinpoet)
|
||||
implementation(libs.dagger)
|
||||
compileOnly(libs.google.autoservice.annotations)
|
||||
kapt(libs.google.autoservice)
|
||||
implementation(libs.ksp.plugin)
|
||||
implementation(libs.kotlinpoet.ksp)
|
||||
}
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalAnvilApi::class)
|
||||
|
||||
package io.element.android.anvilcodegen
|
||||
|
||||
import com.google.auto.service.AutoService
|
||||
import com.squareup.anvil.annotations.ContributesTo
|
||||
import com.squareup.anvil.annotations.ExperimentalAnvilApi
|
||||
import com.squareup.anvil.compiler.api.AnvilCompilationException
|
||||
import com.squareup.anvil.compiler.api.AnvilContext
|
||||
import com.squareup.anvil.compiler.api.CodeGenerator
|
||||
import com.squareup.anvil.compiler.api.GeneratedFile
|
||||
import com.squareup.anvil.compiler.api.createGeneratedFile
|
||||
import com.squareup.anvil.compiler.internal.asClassName
|
||||
import com.squareup.anvil.compiler.internal.buildFile
|
||||
import com.squareup.anvil.compiler.internal.fqName
|
||||
import com.squareup.anvil.compiler.internal.reference.ClassReference
|
||||
import com.squareup.anvil.compiler.internal.reference.asClassName
|
||||
import com.squareup.anvil.compiler.internal.reference.classAndInnerClassReferences
|
||||
import com.squareup.kotlinpoet.AnnotationSpec
|
||||
import com.squareup.kotlinpoet.ClassName
|
||||
import com.squareup.kotlinpoet.FileSpec
|
||||
import com.squareup.kotlinpoet.FunSpec
|
||||
import com.squareup.kotlinpoet.KModifier
|
||||
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
|
||||
import com.squareup.kotlinpoet.STAR
|
||||
import com.squareup.kotlinpoet.TypeSpec
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import dagger.multibindings.IntoMap
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* This is an anvil plugin that allows Node to use [ContributesNode] alone and let this plugin automatically
|
||||
* handle the rest of the Dagger wiring required for constructor injection.
|
||||
*/
|
||||
@AutoService(CodeGenerator::class)
|
||||
class ContributesNodeCodeGenerator : CodeGenerator {
|
||||
override fun isApplicable(context: AnvilContext): Boolean = true
|
||||
|
||||
override fun generateCode(codeGenDir: File, module: ModuleDescriptor, projectFiles: Collection<KtFile>): Collection<GeneratedFile> {
|
||||
return projectFiles.classAndInnerClassReferences(module)
|
||||
.filter { it.isAnnotatedWith(ContributesNode::class.fqName) }
|
||||
.flatMap { listOf(generateModule(it, codeGenDir, module), generateAssistedFactory(it, codeGenDir, module)) }
|
||||
.toList()
|
||||
}
|
||||
|
||||
private fun generateModule(nodeClass: ClassReference.Psi, codeGenDir: File, module: ModuleDescriptor): GeneratedFile {
|
||||
val generatedPackage = nodeClass.packageFqName.toString()
|
||||
val moduleClassName = "${nodeClass.shortName}_Module"
|
||||
val scope = nodeClass.annotations.single { it.fqName == ContributesNode::class.fqName }.scope()
|
||||
val content = FileSpec.buildFile(generatedPackage, moduleClassName) {
|
||||
addType(
|
||||
TypeSpec.classBuilder(moduleClassName)
|
||||
.addModifiers(KModifier.ABSTRACT)
|
||||
.addAnnotation(Module::class)
|
||||
.addAnnotation(AnnotationSpec.builder(ContributesTo::class).addMember("%T::class", scope.asClassName()).build())
|
||||
.addFunction(
|
||||
FunSpec.builder("bind${nodeClass.shortName}Factory")
|
||||
.addModifiers(KModifier.ABSTRACT)
|
||||
.addParameter("factory", ClassName(generatedPackage, "${nodeClass.shortName}_AssistedFactory"))
|
||||
.returns(assistedNodeFactoryFqName.asClassName(module).parameterizedBy(STAR))
|
||||
.addAnnotation(Binds::class)
|
||||
.addAnnotation(IntoMap::class)
|
||||
.addAnnotation(
|
||||
AnnotationSpec.Companion.builder(nodeKeyFqName.asClassName(module)).addMember(
|
||||
"%T::class",
|
||||
nodeClass.asClassName()
|
||||
).build()
|
||||
)
|
||||
.build(),
|
||||
)
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
return createGeneratedFile(codeGenDir, generatedPackage, moduleClassName, content)
|
||||
}
|
||||
|
||||
private fun generateAssistedFactory(nodeClass: ClassReference.Psi, codeGenDir: File, module: ModuleDescriptor): GeneratedFile {
|
||||
val generatedPackage = nodeClass.packageFqName.toString()
|
||||
val assistedFactoryClassName = "${nodeClass.shortName}_AssistedFactory"
|
||||
val constructor = nodeClass.constructors.singleOrNull { it.isAnnotatedWith(AssistedInject::class.fqName) }
|
||||
val assistedParameters = constructor?.parameters?.filter { it.isAnnotatedWith(Assisted::class.fqName) }.orEmpty()
|
||||
if (constructor == null || assistedParameters.size != 2) {
|
||||
throw AnvilCompilationException(
|
||||
"${nodeClass.fqName} must have an @AssistedInject constructor with 2 @Assisted parameters",
|
||||
element = nodeClass.clazz,
|
||||
)
|
||||
}
|
||||
val contextAssistedParam = assistedParameters[0]
|
||||
if (contextAssistedParam.name != "buildContext") {
|
||||
throw AnvilCompilationException(
|
||||
"${nodeClass.fqName} @Assisted parameter must be named buildContext",
|
||||
element = contextAssistedParam.parameter,
|
||||
)
|
||||
}
|
||||
val pluginsAssistedParam = assistedParameters[1]
|
||||
if (pluginsAssistedParam.name != "plugins") {
|
||||
throw AnvilCompilationException(
|
||||
"${nodeClass.fqName} @Assisted parameter must be named plugins",
|
||||
element = pluginsAssistedParam.parameter,
|
||||
)
|
||||
}
|
||||
|
||||
val nodeClassName = nodeClass.asClassName()
|
||||
val buildContextClassName = contextAssistedParam.type().asTypeName()
|
||||
val pluginsClassName = pluginsAssistedParam.type().asTypeName()
|
||||
val content = FileSpec.buildFile(generatedPackage, assistedFactoryClassName) {
|
||||
addType(
|
||||
TypeSpec.interfaceBuilder(assistedFactoryClassName)
|
||||
.addSuperinterface(assistedNodeFactoryFqName.asClassName(module).parameterizedBy(nodeClassName))
|
||||
.addAnnotation(AssistedFactory::class)
|
||||
.addFunction(
|
||||
FunSpec.builder("create")
|
||||
.addModifiers(KModifier.OVERRIDE, KModifier.ABSTRACT)
|
||||
.addParameter("buildContext", buildContextClassName)
|
||||
.addParameter("plugins", pluginsClassName)
|
||||
.returns(nodeClassName)
|
||||
.build(),
|
||||
)
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
return createGeneratedFile(codeGenDir, generatedPackage, assistedFactoryClassName, content)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val assistedNodeFactoryFqName = FqName("io.element.android.libraries.architecture.AssistedNodeFactory")
|
||||
private val nodeKeyFqName = FqName("io.element.android.libraries.architecture.NodeKey")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright 2022-2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.anvilcodegen
|
||||
|
||||
import com.google.devtools.ksp.KspExperimental
|
||||
import com.google.devtools.ksp.getConstructors
|
||||
import com.google.devtools.ksp.isAnnotationPresent
|
||||
import com.google.devtools.ksp.processing.CodeGenerator
|
||||
import com.google.devtools.ksp.processing.Dependencies
|
||||
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.google.devtools.ksp.symbol.KSType
|
||||
import com.google.devtools.ksp.validate
|
||||
import com.squareup.anvil.annotations.ContributesTo
|
||||
import com.squareup.kotlinpoet.AnnotationSpec
|
||||
import com.squareup.kotlinpoet.ClassName
|
||||
import com.squareup.kotlinpoet.FileSpec
|
||||
import com.squareup.kotlinpoet.FunSpec
|
||||
import com.squareup.kotlinpoet.KModifier
|
||||
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
|
||||
import com.squareup.kotlinpoet.STAR
|
||||
import com.squareup.kotlinpoet.TypeSpec
|
||||
import com.squareup.kotlinpoet.ksp.toTypeName
|
||||
import com.squareup.kotlinpoet.ksp.writeTo
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import dagger.multibindings.IntoMap
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
|
||||
class ContributesNodeProcessor(
|
||||
private val logger: KSPLogger,
|
||||
private val codeGenerator: CodeGenerator,
|
||||
private val config: Config,
|
||||
) : SymbolProcessor {
|
||||
data class Config(
|
||||
val enableLogging: Boolean = false,
|
||||
)
|
||||
|
||||
override fun process(resolver: Resolver): List<KSAnnotated> {
|
||||
val annotatedSymbols = resolver.getSymbolsWithAnnotation(ContributesNode::class.qualifiedName!!)
|
||||
.filterIsInstance<KSClassDeclaration>()
|
||||
|
||||
val (validSymbols, invalidSymbols) = annotatedSymbols.partition { it.validate() }
|
||||
|
||||
if (validSymbols.isEmpty()) return invalidSymbols
|
||||
|
||||
for (ksClass in validSymbols) {
|
||||
if (config.enableLogging) {
|
||||
logger.warn("Processing ${ksClass.qualifiedName?.asString()}")
|
||||
}
|
||||
generateModule(ksClass)
|
||||
generateFactory(ksClass)
|
||||
}
|
||||
|
||||
return invalidSymbols
|
||||
}
|
||||
|
||||
private fun generateModule(ksClass: KSClassDeclaration) {
|
||||
val annotation = ksClass.annotations.find { it.shortName.asString() == "ContributesNode" }!!
|
||||
val scope = annotation.arguments.find { it.name?.asString() == "scope" }!!.value as KSType
|
||||
val modulePackage = ksClass.packageName.asString()
|
||||
val moduleClassName = "${ksClass.simpleName.asString()}_Module"
|
||||
val content = FileSpec.builder(
|
||||
packageName = modulePackage,
|
||||
fileName = moduleClassName,
|
||||
)
|
||||
.addType(
|
||||
TypeSpec.classBuilder(moduleClassName)
|
||||
.addModifiers(KModifier.ABSTRACT)
|
||||
.addAnnotation(Module::class)
|
||||
.addAnnotation(AnnotationSpec.builder(ContributesTo::class).addMember("%T::class", scope.toTypeName()).build())
|
||||
.addFunction(
|
||||
FunSpec.builder("bind${ksClass.simpleName.asString()}Factory")
|
||||
.addModifiers(KModifier.ABSTRACT)
|
||||
.addParameter("factory", ClassName(modulePackage, "${ksClass.simpleName.asString()}_AssistedFactory"))
|
||||
.returns(ClassName.bestGuess(assistedNodeFactoryFqName.asString()).parameterizedBy(STAR))
|
||||
.addAnnotation(Binds::class)
|
||||
.addAnnotation(IntoMap::class)
|
||||
.addAnnotation(
|
||||
AnnotationSpec.Companion.builder(ClassName.bestGuess(nodeKeyFqName.asString())).addMember(
|
||||
"%T::class",
|
||||
ClassName.bestGuess(ksClass.qualifiedName!!.asString())
|
||||
).build()
|
||||
)
|
||||
.build(),
|
||||
)
|
||||
.build(),
|
||||
)
|
||||
.build()
|
||||
|
||||
content.writeTo(
|
||||
codeGenerator = codeGenerator,
|
||||
dependencies = Dependencies(
|
||||
aggregating = true,
|
||||
ksClass.containingFile!!
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(KspExperimental::class)
|
||||
private fun generateFactory(ksClass: KSClassDeclaration) {
|
||||
val generatedPackage = ksClass.packageName.asString()
|
||||
val assistedFactoryClassName = "${ksClass.simpleName.asString()}_AssistedFactory"
|
||||
val constructor = ksClass.getConstructors().singleOrNull { it.isAnnotationPresent(AssistedInject::class) }
|
||||
val assistedParameters = constructor?.parameters?.filter { it.isAnnotationPresent(Assisted::class) }.orEmpty()
|
||||
if (constructor == null || assistedParameters.size != 2) {
|
||||
error(
|
||||
"${ksClass.qualifiedName} must have an @AssistedInject constructor with 2 @Assisted parameters",
|
||||
)
|
||||
}
|
||||
val contextAssistedParam = assistedParameters[0]
|
||||
if (contextAssistedParam.name?.asString() != "buildContext") {
|
||||
error(
|
||||
"${ksClass.qualifiedName} @Assisted parameter must be named buildContext",
|
||||
)
|
||||
}
|
||||
val pluginsAssistedParam = assistedParameters[1]
|
||||
if (pluginsAssistedParam.name?.asString() != "plugins") {
|
||||
error(
|
||||
"${ksClass.qualifiedName} @Assisted parameter must be named plugins",
|
||||
)
|
||||
}
|
||||
|
||||
val nodeClassName = ClassName.bestGuess(ksClass.qualifiedName!!.asString())
|
||||
val buildContextClassName = contextAssistedParam.type.toTypeName()
|
||||
val pluginsClassName = pluginsAssistedParam.type.toTypeName()
|
||||
val content = FileSpec.builder(generatedPackage, assistedFactoryClassName)
|
||||
.addType(
|
||||
TypeSpec.interfaceBuilder(assistedFactoryClassName)
|
||||
.addSuperinterface(ClassName.bestGuess(assistedNodeFactoryFqName.asString()).parameterizedBy(nodeClassName))
|
||||
.addAnnotation(AssistedFactory::class)
|
||||
.addFunction(
|
||||
FunSpec.builder("create")
|
||||
.addModifiers(KModifier.OVERRIDE, KModifier.ABSTRACT)
|
||||
.addParameter("buildContext", buildContextClassName)
|
||||
.addParameter("plugins", pluginsClassName)
|
||||
.returns(nodeClassName)
|
||||
.build(),
|
||||
)
|
||||
.build(),
|
||||
)
|
||||
.build()
|
||||
|
||||
content.writeTo(
|
||||
codeGenerator = codeGenerator,
|
||||
dependencies = Dependencies(
|
||||
aggregating = true,
|
||||
ksClass.containingFile!!
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val assistedNodeFactoryFqName = FqName("io.element.android.libraries.architecture.AssistedNodeFactory")
|
||||
private val nodeKeyFqName = FqName("io.element.android.libraries.architecture.NodeKey")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2022-2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.anvilcodegen
|
||||
|
||||
import com.google.devtools.ksp.processing.SymbolProcessor
|
||||
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
|
||||
import com.google.devtools.ksp.processing.SymbolProcessorProvider
|
||||
|
||||
class ContributesNodeProcessorProvider : SymbolProcessorProvider {
|
||||
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
|
||||
val enableLogging = environment.options["enableLogging"]?.toBoolean() ?: false
|
||||
return ContributesNodeProcessor(
|
||||
logger = environment.logger,
|
||||
codeGenerator = environment.codeGenerator,
|
||||
config = ContributesNodeProcessor.Config(enableLogging = enableLogging),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
io.element.android.anvilcodegen.ContributesNodeProcessorProvider
|
||||
@@ -11,6 +11,7 @@ import com.android.build.api.variant.FilterConfiguration.FilterType.ABI
|
||||
import com.android.build.gradle.internal.tasks.factory.dependsOn
|
||||
import com.android.build.gradle.tasks.GenerateBuildConfig
|
||||
import extension.AssetCopyTask
|
||||
import extension.ComponentMergingStrategy
|
||||
import extension.GitBranchNameValueSource
|
||||
import extension.GitRevisionValueSource
|
||||
import extension.allEnterpriseImpl
|
||||
@@ -235,22 +236,24 @@ knit {
|
||||
setupAnvil(
|
||||
generateDaggerCode = true,
|
||||
generateDaggerFactoriesUsingAnvil = false,
|
||||
componentMergingStrategy = ComponentMergingStrategy.KSP,
|
||||
)
|
||||
|
||||
dependencies {
|
||||
allLibrariesImpl()
|
||||
allServicesImpl()
|
||||
if (isEnterpriseBuild) {
|
||||
allEnterpriseImpl(rootDir, logger)
|
||||
allEnterpriseImpl(project)
|
||||
implementation(projects.appicon.enterprise)
|
||||
} else {
|
||||
implementation(projects.appicon.element)
|
||||
}
|
||||
allFeaturesImpl(rootDir, logger)
|
||||
allFeaturesImpl(project)
|
||||
implementation(projects.features.migration.api)
|
||||
implementation(projects.appnav)
|
||||
implementation(projects.appconfig)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
implementation(projects.services.analytics.compose)
|
||||
|
||||
if (ModulesConfig.pushProvidersConfig.includeFirebase) {
|
||||
"gplayImplementation"(projects.libraries.pushproviders.firebase)
|
||||
@@ -275,6 +278,8 @@ dependencies {
|
||||
implementation(libs.serialization.json)
|
||||
|
||||
implementation(libs.matrix.emojibase.bindings)
|
||||
// Needed for UtdTracker
|
||||
implementation(libs.matrix.sdk)
|
||||
|
||||
testImplementation(libs.test.junit)
|
||||
testImplementation(libs.test.robolectric)
|
||||
|
||||
@@ -10,7 +10,6 @@ package io.element.android.x.di
|
||||
import android.content.Context
|
||||
import com.squareup.anvil.annotations.MergeComponent
|
||||
import dagger.BindsInstance
|
||||
import dagger.Component
|
||||
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.ApplicationContext
|
||||
@@ -19,7 +18,7 @@ import io.element.android.libraries.di.SingleIn
|
||||
@SingleIn(AppScope::class)
|
||||
@MergeComponent(AppScope::class)
|
||||
interface AppComponent : NodeFactoriesBindings {
|
||||
@Component.Factory
|
||||
@MergeComponent.Factory
|
||||
interface Factory {
|
||||
fun create(
|
||||
@ApplicationContext @BindsInstance
|
||||
|
||||
@@ -10,7 +10,6 @@ package io.element.android.x.di
|
||||
import com.squareup.anvil.annotations.ContributesTo
|
||||
import com.squareup.anvil.annotations.MergeSubcomponent
|
||||
import dagger.BindsInstance
|
||||
import dagger.Subcomponent
|
||||
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
@@ -20,7 +19,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
@SingleIn(RoomScope::class)
|
||||
@MergeSubcomponent(RoomScope::class)
|
||||
interface RoomComponent : NodeFactoriesBindings {
|
||||
@Subcomponent.Builder
|
||||
@MergeSubcomponent.Builder
|
||||
interface Builder {
|
||||
@BindsInstance
|
||||
fun room(room: MatrixRoom): Builder
|
||||
|
||||
@@ -10,7 +10,6 @@ package io.element.android.x.di
|
||||
import com.squareup.anvil.annotations.ContributesTo
|
||||
import com.squareup.anvil.annotations.MergeSubcomponent
|
||||
import dagger.BindsInstance
|
||||
import dagger.Subcomponent
|
||||
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
@@ -20,7 +19,7 @@ import io.element.android.libraries.matrix.api.MatrixClient
|
||||
@SingleIn(SessionScope::class)
|
||||
@MergeSubcomponent(SessionScope::class)
|
||||
interface SessionComponent : NodeFactoriesBindings {
|
||||
@Subcomponent.Builder
|
||||
@MergeSubcomponent.Builder
|
||||
interface Builder {
|
||||
@BindsInstance
|
||||
fun client(matrixClient: MatrixClient): Builder
|
||||
|
||||
@@ -22,7 +22,7 @@ android {
|
||||
setupAnvil()
|
||||
|
||||
dependencies {
|
||||
allFeaturesApi(rootDir, logger)
|
||||
allFeaturesApi(project)
|
||||
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.androidutils)
|
||||
|
||||
@@ -71,8 +71,9 @@ allprojects {
|
||||
// To have XML report for Danger
|
||||
reporter(org.jlleitschuh.gradle.ktlint.reporter.ReporterType.CHECKSTYLE)
|
||||
}
|
||||
val generatedPath = "${layout.buildDirectory.asFile.get()}/generated/"
|
||||
filter {
|
||||
exclude { element -> element.file.path.contains("${layout.buildDirectory.asFile.get()}/generated/") }
|
||||
exclude { element -> element.file.path.contains(generatedPath) }
|
||||
}
|
||||
}
|
||||
// Dependency check
|
||||
|
||||
Submodule enterprise updated: ceb65e32d9...b4f0427e35
@@ -1,3 +1,4 @@
|
||||
import extension.ComponentMergingStrategy
|
||||
import extension.setupAnvil
|
||||
|
||||
/*
|
||||
@@ -22,7 +23,7 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
setupAnvil()
|
||||
setupAnvil(componentMergingStrategy = ComponentMergingStrategy.KSP)
|
||||
|
||||
dependencies {
|
||||
implementation(projects.libraries.core)
|
||||
|
||||
@@ -9,7 +9,6 @@ package io.element.android.features.createroom.impl.di
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesTo
|
||||
import com.squareup.anvil.annotations.MergeSubcomponent
|
||||
import dagger.Subcomponent
|
||||
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.di.SingleIn
|
||||
@@ -17,7 +16,7 @@ import io.element.android.libraries.di.SingleIn
|
||||
@SingleIn(CreateRoomScope::class)
|
||||
@MergeSubcomponent(CreateRoomScope::class)
|
||||
interface CreateRoomComponent : NodeFactoriesBindings {
|
||||
@Subcomponent.Builder
|
||||
@MergeSubcomponent.Builder
|
||||
interface Builder {
|
||||
fun build(): CreateRoomComponent
|
||||
}
|
||||
|
||||
@@ -7,8 +7,10 @@
|
||||
|
||||
package io.element.android.features.lockscreen.impl.unlock
|
||||
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.lockscreen.impl.biometric.BiometricUnlock
|
||||
import io.element.android.features.lockscreen.impl.biometric.BiometricUnlockError
|
||||
import io.element.android.features.lockscreen.impl.pin.model.PinEntry
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
@@ -23,6 +25,9 @@ open class PinUnlockStateProvider : PreviewParameterProvider<PinUnlockState> {
|
||||
aPinUnlockState(showBiometricUnlock = false),
|
||||
aPinUnlockState(showSignOutPrompt = true, remainingAttempts = 0),
|
||||
aPinUnlockState(signOutAction = AsyncAction.Loading),
|
||||
aPinUnlockState(biometricUnlockResult = BiometricUnlock.AuthenticationResult.Failure(
|
||||
BiometricUnlockError(BiometricPrompt.ERROR_LOCKOUT, "Biometric auth disabled")
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import extension.ComponentMergingStrategy
|
||||
import extension.setupAnvil
|
||||
|
||||
/*
|
||||
@@ -23,7 +24,7 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
setupAnvil()
|
||||
setupAnvil(componentMergingStrategy = ComponentMergingStrategy.KSP)
|
||||
|
||||
dependencies {
|
||||
implementation(projects.appconfig)
|
||||
|
||||
@@ -9,7 +9,6 @@ package io.element.android.features.login.impl.di
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesTo
|
||||
import com.squareup.anvil.annotations.MergeSubcomponent
|
||||
import dagger.Subcomponent
|
||||
import io.element.android.libraries.architecture.NodeFactoriesBindings
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.SingleIn
|
||||
@@ -17,7 +16,7 @@ import io.element.android.libraries.di.SingleIn
|
||||
@SingleIn(QrCodeLoginScope::class)
|
||||
@MergeSubcomponent(QrCodeLoginScope::class)
|
||||
interface QrCodeLoginComponent : NodeFactoriesBindings {
|
||||
@Subcomponent.Builder
|
||||
@MergeSubcomponent.Builder
|
||||
interface Builder {
|
||||
fun build(): QrCodeLoginComponent
|
||||
}
|
||||
|
||||
@@ -16,15 +16,15 @@ import io.element.android.features.login.impl.qrcode.QrCodeLoginManager
|
||||
import io.element.android.libraries.architecture.AssistedNodeFactory
|
||||
import io.element.android.libraries.architecture.createNode
|
||||
|
||||
internal class FakeQrCodeLoginComponent(private val qrCodeLoginManager: QrCodeLoginManager) :
|
||||
QrCodeLoginComponent {
|
||||
internal class FakeMergedQrCodeLoginComponent(private val qrCodeLoginManager: QrCodeLoginManager) :
|
||||
MergedQrCodeLoginComponent {
|
||||
// Ignore this error, it does override a method once code generation is done
|
||||
override fun qrCodeLoginManager(): QrCodeLoginManager = qrCodeLoginManager
|
||||
|
||||
class Builder(private val qrCodeLoginManager: QrCodeLoginManager = FakeQrCodeLoginManager()) :
|
||||
QrCodeLoginComponent.Builder {
|
||||
override fun build(): QrCodeLoginComponent {
|
||||
return FakeQrCodeLoginComponent(qrCodeLoginManager)
|
||||
return FakeMergedQrCodeLoginComponent(qrCodeLoginManager)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.utils.customisations.NodeCustomisationDirectoryImpl
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.login.impl.DefaultLoginUserStory
|
||||
import io.element.android.features.login.impl.di.FakeQrCodeLoginComponent
|
||||
import io.element.android.features.login.impl.di.FakeMergedQrCodeLoginComponent
|
||||
import io.element.android.features.login.impl.screens.qrcode.confirmation.QrCodeConfirmationStep
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep
|
||||
@@ -200,7 +200,7 @@ class QrCodeLoginFlowNodeTest {
|
||||
return QrCodeLoginFlowNode(
|
||||
buildContext = buildContext,
|
||||
plugins = emptyList(),
|
||||
qrCodeLoginComponentBuilder = FakeQrCodeLoginComponent.Builder(qrCodeLoginManager),
|
||||
qrCodeLoginComponentBuilder = FakeMergedQrCodeLoginComponent.Builder(qrCodeLoginManager),
|
||||
defaultLoginUserStory = defaultLoginUserStory,
|
||||
coroutineDispatchers = coroutineDispatchers,
|
||||
)
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.messages.impl.timeline.session
|
||||
|
||||
data class SessionState(
|
||||
val isSessionVerified: Boolean,
|
||||
val isKeyBackupEnabled: Boolean,
|
||||
)
|
||||
@@ -28,6 +28,7 @@ import io.element.android.tests.testutils.EventsRecorder
|
||||
import io.element.android.tests.testutils.ensureCalledOnce
|
||||
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
||||
import io.element.android.tests.testutils.pressBack
|
||||
import io.element.android.tests.testutils.setSafeContent
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TestRule
|
||||
@@ -100,7 +101,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setPinne
|
||||
onUserDataClick: (UserId) -> Unit = EnsureNeverCalledWithParam(),
|
||||
onLinkClick: (String) -> Unit = EnsureNeverCalledWithParam(),
|
||||
) {
|
||||
setContent {
|
||||
setSafeContent {
|
||||
PinnedMessagesListView(
|
||||
state = state,
|
||||
onBackClick = onBackClick,
|
||||
|
||||
@@ -29,6 +29,7 @@ import io.element.android.tests.testutils.EnsureNeverCalledWithParam
|
||||
import io.element.android.tests.testutils.EnsureNeverCalledWithTwoParams
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
import io.element.android.tests.testutils.clickOn
|
||||
import io.element.android.tests.testutils.setSafeContent
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
@@ -151,7 +152,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setTimel
|
||||
onJoinCallClick: () -> Unit = EnsureNeverCalled(),
|
||||
forceJumpToBottomVisibility: Boolean = false,
|
||||
) {
|
||||
setContent {
|
||||
setSafeContent {
|
||||
TimelineView(
|
||||
state = state,
|
||||
typingNotificationState = typingNotificationState,
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
|
||||
open class InvitesStateProvider : PreviewParameterProvider<InvitesState> {
|
||||
override val values: Sequence<InvitesState>
|
||||
get() = sequenceOf(
|
||||
InvitesState.SeenInvites,
|
||||
InvitesState.NewInvites,
|
||||
)
|
||||
}
|
||||
@@ -51,12 +51,6 @@ data class RoomListState(
|
||||
}
|
||||
}
|
||||
|
||||
enum class InvitesState {
|
||||
NoInvites,
|
||||
SeenInvites,
|
||||
NewInvites,
|
||||
}
|
||||
|
||||
enum class SecurityBannerState {
|
||||
None,
|
||||
SetUpRecovery,
|
||||
|
||||
@@ -47,6 +47,7 @@ class ResetIdentityFlowManagerTest {
|
||||
var result: AsyncData.Success<IdentityResetHandle>? = null
|
||||
flowManager.getResetHandle().test {
|
||||
assertThat(awaitItem().isLoading()).isTrue()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
result = awaitItem() as? AsyncData.Success<IdentityResetHandle>
|
||||
assertThat(result).isNotNull()
|
||||
}
|
||||
|
||||
@@ -25,12 +25,13 @@ kotlin.code.style=official
|
||||
# thereby reducing the size of the R class for that library
|
||||
android.nonTransitiveRClass=true
|
||||
|
||||
org.gradle.caching=true
|
||||
org.gradle.configureondemand=true
|
||||
org.gradle.parallel=true
|
||||
# Check here for the reasons https://github.com/square/anvil/issues/693
|
||||
# useClasspathSnapshot=false is not enough in most cases.
|
||||
kotlin.incremental=false
|
||||
|
||||
# Caching
|
||||
org.gradle.caching=true
|
||||
org.gradle.configuration-cache=true
|
||||
kotlin.incremental=true
|
||||
|
||||
# Dummy values for signing secrets / nightly
|
||||
signing.element.nightly.storePassword=Secret
|
||||
@@ -46,3 +47,9 @@ android.experimental.enableTestFixtures=true
|
||||
|
||||
# Create BuildConfig files as bytecode to avoid Java compilation phase
|
||||
android.enableBuildConfigAsBytecode=true
|
||||
|
||||
# Add the KSP code generation annotations to the list of contributing annotations for Anvil
|
||||
com.squareup.anvil.kspContributingAnnotations=io.element.android.anvilannotations.ContributesNode
|
||||
|
||||
# Only apply KSP to main sources
|
||||
ksp.allow.all.target.configuration=false
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
# Project
|
||||
android_gradle_plugin = "8.6.1"
|
||||
kotlin = "1.9.25"
|
||||
kotlinpoetKsp = "1.17.0"
|
||||
ksp = "1.9.25-1.0.20"
|
||||
firebaseAppDistribution = "5.0.0"
|
||||
|
||||
@@ -49,7 +50,7 @@ telephoto = "0.13.0"
|
||||
|
||||
# DI
|
||||
dagger = "2.52"
|
||||
anvil = "2.4.9"
|
||||
anvil = "0.3.1"
|
||||
|
||||
# Auto service
|
||||
autoservice = "1.1.1"
|
||||
@@ -63,14 +64,17 @@ kover = "0.8.3"
|
||||
android_gradle_plugin = { module = "com.android.tools.build:gradle", version.ref = "android_gradle_plugin" }
|
||||
# https://developer.android.com/studio/write/java8-support#library-desugaring-versions
|
||||
android_desugar = "com.android.tools:desugar_jdk_libs:2.1.2"
|
||||
anvil_gradle_plugin = { module = "com.squareup.anvil:gradle-plugin", version.ref = "anvil" }
|
||||
anvil_gradle_plugin = { module = "dev.zacsweers.anvil:gradle-plugin", version.ref = "anvil" }
|
||||
kotlin_gradle_plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
||||
kotlinpoet-ksp = { module = "com.squareup:kotlinpoet-ksp", version.ref = "kotlinpoetKsp" }
|
||||
kover_gradle_plugin = { module = "org.jetbrains.kotlinx:kover-gradle-plugin", version.ref = "kover" }
|
||||
ksp_gradle_plugin = { module = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" }
|
||||
gms_google_services = "com.google.gms:google-services:4.4.2"
|
||||
# https://firebase.google.com/docs/android/setup#available-libraries
|
||||
google_firebase_bom = "com.google.firebase:firebase-bom:33.4.0"
|
||||
firebase_appdistribution_gradle = { module = "com.google.firebase:firebase-appdistribution-gradle", version.ref = "firebaseAppDistribution" }
|
||||
autonomousapps_dependencyanalysis_plugin = { module = "com.autonomousapps:dependency-analysis-gradle-plugin", version.ref = "dependencyAnalysis" }
|
||||
ksp_plugin = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = "ksp" }
|
||||
|
||||
# AndroidX
|
||||
androidx_core = { module = "androidx.core:core", version.ref = "core" }
|
||||
@@ -198,8 +202,8 @@ sigpwned_emoji4j = "com.sigpwned:emoji4j-core:15.1.2"
|
||||
inject = "javax.inject:javax.inject:1"
|
||||
dagger = { module = "com.google.dagger:dagger", version.ref = "dagger" }
|
||||
dagger_compiler = { module = "com.google.dagger:dagger-compiler", version.ref = "dagger" }
|
||||
anvil_compiler_api = { module = "com.squareup.anvil:compiler-api", version.ref = "anvil" }
|
||||
anvil_compiler_utils = { module = "com.squareup.anvil:compiler-utils", version.ref = "anvil" }
|
||||
anvil_compiler_api = { module = "dev.zacsweers.anvil:compiler-api", version.ref = "anvil" }
|
||||
anvil_compiler_utils = { module = "dev.zacsweers.anvil:compiler-utils", version.ref = "anvil" }
|
||||
|
||||
# Auto services
|
||||
google_autoservice = { module = "com.google.auto.service:auto-service", version.ref = "autoservice" }
|
||||
@@ -222,7 +226,7 @@ kotlin_jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||
kotlin_serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
||||
kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" }
|
||||
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
|
||||
anvil = { id = "com.squareup.anvil", version.ref = "anvil" }
|
||||
anvil = { id = "dev.zacsweers.anvil", version.ref = "anvil" }
|
||||
detekt = "io.gitlab.arturbosch.detekt:1.23.7"
|
||||
ktlint = "org.jlleitschuh.gradle.ktlint:12.1.1"
|
||||
dependencygraph = "com.savvasdalkitsis.module-dependency-graph:0.12"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
package io.element.android.libraries.designsystem.component.async
|
||||
|
||||
import androidx.compose.animation.core.Transition
|
||||
import androidx.compose.animation.core.updateTransition
|
||||
import androidx.compose.animation.core.rememberTransition
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
@@ -237,7 +237,7 @@ class AsyncIndicatorTest {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val transition = state.currentItem.value?.let {
|
||||
// If there is an item, update its transition state to simulate an animation
|
||||
updateTransition(state.currentAnimationState, label = "")
|
||||
rememberTransition(state.currentAnimationState, label = "")
|
||||
}
|
||||
if (state.currentAnimationState.hasEntered() && state.currentItem.value?.durationMs != null) {
|
||||
SideEffect {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import extension.setupAnvil
|
||||
|
||||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
@@ -7,16 +9,17 @@
|
||||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
alias(libs.plugins.anvil)
|
||||
}
|
||||
|
||||
setupAnvil()
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.libraries.mediapickers.impl"
|
||||
|
||||
dependencies {
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.di)
|
||||
implementation(libs.inject)
|
||||
api(projects.libraries.mediapickers.api)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.di)
|
||||
implementation(libs.inject)
|
||||
api(projects.libraries.mediapickers.api)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import extension.setupAnvil
|
||||
import org.gradle.internal.extensions.stdlib.capitalized
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
|
||||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
@@ -51,3 +53,15 @@ sqldelight {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Workaround for KSP not picking up the generated files from SqlDelight
|
||||
androidComponents {
|
||||
onVariants(selector().all()) { variant ->
|
||||
afterEvaluate {
|
||||
val variantName = variant.name.capitalized()
|
||||
tasks.getByName<KotlinCompile>("ksp${variantName}Kotlin") {
|
||||
setSource(tasks.getByName("generate${variantName}SessionDatabaseInterface").outputs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,4 +23,5 @@ dependencies {
|
||||
implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location))
|
||||
implementation(libs.autonomousapps.dependencyanalysis.plugin)
|
||||
implementation(libs.anvil.gradle.plugin)
|
||||
implementation(libs.ksp.gradle.plugin)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -29,7 +29,7 @@ import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.inject.Inject
|
||||
|
||||
@SingleIn(AppScope::class)
|
||||
@ContributesBinding(AppScope::class, boundType = AnalyticsService::class, priority = ContributesBinding.Priority.HIGHEST)
|
||||
@ContributesBinding(AppScope::class, boundType = AnalyticsService::class, rank = ContributesBinding.RANK_HIGHEST)
|
||||
class DefaultAnalyticsService @Inject constructor(
|
||||
private val analyticsProviders: Set<@JvmSuppressWildcards AnalyticsProvider>,
|
||||
private val analyticsStore: AnalyticsStore,
|
||||
|
||||
@@ -28,7 +28,6 @@ dependencies {
|
||||
// - Add every single module as a dependency of this one.
|
||||
// - Move the Konsist tests to the `app` module, but the `app` module does not need to know about Konsist.
|
||||
tasks.withType<Test>().configureEach {
|
||||
outputs.upToDateWhen {
|
||||
gradle.startParameter.taskNames.any { it.contains("check", ignoreCase = true).not() }
|
||||
}
|
||||
val isNotCheckTask = gradle.startParameter.taskNames.any { it.contains("check", ignoreCase = true).not() }
|
||||
outputs.upToDateWhen { isNotCheckTask }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.tests.testutils
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.rules.TestRule
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
object RobolectricDispatcherCleaner {
|
||||
// HACK: Workaround for https://github.com/robolectric/robolectric/issues/7055#issuecomment-1551119229
|
||||
fun clearAndroidUiDispatcher(pkg: String = "androidx.compose.ui.platform") {
|
||||
val clazz = javaClass.classLoader!!.loadClass("$pkg.AndroidUiDispatcher")
|
||||
val combinedContextClass = javaClass.classLoader!!.loadClass("kotlin.coroutines.CombinedContext")
|
||||
val companionClazz = clazz.getDeclaredField("Companion").get(clazz)
|
||||
val combinedContext = companionClazz.javaClass.getDeclaredMethod("getMain")
|
||||
.invoke(companionClazz) as CoroutineContext
|
||||
val androidUiDispatcher = combinedContextClass.getDeclaredField("element")
|
||||
.apply { isAccessible = true }
|
||||
.get(combinedContext)
|
||||
.let { clazz.cast(it) }
|
||||
var scheduledFrameDispatch = clazz.getDeclaredField("scheduledFrameDispatch")
|
||||
.apply { isAccessible = true }
|
||||
.getBoolean(androidUiDispatcher)
|
||||
var scheduledTrampolineDispatch = clazz.getDeclaredField("scheduledTrampolineDispatch")
|
||||
.apply { isAccessible = true }
|
||||
.getBoolean(androidUiDispatcher)
|
||||
val dispatchCallback = clazz.getDeclaredField("dispatchCallback")
|
||||
.apply { isAccessible = true }
|
||||
.get(androidUiDispatcher) as Runnable
|
||||
if (scheduledFrameDispatch || scheduledTrampolineDispatch) {
|
||||
dispatchCallback.run()
|
||||
scheduledFrameDispatch = clazz.getDeclaredField("scheduledFrameDispatch")
|
||||
.apply { isAccessible = true }
|
||||
.getBoolean(androidUiDispatcher)
|
||||
scheduledTrampolineDispatch = clazz.getDeclaredField("scheduledTrampolineDispatch")
|
||||
.apply { isAccessible = true }
|
||||
.getBoolean(androidUiDispatcher)
|
||||
}
|
||||
assertFalse(scheduledFrameDispatch)
|
||||
assertFalse(scheduledTrampolineDispatch)
|
||||
}
|
||||
}
|
||||
|
||||
fun <R : TestRule, A : ComponentActivity> AndroidComposeTestRule<R, A>.setSafeContent(content: @Composable () -> Unit) {
|
||||
RobolectricDispatcherCleaner.clearAndroidUiDispatcher()
|
||||
setContent(content)
|
||||
}
|
||||
@@ -52,7 +52,7 @@ dependencies {
|
||||
implementation(projects.appnav)
|
||||
allLibrariesImpl()
|
||||
allServicesImpl()
|
||||
allFeaturesImpl(rootDir, logger)
|
||||
allFeaturesImpl(project)
|
||||
implementation(projects.appicon.element)
|
||||
implementation(projects.appicon.enterprise)
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -122,7 +122,7 @@ const previewAnnotations = [
|
||||
|
||||
const filesWithPreviews = editedFiles.filter(file => file.endsWith(".kt")).filter(file => {
|
||||
const content = fs.readFileSync(file);
|
||||
return previewAnnotations.some((ann) => content.includes(ann));
|
||||
return previewAnnotations.some((ann) => content.includes("import " + ann));
|
||||
})
|
||||
|
||||
const composablePreviewProviderContents = fs.readFileSync('tests/uitests/src/test/kotlin/base/ComposablePreviewProvider.kt');
|
||||
|
||||
Reference in New Issue
Block a user