|
|
|
/*
|
|
|
|
* Copyright (c) 2022 New Vector Ltd
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
@file:Suppress("UnstableApiUsage")
|
|
|
|
|
|
|
|
import com.android.build.api.variant.FilterConfiguration.FilterType.ABI
|
|
|
|
import extension.allFeaturesImpl
|
|
|
|
import extension.allLibrariesImpl
|
|
|
|
import extension.allServicesImpl
|
|
|
|
import org.jetbrains.kotlin.cli.common.toBooleanLenient
|
|
|
|
|
|
|
|
plugins {
|
|
|
|
id("io.element.android-compose-application")
|
|
|
|
alias(libs.plugins.kotlin.android)
|
|
|
|
alias(libs.plugins.anvil)
|
|
|
|
alias(libs.plugins.ksp)
|
|
|
|
alias(libs.plugins.kapt)
|
|
|
|
alias(libs.plugins.firebaseAppDistribution)
|
|
|
|
alias(libs.plugins.knit)
|
|
|
|
id("kotlin-parcelize")
|
|
|
|
// To be able to update the firebase.xml files, uncomment and build the project
|
|
|
|
// id("com.google.gms.google-services")
|
|
|
|
}
|
|
|
|
|
|
|
|
android {
|
|
|
|
namespace = "io.element.android.x"
|
|
|
|
|
|
|
|
defaultConfig {
|
|
|
|
applicationId = "io.element.android.x"
|
|
|
|
targetSdk = Versions.targetSdk
|
|
|
|
versionCode = Versions.versionCode
|
|
|
|
versionName = Versions.versionName
|
|
|
|
|
|
|
|
// Keep abiFilter for the universalApk
|
|
|
|
ndk {
|
|
|
|
abiFilters += listOf("armeabi-v7a", "x86", "arm64-v8a", "x86_64")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ref: https://developer.android.com/studio/build/configure-apk-splits.html#configure-abi-split
|
|
|
|
splits {
|
|
|
|
// Configures multiple APKs based on ABI.
|
|
|
|
abi {
|
|
|
|
// Enables building multiple APKs per ABI.
|
|
|
|
isEnable = true
|
|
|
|
// By default all ABIs are included, so use reset() and include to specify that we only
|
|
|
|
// want APKs for armeabi-v7a, x86, arm64-v8a and x86_64.
|
|
|
|
// Resets the list of ABIs that Gradle should create APKs for to none.
|
|
|
|
reset()
|
|
|
|
// Specifies a list of ABIs that Gradle should create APKs for.
|
|
|
|
include("armeabi-v7a", "x86", "arm64-v8a", "x86_64")
|
|
|
|
// Generate a universal APK that includes all ABIs, so user who installs from CI tool can use this one by default.
|
|
|
|
isUniversalApk = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
signingConfigs {
|
|
|
|
named("debug") {
|
|
|
|
keyAlias = "androiddebugkey"
|
|
|
|
keyPassword = "android"
|
|
|
|
storeFile = file("./signature/debug.keystore")
|
|
|
|
storePassword = "android"
|
|
|
|
}
|
|
|
|
register("nightly") {
|
|
|
|
keyAlias = System.getenv("ELEMENT_ANDROID_NIGHTLY_KEYID")
|
|
|
|
?: project.property("signing.element.nightly.keyId") as? String?
|
|
|
|
keyPassword = System.getenv("ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD")
|
|
|
|
?: project.property("signing.element.nightly.keyPassword") as? String?
|
|
|
|
storeFile = file("./signature/nightly.keystore")
|
|
|
|
storePassword = System.getenv("ELEMENT_ANDROID_NIGHTLY_STOREPASSWORD")
|
|
|
|
?: project.property("signing.element.nightly.storePassword") as? String?
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
buildTypes {
|
|
|
|
named("debug") {
|
|
|
|
resValue("string", "app_name", "Element X dbg")
|
|
|
|
applicationIdSuffix = ".debug"
|
|
|
|
signingConfig = signingConfigs.getByName("debug")
|
|
|
|
}
|
|
|
|
|
|
|
|
named("release") {
|
|
|
|
resValue("string", "app_name", "Element X")
|
|
|
|
signingConfig = signingConfigs.getByName("debug")
|
|
|
|
|
|
|
|
postprocessing {
|
|
|
|
isRemoveUnusedCode = true
|
|
|
|
isObfuscate = false
|
|
|
|
isOptimizeCode = true
|
|
|
|
isRemoveUnusedResources = true
|
|
|
|
proguardFiles("proguard-rules.pro")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
register("nightly") {
|
|
|
|
val release = getByName("release")
|
|
|
|
initWith(release)
|
|
|
|
applicationIdSuffix = ".nightly"
|
|
|
|
versionNameSuffix = "-nightly"
|
|
|
|
resValue("string", "app_name", "Element X nightly")
|
|
|
|
matchingFallbacks += listOf("release")
|
|
|
|
signingConfig = signingConfigs.getByName("nightly")
|
|
|
|
|
|
|
|
postprocessing {
|
|
|
|
initWith(release.postprocessing)
|
|
|
|
}
|
|
|
|
|
|
|
|
firebaseAppDistribution {
|
|
|
|
artifactType = "APK"
|
|
|
|
// We upload the universal APK to fix this error:
|
|
|
|
// "App Distribution found more than 1 output file for this variant.
|
|
|
|
// Please contact firebase-support@google.com for help using APK splits with App Distribution."
|
|
|
|
artifactPath = "$rootDir/app/build/outputs/apk/nightly/app-universal-nightly.apk"
|
|
|
|
// artifactType = "AAB"
|
|
|
|
// artifactPath = "$rootDir/app/build/outputs/bundle/nightly/app-nightly.aab"
|
|
|
|
// This file will be generated by the GitHub action
|
|
|
|
releaseNotesFile = "CHANGES_NIGHTLY.md"
|
|
|
|
groups = "external-testers"
|
|
|
|
// This should not be required, but if I do not add the appId, I get this error:
|
|
|
|
// "App Distribution halted because it had a problem uploading the APK: [404] Requested entity was not found."
|
|
|
|
appId = "1:912726360885:android:e17435e0beb0303000427c"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
kotlinOptions {
|
|
|
|
jvmTarget = "17"
|
|
|
|
}
|
|
|
|
|
|
|
|
buildFeatures {
|
|
|
|
buildConfig = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
androidComponents {
|
|
|
|
// map for the version codes last digit
|
|
|
|
// x86 must have greater values than arm
|
|
|
|
// 64 bits have greater value than 32 bits
|
|
|
|
val abiVersionCodes = mapOf(
|
|
|
|
"armeabi-v7a" to 1,
|
|
|
|
"arm64-v8a" to 2,
|
|
|
|
"x86" to 3,
|
|
|
|
"x86_64" to 4,
|
|
|
|
)
|
|
|
|
|
|
|
|
onVariants { variant ->
|
|
|
|
// Assigns a different version code for each output APK
|
|
|
|
// other than the universal APK.
|
|
|
|
variant.outputs.forEach { output ->
|
|
|
|
val name = output.filters.find { it.filterType == ABI }?.identifier
|
|
|
|
|
|
|
|
// Stores the value of abiCodes that is associated with the ABI for this variant.
|
|
|
|
val abiCode = abiVersionCodes[name] ?: 0
|
|
|
|
// Assigns the new version code to output.versionCode, which changes the version code
|
|
|
|
// for only the output APK, not for the variant itself.
|
|
|
|
output.versionCode.set((output.versionCode.get() ?: 0) * 10 + abiCode)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Knit
|
|
|
|
apply {
|
|
|
|
plugin("kotlinx-knit")
|
|
|
|
}
|
|
|
|
|
|
|
|
knit {
|
|
|
|
files = fileTree(project.rootDir) {
|
|
|
|
include(
|
|
|
|
"**/*.md",
|
|
|
|
"**/*.kt",
|
|
|
|
"*/*.kts",
|
|
|
|
)
|
|
|
|
exclude(
|
|
|
|
"**/build/**",
|
|
|
|
"*/.gradle/**",
|
|
|
|
"*/towncrier/template.md",
|
|
|
|
"**/CHANGES.md",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Kover configuration
|
|
|
|
*/
|
|
|
|
|
|
|
|
dependencies {
|
|
|
|
// Add all sub projects to kover except some of them
|
|
|
|
project.rootProject.subprojects
|
|
|
|
.filter {
|
|
|
|
it.project.projectDir.resolve("build.gradle.kts").exists()
|
|
|
|
}
|
|
|
|
.map { it.path }
|
|
|
|
.sorted()
|
|
|
|
.filter {
|
|
|
|
it !in listOf(
|
|
|
|
":app",
|
|
|
|
":samples",
|
|
|
|
":anvilannotations",
|
|
|
|
":anvilcodegen",
|
|
|
|
":samples:minimal",
|
|
|
|
":tests:testutils",
|
|
|
|
// Exclude `:libraries:matrix:impl` module, it contains only wrappers to access the Rust Matrix
|
|
|
|
// SDK api, so it is not really relevant to unit test it: there is no logic to test.
|
|
|
|
":libraries:matrix:impl",
|
|
|
|
// Exclude modules which are not Android libraries
|
|
|
|
// See https://github.com/Kotlin/kotlinx-kover/issues/312
|
|
|
|
":appconfig",
|
|
|
|
":libraries:core",
|
|
|
|
":libraries:coroutines",
|
|
|
|
":libraries:di",
|
|
|
|
":libraries:rustsdk",
|
|
|
|
":libraries:textcomposer:lib",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
.forEach {
|
|
|
|
// println("Add $it to kover")
|
|
|
|
kover(project(it))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
val ciBuildProperty = "ci-build"
|
|
|
|
val isCiBuild = if (project.hasProperty(ciBuildProperty)) {
|
|
|
|
val raw = project.property(ciBuildProperty) as? String
|
|
|
|
raw?.toBooleanLenient() == true || raw?.toIntOrNull() == 1
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
|
|
|
kover {
|
|
|
|
// When running on the CI, run only debug test variants
|
|
|
|
if (isCiBuild) {
|
|
|
|
excludeTests {
|
|
|
|
// Disable instrumentation for debug test tasks
|
|
|
|
tasks(
|
|
|
|
"testDebugUnitTest",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// https://kotlin.github.io/kotlinx-kover/
|
|
|
|
// Run `./gradlew :app:koverHtmlReport` to get report at ./app/build/reports/kover
|
|
|
|
// Run `./gradlew :app:koverXmlReport` to get XML report
|
|
|
|
koverReport {
|
|
|
|
filters {
|
|
|
|
excludes {
|
|
|
|
classes(
|
|
|
|
// Exclude generated classes.
|
|
|
|
"*_ModuleKt",
|
|
|
|
"anvil.hint.binding.io.element.*",
|
|
|
|
"anvil.hint.merge.*",
|
|
|
|
"anvil.hint.multibinding.io.element.*",
|
|
|
|
"anvil.module.*",
|
|
|
|
"com.airbnb.android.showkase*",
|
|
|
|
"io.element.android.libraries.designsystem.showkase.*",
|
|
|
|
"io.element.android.x.di.DaggerAppComponent*",
|
|
|
|
"*_Factory",
|
|
|
|
"*_Factory_Impl",
|
|
|
|
"*_Factory$*",
|
|
|
|
"*_Module",
|
|
|
|
"*_Module$*",
|
|
|
|
"*Module_Provides*",
|
|
|
|
"Dagger*Component*",
|
|
|
|
"*ComposableSingletons$*",
|
|
|
|
"*_AssistedFactory_Impl*",
|
|
|
|
"*BuildConfig",
|
|
|
|
// Generated by Showkase
|
|
|
|
"*Ioelementandroid*PreviewKt$*",
|
|
|
|
"*Ioelementandroid*PreviewKt",
|
|
|
|
// Other
|
|
|
|
// We do not cover Nodes (normally covered by maestro, but code coverage is not computed with maestro)
|
|
|
|
"*Node",
|
|
|
|
"*Node$*",
|
|
|
|
"*Presenter\$present\$*",
|
|
|
|
// Forked from compose
|
|
|
|
"io.element.android.libraries.designsystem.theme.components.bottomsheet.*",
|
|
|
|
)
|
|
|
|
annotatedBy(
|
|
|
|
"androidx.compose.ui.tooling.preview.Preview",
|
|
|
|
"io.element.android.libraries.architecture.coverage.ExcludeFromCoverage",
|
|
|
|
"io.element.android.libraries.designsystem.preview.PreviewsDayNight",
|
|
|
|
"io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
defaults {
|
|
|
|
// add reports of both 'debug' and 'release' Android build variants to default reports
|
|
|
|
mergeWith("debug")
|
|
|
|
mergeWith("release")
|
|
|
|
|
|
|
|
verify {
|
|
|
|
onCheck = true
|
|
|
|
// General rule: minimum code coverage.
|
|
|
|
rule("Global minimum code coverage.") {
|
|
|
|
isEnabled = true
|
|
|
|
entity = kotlinx.kover.gradle.plugin.dsl.GroupingEntityType.APPLICATION
|
|
|
|
bound {
|
|
|
|
minValue = 65
|
|
|
|
// Setting a max value, so that if coverage is bigger, it means that we have to change minValue.
|
|
|
|
// For instance if we have minValue = 20 and maxValue = 30, and current code coverage is now 31.32%, update
|
|
|
|
// minValue to 25 and maxValue to 35.
|
|
|
|
maxValue = 75
|
|
|
|
metric = kotlinx.kover.gradle.plugin.dsl.MetricType.INSTRUCTION
|
|
|
|
aggregation = kotlinx.kover.gradle.plugin.dsl.AggregationType.COVERED_PERCENTAGE
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Rule to ensure that coverage of Presenters is sufficient.
|
|
|
|
rule("Check code coverage of presenters") {
|
|
|
|
isEnabled = true
|
|
|
|
entity = kotlinx.kover.gradle.plugin.dsl.GroupingEntityType.CLASS
|
|
|
|
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$*",
|
|
|
|
"*Presenter\$present\$*",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
bound {
|
|
|
|
minValue = 85
|
|
|
|
metric = kotlinx.kover.gradle.plugin.dsl.MetricType.INSTRUCTION
|
|
|
|
aggregation = kotlinx.kover.gradle.plugin.dsl.AggregationType.COVERED_PERCENTAGE
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Rule to ensure that coverage of States is sufficient.
|
|
|
|
rule("Check code coverage of states") {
|
|
|
|
isEnabled = true
|
|
|
|
entity = kotlinx.kover.gradle.plugin.dsl.GroupingEntityType.CLASS
|
|
|
|
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.*",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
bound {
|
|
|
|
minValue = 90
|
|
|
|
metric = kotlinx.kover.gradle.plugin.dsl.MetricType.INSTRUCTION
|
|
|
|
aggregation = kotlinx.kover.gradle.plugin.dsl.AggregationType.COVERED_PERCENTAGE
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Rule to ensure that coverage of Views is sufficient (deactivated for now).
|
|
|
|
rule("Check code coverage of views") {
|
|
|
|
isEnabled = true
|
|
|
|
entity = kotlinx.kover.gradle.plugin.dsl.GroupingEntityType.CLASS
|
|
|
|
filters {
|
|
|
|
includes {
|
|
|
|
classes(
|
|
|
|
"*ViewKt",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
bound {
|
|
|
|
// TODO Update this value, for now there are too many missing tests.
|
|
|
|
minValue = 0
|
|
|
|
metric = kotlinx.kover.gradle.plugin.dsl.MetricType.INSTRUCTION
|
|
|
|
aggregation = kotlinx.kover.gradle.plugin.dsl.AggregationType.COVERED_PERCENTAGE
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
androidReports("release") {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
dependencies {
|
|
|
|
allLibrariesImpl()
|
|
|
|
allServicesImpl()
|
|
|
|
allFeaturesImpl(rootDir, logger)
|
|
|
|
implementation(projects.features.call)
|
|
|
|
implementation(projects.anvilannotations)
|
|
|
|
implementation(projects.appnav)
|
|
|
|
implementation(projects.appconfig)
|
|
|
|
anvil(projects.anvilcodegen)
|
|
|
|
|
|
|
|
implementation(libs.appyx.core)
|
|
|
|
implementation(libs.androidx.splash)
|
|
|
|
implementation(libs.androidx.core)
|
|
|
|
implementation(libs.androidx.corektx)
|
|
|
|
implementation(libs.androidx.lifecycle.runtime)
|
|
|
|
implementation(libs.androidx.lifecycle.process)
|
|
|
|
implementation(libs.androidx.activity.compose)
|
|
|
|
implementation(libs.androidx.startup)
|
|
|
|
implementation(libs.androidx.preference)
|
|
|
|
implementation(libs.coil)
|
|
|
|
|
|
|
|
implementation(platform(libs.network.okhttp.bom))
|
|
|
|
implementation(libs.network.okhttp.logging)
|
|
|
|
implementation(libs.serialization.json)
|
|
|
|
|
|
|
|
implementation(libs.matrix.emojibase.bindings)
|
|
|
|
|
|
|
|
implementation(libs.dagger)
|
|
|
|
kapt(libs.dagger.compiler)
|
|
|
|
|
|
|
|
testImplementation(libs.test.junit)
|
|
|
|
testImplementation(libs.test.robolectric)
|
|
|
|
testImplementation(libs.coroutines.test)
|
|
|
|
testImplementation(libs.molecule.runtime)
|
|
|
|
testImplementation(libs.test.truth)
|
|
|
|
testImplementation(libs.test.turbine)
|
|
|
|
testImplementation(projects.libraries.matrix.test)
|
|
|
|
|
|
|
|
ksp(libs.showkase.processor)
|
|
|
|
}
|