Updated on 2026-08-14

This commit is contained in:
Tangem 2025-03-11 18:55:37 +03:00
parent 330ae73357
commit e7efe7b76a
4636 changed files with 234864 additions and 63507 deletions

1
plugins/configuration/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,27 @@
plugins {
`kotlin-dsl`
`java-gradle-plugin`
}
repositories {
google()
mavenCentral()
}
configure<JavaPluginExtension> {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
dependencies {
implementation(deps.gradle.kotlin)
implementation(deps.gradle.android)
implementation(deps.gradle.detekt)
}
gradlePlugin {
plugins.register("configuration") {
id = "configuration"
implementationClass = "com.tangem.plugin.configuration.ConfigurationPlugin"
}
}

View file

@ -0,0 +1,10 @@
dependencyResolutionManagement {
versionCatalogs {
create("deps") {
from(files("../../gradle/dependencies.toml"))
}
create("tangemDeps") {
from(files("../../gradle/tangem_dependencies.toml"))
}
}
}

View file

@ -0,0 +1,30 @@
package com.tangem.plugin.configuration
import com.android.build.gradle.AppExtension
import com.android.build.gradle.LibraryExtension
import com.android.build.gradle.internal.plugins.AppPlugin
import com.android.build.gradle.internal.plugins.LibraryPlugin
import com.tangem.plugin.configuration.configurations.configure
import com.tangem.plugin.configuration.configurations.extension.configure
import org.gradle.api.Action
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
class ConfigurationPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.configure()
project.plugins.all(startPluginConfigurationAction(project))
}
private fun startPluginConfigurationAction(project: Project) = Action<Plugin<*>> {
when (this) {
is AppPlugin -> {
project.configure<AppExtension> { configure(project) }
}
is LibraryPlugin -> {
project.configure<LibraryExtension> { configure(project) }
}
}
}
}

View file

@ -0,0 +1,52 @@
package com.tangem.plugin.configuration.configurations
import com.tangem.plugin.configuration.utils.findLibrary
import com.tangem.plugin.configuration.utils.findPlugin
import io.gitlab.arturbosch.detekt.CONFIGURATION_DETEKT
import io.gitlab.arturbosch.detekt.CONFIGURATION_DETEKT_PLUGINS
import io.gitlab.arturbosch.detekt.Detekt
import io.gitlab.arturbosch.detekt.extensions.DetektExtension
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.withType
internal fun Project.configureDetektRules() {
plugins.apply(findPlugin(alias = CONFIGURATION_DETEKT).pluginId)
extensions.configure<DetektExtension> { configure(project) }
configureDetektPlugins()
configureDetektTask()
}
private fun DetektExtension.configure(project: Project) {
parallel = true
ignoreFailures = false
autoCorrect = true
buildUponDefaultConfig = true
config.setFrom(project.rootProject.files("tangem-android-tools/detekt-config.yml"))
}
private fun Project.configureDetektPlugins() {
listOf(
findLibrary(alias = "detekt-formatting"),
findLibrary(alias = "detekt-compose"),
).forEach {
dependencies.add(CONFIGURATION_DETEKT_PLUGINS, it)
}
}
private fun Project.configureDetektTask() {
tasks.withType<Detekt> {
include("**/*.kt")
exclude("**/resources/**", "**/build/**")
reports {
sarif {
required.set(false)
}
txt {
required.set(true)
}
}
jvmTarget = "17"
}
}

View file

@ -0,0 +1,16 @@
package com.tangem.plugin.configuration.configurations
import org.gradle.api.Project
import org.gradle.kotlin.dsl.withType
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
internal fun Project.configureKotlinCompilerOptions() {
project.tasks.withType<KotlinCompile> {
kotlinOptions {
jvmTarget = "17"
allWarningsAsErrors = false
// this is required to produce a unique META-INF/*.kotlin_module files
moduleName = project.path.removePrefix(":").replace(':', '-')
}
}
}

View file

@ -0,0 +1,8 @@
package com.tangem.plugin.configuration.configurations
import org.gradle.api.Project
internal fun Project.configure() {
configureKotlinCompilerOptions()
configureDetektRules()
}

View file

@ -0,0 +1,118 @@
package com.tangem.plugin.configuration.configurations.extension
import com.android.build.gradle.AppExtension
import com.tangem.plugin.configuration.model.AppConfig
import com.tangem.plugin.configuration.model.BuildType
import com.tangem.plugin.configuration.utils.BuildConfigFieldFactory
import org.gradle.api.Project
import com.android.build.gradle.internal.dsl.BuildType as AndroidBuildType
internal fun AppExtension.configure(project: Project) {
configureCompileSdk()
configureDefaultConfig(project)
configureBuildFeatures()
configureBuildTypes()
configurePackagingOptions()
configureCompose(project)
configureCompilerOptions()
}
private fun AppExtension.configureDefaultConfig(project: Project) {
defaultConfig {
applicationId = AppConfig.packageName
minSdk = AppConfig.minSdkVersion
targetSdk = AppConfig.targetSdkVersion
versionCode = if (project.hasProperty("versionCode")) {
(project.property("versionCode") as String).toInt()
} else {
AppConfig.versionCode
}
versionName = if (project.hasProperty("versionName")) {
project.property("versionName") as String
} else {
AppConfig.versionName
}
buildFeatures.buildConfig = true
testInstrumentationRunner = "com.tangem.common.HiltTestRunner"
}
}
// TODO: [REDACTED_JIRA]
private fun AppExtension.configureBuildFeatures() {
buildFeatures.apply {
viewBinding = true
}
}
private fun AppExtension.configureBuildTypes() {
buildTypes {
BuildType.values().forEach { buildType ->
maybeCreate(buildType.id).apply {
configureBuildVariant(
appExtension = this@configureBuildTypes,
buildType = buildType,
)
BuildConfigFieldFactory(
fields = buildType.configFields,
builder = ::buildConfigField,
).create()
}
}
}
testBuildType = BuildType.Mocked.id
}
private fun AndroidBuildType.configureBuildVariant(appExtension: AppExtension, buildType: BuildType) {
when (buildType) {
BuildType.Release -> {
isDebuggable = false
}
BuildType.Debug -> {
isDebuggable = true
}
BuildType.DebugPG -> {
// build is not debuggable to let R8 make optimizations
// otherwise, only shrinking will be applied
matchingFallbacks.add(BuildType.Debug.id)
signingConfig = appExtension.signingConfigs.getByName(BuildType.Debug.id)
}
BuildType.Internal,
BuildType.External,
-> {
initWith(appExtension.buildTypes.getByName(BuildType.Release.id))
matchingFallbacks.add(BuildType.Release.id)
signingConfig = appExtension.signingConfigs.getByName(BuildType.Debug.id)
}
BuildType.Mocked -> {
initWith(appExtension.buildTypes.getByName(BuildType.Release.id))
matchingFallbacks.add(BuildType.Release.id)
signingConfig = appExtension.signingConfigs.getByName(BuildType.Debug.id)
isDebuggable = true
}
}
versionNameSuffix = buildType.versionSuffix?.let { "-$it" }
applicationIdSuffix = buildType.appIdSuffix?.let { ".$it" }
isMinifyEnabled = buildType.obfuscating
isShrinkResources = buildType.obfuscating
proguardFiles("proguard-rules.pro", appExtension.getDefaultProguardFile("proguard-android.txt"))
}
private fun AppExtension.configurePackagingOptions() {
packagingOptions {
resources {
excludes += "lib/x86_64/darwin/libscrypt.dylib"
excludes += "lib/x86_64/freebsd/libscrypt.so"
excludes += "lib/x86_64/linux/libscrypt.so"
excludes += "META-INF/gradle/incremental.annotation.processors"
pickFirsts += "google/protobuf/*"
merges += "paymentrequest.proto"
}
}
}

View file

@ -0,0 +1,39 @@
package com.tangem.plugin.configuration.configurations.extension
import com.android.build.gradle.BaseExtension
import com.tangem.plugin.configuration.model.AppConfig
import com.tangem.plugin.configuration.utils.findVersion
import org.gradle.api.JavaVersion
import org.gradle.api.Project
internal fun BaseExtension.configureCompileSdk() {
compileSdkVersion(AppConfig.compileSdkVersion)
}
internal fun BaseExtension.configureCompilerOptions() {
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}
internal fun BaseExtension.configureCompose(project: Project) {
val useCompose = with(project.path) {
contains(":ui") ||
contains(":common:ui-charts") ||
contains(":features:onboarding") || // TODO: divide on api/impl after migrating all onboarding to module
contains(Regex(pattern = ":presentation\$")) ||
contains(Regex(pattern = ":app\$")) || // TODO: [REDACTED_JIRA]
contains(Regex(pattern = ":features:markets:api\$")) || // provides Composable function
contains(Regex(pattern = ":features:manage-tokens:api\$")) || // provides Composable function
contains(Regex(pattern = ":impl\$"))
}
buildFeatures.compose = useCompose
if (useCompose) {
composeOptions {
kotlinCompilerExtensionVersion = project.findVersion(alias = "compose-compiler").requiredVersion
}
}
}

View file

@ -0,0 +1,25 @@
package com.tangem.plugin.configuration.configurations.extension
import com.android.build.gradle.internal.plugins.AppPlugin
import com.android.build.gradle.internal.plugins.LibraryPlugin
import com.tangem.plugin.configuration.model.BuildType
import org.gradle.api.Project
fun Project.kaptForObfuscatingVariants(dependencyNotation: Any) {
plugins.all {
when (this) {
is AppPlugin -> {
BuildType.values().filter { it.obfuscating }.forEach {
dependencies.add(it.createKaptConfiguration(), dependencyNotation)
}
}
is LibraryPlugin -> {
BuildType.values().filter { it.obfuscating }.forEach {
dependencies.add(it.createKaptConfiguration(), dependencyNotation)
}
}
}
}
}
private fun BuildType.createKaptConfiguration() = "kapt" + name.replaceFirstChar(Char::titlecase)

View file

@ -0,0 +1,51 @@
package com.tangem.plugin.configuration.configurations.extension
import com.android.build.gradle.LibraryExtension
import com.tangem.plugin.configuration.model.AppConfig
import com.tangem.plugin.configuration.model.BuildType
import com.tangem.plugin.configuration.utils.BuildConfigFieldFactory
import org.gradle.api.Project
internal fun LibraryExtension.configure(project: Project) {
configureCompileSdk()
configureDefaultConfig()
configureBuildTypes()
configurePackagingOptions()
configureCompose(project)
configureCompilerOptions()
}
private fun LibraryExtension.configureDefaultConfig() {
defaultConfig {
minSdk = AppConfig.minSdkVersion
vectorDrawables {
useSupportLibrary = true
}
buildFeatures.buildConfig = true
}
}
private fun LibraryExtension.configureBuildTypes() {
buildTypes {
BuildType.values().forEach { buildVariant ->
maybeCreate(buildVariant.id).apply {
BuildConfigFieldFactory(
fields = buildVariant.configFields,
builder = ::buildConfigField,
).create()
}
}
}
}
private fun LibraryExtension.configurePackagingOptions() {
packaging {
resources {
excludes += "lib/x86_64/darwin/libscrypt.dylib"
excludes += "lib/x86_64/freebsd/libscrypt.so"
excludes += "lib/x86_64/linux/libscrypt.so"
excludes += "META-INF/gradle/incremental.annotation.processors"
pickFirsts += "google/protobuf/*"
}
}
}

View file

@ -0,0 +1,11 @@
package com.tangem.plugin.configuration.model
internal object AppConfig {
const val packageName = "com.tangem.wallet"
const val versionCode = 1
const val versionName = "1.0.0-SNAPSHOT"
// const val versionName = "100.0.0-SNAPSHOT" //TODO: [REDACTED_JIRA]
const val minSdkVersion = 24
const val targetSdkVersion = 34
const val compileSdkVersion = 34
}

View file

@ -0,0 +1,42 @@
package com.tangem.plugin.configuration.model
/**
* Build config field
*
* @property type field type
* @property name field name
* @property value field value
*/
internal sealed class BuildConfigField(val type: String, val name: String, val value: String) {
class Environment(value: String) : BuildConfigField(
type = "String",
name = "ENVIRONMENT",
value = "\"$value\"",
)
// TODO remove
class TestActionEnabled(isEnabled: Boolean) : BuildConfigField(
type = "Boolean",
name = "TEST_ACTION_ENABLED",
value = isEnabled.toString(),
)
class LogEnabled(isEnabled: Boolean) : BuildConfigField(
type = "Boolean",
name = "LOG_ENABLED",
value = isEnabled.toString(),
)
class TesterMenuAvailability(isEnabled: Boolean) : BuildConfigField(
type = "Boolean",
name = "TESTER_MENU_ENABLED",
value = isEnabled.toString(),
)
class MockDataSource(isEnabled: Boolean) : BuildConfigField(
type = "Boolean",
name = "MOCK_DATA_SOURCE",
value = isEnabled.toString(),
)
}

View file

@ -0,0 +1,133 @@
package com.tangem.plugin.configuration.model
internal enum class BuildType(
val id: String,
val appIdSuffix: String? = null,
val versionSuffix: String? = null,
val obfuscating: Boolean = false,
val configFields: List<BuildConfigField>,
) {
/**
* Build type for developers
*
* Features:
* - Env: dev
* - Signing config: debug
* - Debuggable
* - Logs
* - Tester menu
* - Test action
* - Traffic sniffing
* */
Debug(
id = "debug",
appIdSuffix = "debug",
configFields = listOf(
BuildConfigField.Environment(value = "dev"),
BuildConfigField.TestActionEnabled(isEnabled = true),
BuildConfigField.LogEnabled(isEnabled = true),
BuildConfigField.TesterMenuAvailability(isEnabled = true),
BuildConfigField.MockDataSource(isEnabled = false),
),
),
DebugPG(
id = "debugPG",
appIdSuffix = "debug",
obfuscating = true,
configFields = listOf(
BuildConfigField.Environment(value = "dev"),
BuildConfigField.TestActionEnabled(isEnabled = true),
BuildConfigField.LogEnabled(isEnabled = true),
BuildConfigField.TesterMenuAvailability(isEnabled = true),
BuildConfigField.MockDataSource(isEnabled = false),
),
),
/**
* Build type for QA and business
*
* Features:
* - Env: dev
* - Signing config: debug
* - Logs
* - Enabled mocked datasource
* */
Mocked(
id = "mocked",
appIdSuffix = "mocked",
versionSuffix = "mocked",
configFields = listOf(
BuildConfigField.Environment(value = "dev"),
BuildConfigField.TestActionEnabled(isEnabled = false),
BuildConfigField.LogEnabled(isEnabled = true),
BuildConfigField.TesterMenuAvailability(isEnabled = false),
BuildConfigField.MockDataSource(isEnabled = true),
),
),
/**
* Build type for QA
*
* Features:
* - Env: dev
* - Signing config: debug
* - Logs
* - Tester menu
* - Test action
* - Traffic sniffing
* */
Internal(
id = "internal",
appIdSuffix = "internal",
versionSuffix = "internal",
configFields = listOf(
BuildConfigField.Environment(value = "prod"),
BuildConfigField.TestActionEnabled(isEnabled = true),
BuildConfigField.LogEnabled(isEnabled = true),
BuildConfigField.TesterMenuAvailability(isEnabled = true),
BuildConfigField.MockDataSource(isEnabled = false),
),
),
/**
* Build type for QA and business
*
* Features:
* - Env: prod
* - Signing config: debug
* - Proguard
* */
External(
id = "external",
appIdSuffix = "external",
versionSuffix = "external",
configFields = listOf(
BuildConfigField.Environment(value = "prod"),
BuildConfigField.TestActionEnabled(isEnabled = false),
BuildConfigField.LogEnabled(isEnabled = false),
BuildConfigField.TesterMenuAvailability(isEnabled = false),
BuildConfigField.MockDataSource(isEnabled = false),
),
),
/**
* Production ready build type for clients
*
* Features:
* - Env: prod
* - Signing config: release
* - Proguard
* */
Release(
id = "release",
configFields = listOf(
BuildConfigField.Environment(value = "prod"),
BuildConfigField.TestActionEnabled(isEnabled = false),
BuildConfigField.LogEnabled(isEnabled = false),
BuildConfigField.TesterMenuAvailability(isEnabled = false),
BuildConfigField.MockDataSource(isEnabled = false),
),
),
}

View file

@ -0,0 +1,14 @@
package com.tangem.plugin.configuration.utils
import com.tangem.plugin.configuration.model.BuildConfigField
private typealias ConfigFieldBuilder = (String, String, String) -> Unit
internal class BuildConfigFieldFactory(
private val fields: List<BuildConfigField>,
private val builder: ConfigFieldBuilder,
) {
fun create() {
fields.forEach { field -> builder(field.type, field.name, field.value) }
}
}

View file

@ -0,0 +1,28 @@
package com.tangem.plugin.configuration.utils
import org.gradle.api.Project
import org.gradle.api.artifacts.MinimalExternalModuleDependency
import org.gradle.api.artifacts.VersionCatalog
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.api.artifacts.VersionConstraint
import org.gradle.kotlin.dsl.getByType
import org.gradle.plugin.use.PluginDependency
import kotlin.jvm.optionals.getOrNull
private val Project.depsVersionCatalog: VersionCatalog
get() = extensions.getByType<VersionCatalogsExtension>().named("deps")
internal fun Project.findVersion(alias: String): VersionConstraint {
return depsVersionCatalog.findVersion(alias).getOrNull()
?: error("Unable to find version $alias")
}
internal fun Project.findPlugin(alias: String): PluginDependency {
return depsVersionCatalog.findPlugin(alias).getOrNull()?.orNull
?: error("Unable to find plugin $alias")
}
internal fun Project.findLibrary(alias: String): MinimalExternalModuleDependency {
return depsVersionCatalog.findLibrary(alias).getOrNull()?.orNull
?: error("Unable to find library $alias")
}