Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


SSL build.gradle.kts   Interaktion und
Portierbarkeitunbekannt

 
Spracherkennung für: .kts vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

import com.android.build.gradle.internal.api.ApkVariantOutputImpl
import com.android.build.gradle.internal.tasks.factory.dependsOn
import config.BuildFeatureFlags
import config.PublicKeys
import config.SentryConfig
import config.setProductNames
import config.setSentryConfig
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.kotlin.dsl.lintChecks
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import utils.*

plugins {
    alias(libs.plugins.sonarqube)
    alias(libs.plugins.kotlin.serialization)
    alias(libs.plugins.rust.android)
    id("com.android.application")
    id("kotlin-android")
    alias(libs.plugins.ksp)
    alias(libs.plugins.compose.compiler)
    alias(libs.plugins.stem)
}

// only apply the plugin if we are dealing with an AppGallery build
if (gradle.startParameter.taskRequests.toString().contains("Hms")) {
    logger.info("enabling hms plugin")
    apply {
        plugin("com.huawei.agconnect")
    }
}

/**
 * Only use the scheme "<major>.<minor>.<patch>" for the appVersion
 */
val appVersion = "6.4.1"

/**
 * betaSuffix with leading dash (e.g. `-beta1`).
 * Should be one of (alpha|beta|rc) and an increasing number, or empty for a regular release.
 * Note: in nightly builds this will be overwritten with a nightly version "-n12345"
 */
val betaSuffix = ""

val defaultVersionCode = 1137

/**
 * Map with keystore paths (if found).
 */
val keystores: Map<String, KeystoreConfig?> = mapOf(
    "debug" to findKeystore(projectDir, "debug"),
    "release" to findKeystore(projectDir, "threema"),
    "hms_release" to findKeystore(projectDir, "threema_hms"),
    "onprem_release" to findKeystore(projectDir, "onprem"),
    "blue_release" to findKeystore(projectDir, "threema_blue"),
)

android {
    // NOTE: When adjusting compileSdkVersion, buildToolsVersion or ndkVersion,
    //       make sure to adjust them in `scripts/Dockerfile` as well!
    compileSdk = 35
    buildToolsVersion = "35.0.0"
    ndkVersion = "28.2.13676358"

    defaultConfig {
        // https://developer.android.com/training/testing/espresso/setup#analytics
        with(testInstrumentationRunnerArguments) {
            put("notAnnotation", "ch.threema.app.DangerousTest")
            put("disableAnalytics", "true")
        }
        minSdk = 24
        targetSdk = 35
        vectorDrawables.useSupportLibrary = true
        applicationId = "ch.threema.app"
        testApplicationId = "$applicationId.test"
        versionCode = defaultVersionCode
        versionName = "$appVersion$betaSuffix"

        setProductNames(
            appName = "Threema",
        )
        intBuildConfigField("DEFAULT_VERSION_CODE", defaultVersionCode)
        // package name used for sync adapter - needs to match mime types below
        stringResValue("package_name", applicationId!!)
        stringResValue("contacts_mime_type", "vnd.android.cursor.item/vnd.$applicationId.profile")
        stringResValue("call_mime_type", "vnd.android.cursor.item/vnd.$applicationId.call")

        intBuildConfigField("MAX_GROUP_SIZE", 256)
        stringBuildConfigField("CHAT_SERVER_PREFIX", "g-")
        stringBuildConfigField("CHAT_SERVER_IPV6_PREFIX", "ds.g-")
        stringBuildConfigField("CHAT_SERVER_SUFFIX", ".0.threema.ch")
        intArrayBuildConfigField("CHAT_SERVER_PORTS", intArrayOf(5222, 443))
        stringBuildConfigField("MEDIA_PATH", "Threema")
        booleanBuildConfigField("CHAT_SERVER_GROUPS", true)
        booleanBuildConfigField("DISABLE_CERT_PINNING", false)
        booleanBuildConfigField("VIDEO_CALLS_ENABLED", true)
        // This public key is pinned for the chat server protocol.
        byteArrayBuildConfigField("SERVER_PUBKEY", PublicKeys.prodServer)
        byteArrayBuildConfigField("SERVER_PUBKEY_ALT", PublicKeys.prodServerAlt)
        stringBuildConfigField("GIT_HASH", getGitHash())
        stringBuildConfigField("GIT_BRANCH", getGitBranch())
        stringBuildConfigField("DIRECTORY_SERVER_URL", "https://apip.threema.ch/")
        stringBuildConfigField("DIRECTORY_SERVER_IPV6_URL", "https://ds-apip.threema.ch/")
        stringBuildConfigField("WORK_SERVER_URL", null)
        stringBuildConfigField("WORK_SERVER_IPV6_URL", null)
        stringBuildConfigField("MEDIATOR_SERVER_URL", "wss://mediator-{deviceGroupIdPrefix4}.threema.ch/{deviceGroupIdPrefix8}/")

        // Base blob url used for "download" and "done" calls
        stringBuildConfigField("BLOB_SERVER_URL", "https://blobp-{blobIdPrefix}.threema.ch")
        stringBuildConfigField("BLOB_SERVER_IPV6_URL", "https://ds-blobp-{blobIdPrefix}.threema.ch")

        // Specific blob url used for "upload" calls
        stringBuildConfigField("BLOB_SERVER_URL_UPLOAD", "https://blobp-upload.threema.ch/upload")
        stringBuildConfigField("BLOB_SERVER_IPV6_URL_UPLOAD", "https://ds-blobp-upload.threema.ch/upload")

        // Base blob mirror url used for "download", "upload", "done"
        stringBuildConfigField("BLOB_MIRROR_SERVER_URL", "https://blob-mirror-{deviceGroupIdPrefix4}.threema.ch/{deviceGroupIdPrefix8}")

        stringBuildConfigField("AVATAR_FETCH_URL", "https://avatar.threema.ch/")
        stringBuildConfigField("SAFE_SERVER_URL", "https://safe-{backupIdPrefix8}.threema.ch/")
        stringBuildConfigField("WEB_SERVER_URL", "https://web.threema.ch/")
        stringBuildConfigField("APP_RATING_URL", "https://threema.com/app-rating/android/{rating}")
        stringBuildConfigField("MAP_STYLES_URL", "https://map.threema.ch/styles/threema/style.json")
        stringBuildConfigField("MAP_POI_AROUND_URL", "https://poi.threema.ch/around/{latitude}/{longitude}/{radius}/")
        stringBuildConfigField("MAP_POI_NAMES_URL", "https://poi.threema.ch/names/{latitude}/{longitude}/{query}/")
        byteArrayBuildConfigField("THREEMA_PUSH_PUBLIC_KEY", PublicKeys.threemaPush)
        stringBuildConfigField("ONPREM_ID_PREFIX", "O")
        stringBuildConfigField("LOG_TAG", "3ma")
        stringBuildConfigField("DEFAULT_APP_THEME", "2")

        stringArrayBuildConfigField("ONPREM_CONFIG_TRUSTED_PUBLIC_KEYS", emptyArray())
        booleanBuildConfigField("MD_SYNC_DISTRIBUTION_LISTS", false)
        booleanBuildConfigField("AVAILABILITY_STATUS_ENABLED", BuildFeatureFlags["availability_status"] ?: false)
        booleanBuildConfigField("ERROR_REPORTING_SUPPORTED", false)

        // config fields for action URLs / deep links
        stringBuildConfigField("uriScheme", "threema")
        stringBuildConfigField("actionUrl", "go.threema.ch")
        stringBuildConfigField("contactActionUrl", "threema.id")

        // The OPPF url must be null in the default config. Do not change this.
        stringBuildConfigField("PRESET_OPPF_URL", null)

        setSentryConfig(
            projectId = 0,
            publicApikey = "",
        )

        with(manifestPlaceholders) {
            put("uriScheme", "threema")
            put("contactActionUrl", "threema.id")
            put("actionUrl", "go.threema.ch")
            put("callMimeType", "vnd.android.cursor.item/vnd.$applicationId.call")
        }

        testInstrumentationRunner = "$applicationId.ThreemaTestRunner"

        // Only include language resources for those languages
        androidResources.localeFilters.addAll(
            setOf(
                "en",
                "be-rBY",
                "bg",
                "ca",
                "cs",
                "de",
                "es",
                "fr",
                "gsw",
                "hu",
                "it",
                "ja",
                "nl-rNL",
                "no",
                "pl",
                "pt-rBR",
                "ru",
                "sk",
                "tr",
                "uk",
                "zh-rCN",
                "zh-rTW",
            ),
        )
    }

    splits {
        abi {
            isEnable = true
            reset()
            if (project.hasProperty("noAbiSplits")) {
                isUniversalApk = true
            } else {
                include("armeabi-v7a", "x86", "arm64-v8a", "x86_64")
                isUniversalApk = project.hasProperty("buildUniversalApk")
            }
        }
    }

    // Assign different version code for each output
    android.applicationVariants.all {
        outputs.all {
            if (this is ApkVariantOutputImpl) {
                val abi = getFilter("ABI")
                val abiVersionCode = when (abi) {
                    "armeabi-v7a" -> 2
                    "arm64-v8a" -> 3
                    "x86" -> 8
                    "x86_64" -> 9
                    else -> 0
                }
                versionCodeOverride = abiVersionCode * 1_000_000 + defaultVersionCode
            }
        }
    }

    namespace = "ch.threema.app"
    flavorDimensions.add("default")
    productFlavors {
        create("none")
        create("store_google")
        create("store_threema") {
            stringResValue("shop_download_filename", "Threema-update.apk")
        }
        create("store_google_work") {
            versionName = "${appVersion}k$betaSuffix"
            applicationId = "ch.threema.app.work"
            testApplicationId = "$applicationId.test"
            setProductNames(appName = "Threema Work")
            stringResValue("package_name", applicationId!!)
            stringResValue("contacts_mime_type", "vnd.android.cursor.item/vnd.$applicationId.profile")
            stringResValue("call_mime_type", "vnd.android.cursor.item/vnd.$applicationId.call")
            stringBuildConfigField("CHAT_SERVER_PREFIX", "w-")
            stringBuildConfigField("CHAT_SERVER_IPV6_PREFIX", "ds.w-")
            stringBuildConfigField("MEDIA_PATH", "ThreemaWork")
            stringBuildConfigField("WORK_SERVER_URL", "https://apip-work.threema.ch/")
            stringBuildConfigField("WORK_SERVER_IPV6_URL", "https://ds-apip-work.threema.ch/")
            stringBuildConfigField("APP_RATING_URL", "https://threema.com/app-rating/android-work/{rating}")
            stringBuildConfigField("LOG_TAG", "3mawrk")
            stringBuildConfigField("DEFAULT_APP_THEME", "2")

            // config fields for action URLs / deep links
            stringBuildConfigField("uriScheme", "threemawork")
            stringBuildConfigField("actionUrl", "work.threema.ch")

            stringBuildConfigField("SCREENSHOT_TEST_WORK_USERNAME", LocalProperties.getString("screenshotTestWorkUsername"))
            stringBuildConfigField("SCREENSHOT_TEST_WORK_PASSWORD", LocalProperties.getString("screenshotTestWorkPassword"))

            with(manifestPlaceholders) {
                put("uriScheme", "threemawork")
                put("actionUrl", "work.threema.ch")
                put("callMimeType", "vnd.android.cursor.item/vnd.$applicationId.call")
            }
        }
        create("green") {
            applicationId = "ch.threema.app.green"
            testApplicationId = "$applicationId.test"
            setProductNames(appName = "Threema Green")
            setSentryConfig(
                projectId = SentryConfig.SANDBOX_PROJECT_ID,
                publicApikey = SentryConfig.SANDBOX_PUBLIC_API_KEY,
            )
            stringResValue("package_name", applicationId!!)
            stringResValue("contacts_mime_type", "vnd.android.cursor.item/vnd.$applicationId.profile")
            stringResValue("call_mime_type", "vnd.android.cursor.item/vnd.$applicationId.call")
            stringBuildConfigField("MEDIA_PATH", "ThreemaGreen")
            stringBuildConfigField("CHAT_SERVER_SUFFIX", ".0.test.threema.ch")
            // This public key is pinned for the chat server protocol.
            byteArrayBuildConfigField("SERVER_PUBKEY", PublicKeys.sandboxServer)
            byteArrayBuildConfigField("SERVER_PUBKEY_ALT", PublicKeys.sandboxServer)
            stringBuildConfigField("DIRECTORY_SERVER_URL", "https://apip.test.threema.ch/")
            stringBuildConfigField("DIRECTORY_SERVER_IPV6_URL", "https://ds-apip.test.threema.ch/")
            stringBuildConfigField("MEDIATOR_SERVER_URL", "wss://mediator-{deviceGroupIdPrefix4}.test.threema.ch/{deviceGroupIdPrefix8}/")
            stringBuildConfigField("BLOB_SERVER_URL", "https://blobp-{blobIdPrefix}.test.threema.ch")
            stringBuildConfigField("BLOB_SERVER_IPV6_URL", "https://ds-blobp-{blobIdPrefix}.test.threema.ch")
            stringBuildConfigField("BLOB_SERVER_URL_UPLOAD", "https://blobp-upload.test.threema.ch/upload")
            stringBuildConfigField("BLOB_SERVER_IPV6_URL_UPLOAD", "https://ds-blobp-upload.test.threema.ch/upload")
            stringBuildConfigField("AVATAR_FETCH_URL", "https://avatar.test.threema.ch/")
            stringBuildConfigField("APP_RATING_URL", "https://test.threema.com/app-rating/android/{rating}")
            stringBuildConfigField("MAP_STYLES_URL", "https://map.test.threema.ch/styles/threema/style.json")
            stringBuildConfigField("MAP_POI_AROUND_URL", "https://poi.test.threema.ch/around/{latitude}/{longitude}/{radius}/")
            stringBuildConfigField("MAP_POI_NAMES_URL", "https://poi.test.threema.ch/names/{latitude}/{longitude}/{query}/")
            stringBuildConfigField("BLOB_MIRROR_SERVER_URL", "https://blob-mirror-{deviceGroupIdPrefix4}.test.threema.ch/{deviceGroupIdPrefix8}")
            booleanBuildConfigField("ERROR_REPORTING_SUPPORTED", true)
        }
        create("sandbox_work") {
            versionName = "${appVersion}k$betaSuffix"
            applicationId = "ch.threema.app.sandbox.work"
            testApplicationId = "$applicationId.test"
            setProductNames(
                appName = "Threema Sandbox Work",
                appNameDesktop = "Threema Blue",
            )
            setSentryConfig(
                projectId = SentryConfig.SANDBOX_PROJECT_ID,
                publicApikey = SentryConfig.SANDBOX_PUBLIC_API_KEY,
            )
            stringResValue("package_name", applicationId!!)
            stringResValue("contacts_mime_type", "vnd.android.cursor.item/vnd.$applicationId.profile")
            stringResValue("call_mime_type", "vnd.android.cursor.item/vnd.$applicationId.call")
            stringBuildConfigField("CHAT_SERVER_PREFIX", "w-")
            stringBuildConfigField("CHAT_SERVER_IPV6_PREFIX", "ds.w-")
            stringBuildConfigField("CHAT_SERVER_SUFFIX", ".0.test.threema.ch")
            stringBuildConfigField("MEDIA_PATH", "ThreemaWorkSandbox")
            // This public key is pinned for the chat server protocol.
            byteArrayBuildConfigField("SERVER_PUBKEY", PublicKeys.sandboxServer)
            byteArrayBuildConfigField("SERVER_PUBKEY_ALT", PublicKeys.sandboxServer)
            stringBuildConfigField("DIRECTORY_SERVER_URL", "https://apip.test.threema.ch/")
            stringBuildConfigField("DIRECTORY_SERVER_IPV6_URL", "https://ds-apip.test.threema.ch/")
            stringBuildConfigField("WORK_SERVER_URL", "https://apip-work.test.threema.ch/")
            stringBuildConfigField("WORK_SERVER_IPV6_URL", "https://ds-apip-work.test.threema.ch/")
            stringBuildConfigField("MEDIATOR_SERVER_URL", "wss://mediator-{deviceGroupIdPrefix4}.test.threema.ch/{deviceGroupIdPrefix8}/")
            stringBuildConfigField("BLOB_SERVER_URL", "https://blobp-{blobIdPrefix}.test.threema.ch")
            stringBuildConfigField("BLOB_SERVER_IPV6_URL", "https://ds-blobp-{blobIdPrefix}.test.threema.ch")
            stringBuildConfigField("BLOB_SERVER_URL_UPLOAD", "https://blobp-upload.test.threema.ch/upload")
            stringBuildConfigField("BLOB_SERVER_IPV6_URL_UPLOAD", "https://ds-blobp-upload.test.threema.ch/upload")
            stringBuildConfigField("AVATAR_FETCH_URL", "https://avatar.test.threema.ch/")
            stringBuildConfigField("APP_RATING_URL", "https://test.threema.com/app-rating/android-work/{rating}")
            stringBuildConfigField("MAP_STYLES_URL", "https://map.test.threema.ch/styles/threema/style.json")
            stringBuildConfigField("MAP_POI_AROUND_URL", "https://poi.test.threema.ch/around/{latitude}/{longitude}/{radius}/")
            stringBuildConfigField("MAP_POI_NAMES_URL", "https://poi.test.threema.ch/names/{latitude}/{longitude}/{query}/")
            stringBuildConfigField("LOG_TAG", "3mawrk")
            stringBuildConfigField("DEFAULT_APP_THEME", "2")
            stringBuildConfigField("BLOB_MIRROR_SERVER_URL", "https://blob-mirror-{deviceGroupIdPrefix4}.test.threema.ch/{deviceGroupIdPrefix8}")

            // config fields for action URLs / deep links
            stringBuildConfigField("uriScheme", "threemawork")
            stringBuildConfigField("actionUrl", "work.test.threema.ch")

            booleanBuildConfigField("ERROR_REPORTING_SUPPORTED", true)

            stringBuildConfigField("MD_CLIENT_DOWNLOAD_URL", "https://three.ma/mdw")

            with(manifestPlaceholders) {
                put("uriScheme", "threemawork")
                put("actionUrl", "work.test.threema.ch")
            }
        }
        create("onprem") {
            versionName = "${appVersion}o$betaSuffix"
            applicationId = "ch.threema.app.onprem"
            testApplicationId = "$applicationId.test"
            setProductNames(
                appName = "Threema OnPrem",
                shortAppName = "Threema",
                companyName = "Threema",
            )
            stringResValue("package_name", applicationId!!)
            stringResValue("contacts_mime_type", "vnd.android.cursor.item/vnd.$applicationId.profile")
            stringResValue("call_mime_type", "vnd.android.cursor.item/vnd.$applicationId.call")
            intBuildConfigField("MAX_GROUP_SIZE", 256)
            stringBuildConfigField("CHAT_SERVER_PREFIX", "")
            stringBuildConfigField("CHAT_SERVER_IPV6_PREFIX", "")
            stringBuildConfigField("CHAT_SERVER_SUFFIX", null)
            stringBuildConfigField("MEDIA_PATH", "ThreemaOnPrem")
            booleanBuildConfigField("CHAT_SERVER_GROUPS", false)
            byteArrayBuildConfigField("SERVER_PUBKEY", null)
            byteArrayBuildConfigField("SERVER_PUBKEY_ALT", null)
            stringBuildConfigField("DIRECTORY_SERVER_URL", null)
            stringBuildConfigField("DIRECTORY_SERVER_IPV6_URL", null)
            stringBuildConfigField("BLOB_SERVER_URL", null)
            stringBuildConfigField("BLOB_SERVER_IPV6_URL", null)
            stringBuildConfigField("BLOB_SERVER_URL_UPLOAD", null)
            stringBuildConfigField("BLOB_SERVER_IPV6_URL_UPLOAD", null)
            stringBuildConfigField("BLOB_MIRROR_SERVER_URL", null)
            stringArrayBuildConfigField("ONPREM_CONFIG_TRUSTED_PUBLIC_KEYS", PublicKeys.onPremTrusted)
            stringBuildConfigField("LOG_TAG", "3maop")

            // config fields for action URLs / deep links
            val uriScheme = "threemaonprem"
            val actionUrl = "onprem.threema.ch"
            stringBuildConfigField("uriScheme", uriScheme)
            stringBuildConfigField("actionUrl", actionUrl)

            stringBuildConfigField("PRESET_OPPF_URL", null)

            stringBuildConfigField("MD_CLIENT_DOWNLOAD_URL", "https://three.ma/mdo")

            stringBuildConfigField("SCREENSHOT_TEST_ONPREM_USERNAME", LocalProperties.getString("screenshotTestOnPremUsername"))
            stringBuildConfigField("SCREENSHOT_TEST_ONPREM_PASSWORD", LocalProperties.getString("screenshotTestOnPremPassword"))
            stringBuildConfigField("SCREENSHOT_TEST_ONPREM_SERVER_URL", LocalProperties.getString("screenshotTestOnPremServerUrl"))

            with(manifestPlaceholders) {
                put("uriScheme", uriScheme)
                put("actionUrl", actionUrl)
                put("callMimeType", "vnd.android.cursor.item/vnd.$applicationId.call")
            }
        }
        create("blue") {
            // Essentially like sandbox work, but with a different icon and application id, used for internal testing
            versionName = "${appVersion}b$betaSuffix"
            // The app was previously named `red`. The app id remains unchanged to still be able to install updates.
            applicationId = "ch.threema.app.red"
            testApplicationId = "ch.threema.app.blue.test"
            setProductNames(appName = "Threema Blue")
            setSentryConfig(
                projectId = SentryConfig.SANDBOX_PROJECT_ID,
                publicApikey = SentryConfig.SANDBOX_PUBLIC_API_KEY,
            )
            stringResValue("package_name", applicationId!!)
            stringResValue("contacts_mime_type", "vnd.android.cursor.item/vnd.ch.threema.app.blue.profile")
            stringResValue("call_mime_type", "vnd.android.cursor.item/vnd.ch.threema.app.blue.call")

            stringBuildConfigField("CHAT_SERVER_PREFIX", "w-")
            stringBuildConfigField("CHAT_SERVER_IPV6_PREFIX", "ds.w-")
            stringBuildConfigField("CHAT_SERVER_SUFFIX", ".0.test.threema.ch")
            stringBuildConfigField("MEDIA_PATH", "ThreemaBlue")
            // This public key is pinned for the chat server protocol.
            byteArrayBuildConfigField("SERVER_PUBKEY", PublicKeys.sandboxServer)
            byteArrayBuildConfigField("SERVER_PUBKEY_ALT", PublicKeys.sandboxServer)
            stringBuildConfigField("DIRECTORY_SERVER_URL", "https://apip.test.threema.ch/")
            stringBuildConfigField("DIRECTORY_SERVER_IPV6_URL", "https://ds-apip.test.threema.ch/")
            stringBuildConfigField("WORK_SERVER_URL", "https://apip-work.test.threema.ch/")
            stringBuildConfigField("WORK_SERVER_IPV6_URL", "https://ds-apip-work.test.threema.ch/")
            stringBuildConfigField("MEDIATOR_SERVER_URL", "wss://mediator-{deviceGroupIdPrefix4}.test.threema.ch/{deviceGroupIdPrefix8}/")
            stringBuildConfigField("BLOB_SERVER_URL", "https://blobp-{blobIdPrefix}.test.threema.ch")
            stringBuildConfigField("BLOB_SERVER_IPV6_URL", "https://ds-blobp-{blobIdPrefix}.test.threema.ch")
            stringBuildConfigField("BLOB_SERVER_URL_UPLOAD", "https://blobp-upload.test.threema.ch/upload")
            stringBuildConfigField("BLOB_SERVER_IPV6_URL_UPLOAD", "https://ds-blobp-upload.test.threema.ch/upload")
            stringBuildConfigField("AVATAR_FETCH_URL", "https://avatar.test.threema.ch/")
            stringBuildConfigField("APP_RATING_URL", "https://test.threema.com/app-rating/android-work/{rating}")
            stringBuildConfigField("MAP_STYLES_URL", "https://map.test.threema.ch/styles/threema/style.json")
            stringBuildConfigField("MAP_POI_AROUND_URL", "https://poi.test.threema.ch/around/{latitude}/{longitude}/{radius}/")
            stringBuildConfigField("MAP_POI_NAMES_URL", "https://poi.test.threema.ch/names/{latitude}/{longitude}/{query}/")
            stringBuildConfigField("LOG_TAG", "3mablue")
            stringBuildConfigField("BLOB_MIRROR_SERVER_URL", "https://blob-mirror-{deviceGroupIdPrefix4}.test.threema.ch/{deviceGroupIdPrefix8}")

            booleanBuildConfigField("ERROR_REPORTING_SUPPORTED", true)

            // config fields for action URLs / deep links
            stringBuildConfigField("uriScheme", "threemablue")
            stringBuildConfigField("actionUrl", "blue.threema.ch")

            with(manifestPlaceholders) {
                put("uriScheme", "threemablue")
                put("actionUrl", "blue.threema.ch")
                put("callMimeType", "vnd.android.cursor.item/vnd.ch.threema.app.blue.call")
            }
        }
        create("hms") {
            applicationId = "ch.threema.app.hms"
        }
        create("hms_work") {
            versionName = "${appVersion}k$betaSuffix"
            applicationId = "ch.threema.app.work.hms"
            testApplicationId = "ch.threema.app.work.test.hms"
            setProductNames(appName = "Threema Work")
            stringResValue("package_name", "ch.threema.app.work")
            stringResValue("contacts_mime_type", "vnd.android.cursor.item/vnd.ch.threema.app.work.profile")
            stringResValue("call_mime_type", "vnd.android.cursor.item/vnd.ch.threema.app.work.call")
            stringBuildConfigField("CHAT_SERVER_PREFIX", "w-")
            stringBuildConfigField("CHAT_SERVER_IPV6_PREFIX", "ds.w-")
            stringBuildConfigField("MEDIA_PATH", "ThreemaWork")
            stringBuildConfigField("WORK_SERVER_URL", "https://apip-work.threema.ch/")
            stringBuildConfigField("WORK_SERVER_IPV6_URL", "https://ds-apip-work.threema.ch/")
            stringBuildConfigField("APP_RATING_URL", "https://threema.com/app-rating/android-work/{rating}")
            stringBuildConfigField("LOG_TAG", "3mawrk")
            stringBuildConfigField("DEFAULT_APP_THEME", "2")

            // config fields for action URLs / deep links
            stringBuildConfigField("uriScheme", "threemawork")
            stringBuildConfigField("actionUrl", "work.threema.ch")

            with(manifestPlaceholders) {
                put("uriScheme", "threemawork")
                put("actionUrl", "work.threema.ch")
                put("callMimeType", "vnd.android.cursor.item/vnd.ch.threema.app.work.call")
            }
        }
        create("libre") {
            versionName = "${appVersion}l$betaSuffix"
            applicationId = "ch.threema.app.libre"
            testApplicationId = "$applicationId.test"
            stringResValue("package_name", applicationId!!)
            setProductNames(
                appName = "Threema Libre",
                appNameDesktop = "Threema",
            )
            stringBuildConfigField("MEDIA_PATH", "ThreemaLibre")
        }
    }

    signingConfigs {
        // Debug config
        keystores["debug"]
            ?.let { keystore ->
                getByName("debug") {
                    storeFile = keystore.storeFile
                }
            }
            ?: run {
                logger.warn("No debug keystore found. Falling back to locally generated keystore.")
            }

        // Release config
        keystores["release"]
            ?.let { keystore ->
                create("release") {
                    apply(keystore)
                }
            }
            ?: run {
                logger.warn("No release keystore found. Falling back to locally generated keystore.")
            }

        // Release config
        keystores["hms_release"]
            ?.let { keystore ->
                create("hms_release") {
                    apply(keystore)
                }
            }
            ?: run {
                logger.warn("No hms keystore found. Falling back to locally generated keystore.")
            }

        // Onprem release config
        keystores["onprem_release"]
            ?.let { keystore ->
                create("onprem_release") {
                    apply(keystore)
                }
            }
            ?: run {
                logger.warn("No onprem keystore found. Falling back to locally generated keystore.")
            }

        // Blue release config
        keystores["blue_release"]
            ?.let { keystore ->
                create("blue_release") {
                    apply(keystore)
                }
            }
            ?: run {
                logger.warn("No blue keystore found. Falling back to locally generated keystore.")
            }

        // Note: Libre release is signed with HSM, no config here
    }

    sourceSets {
        getByName("main") {
            assets.srcDirs("assets")
            jniLibs.srcDirs("libs")
            res.srcDir("src/main/res-rendezvous")
            java.srcDir("./build/generated/source/protobuf/main/java")
            java.srcDir("./build/generated/source/protobuf/main/kotlin")
        }

        // Include Dev features only in debug builds and in release builds for flavors used only internally
        val devFlavors = arrayOf("blue", "none")
        productFlavors.names
            .flatMap { productFlavor ->
                buildList {
                    add("${productFlavor}Debug" to true)
                    if (productFlavor != "green" && productFlavor != "sandbox_work") {
                        add("${productFlavor}Release" to (productFlavor in devFlavors))
                    }
                }
            }
            .forEach { (name, needsDevFeatures) ->
                create(name) {
                    val srcPath = if (needsDevFeatures) "src/with_dev_support" else "src/without_dev_support"
                    java.srcDir("$srcPath/java")
                    manifest.srcFile("$srcPath/AndroidManifest.xml")
                    res.srcDir("$srcPath/res")
                }
            }

        // Based on Google services
        getByName("none") {
            java.srcDir("src/google_services_based/java")
        }
        getByName("store_google") {
            java.srcDir("src/google_services_based/java")
        }
        getByName("store_google_work") {
            java.srcDir("src/google_services_based/java")
        }
        getByName("store_threema") {
            java.srcDir("src/google_services_based/java")
        }
        getByName("libre") {
            assets.srcDirs("src/foss_based/assets")
            java.srcDir("src/foss_based/java")
        }
        getByName("onprem") {
            java.srcDir("src/google_services_based/java")
        }
        getByName("green") {
            java.srcDir("src/google_services_based/java")
            manifest.srcFile("src/store_google/AndroidManifest.xml")
        }
        getByName("sandbox_work") {
            java.srcDir("src/google_services_based/java")
            res.srcDir("src/store_google_work/res")
            manifest.srcFile("src/store_google_work/AndroidManifest.xml")
        }
        getByName("blue") {
            java.srcDir("src/google_services_based/java")
            res.srcDir("src/blue/res")
        }

        // Based on Huawei services
        getByName("hms") {
            java.srcDir("src/hms_services_based/java")
        }
        getByName("hms_work") {
            java.srcDir("src/hms_services_based/java")
            res.srcDir("src/store_google_work/res")
        }

        // FOSS, no proprietary services
        getByName("libre") {
            assets.srcDirs("src/foss_based/assets")
            java.srcDir("src/foss_based/java")
        }
    }

    buildTypes {
        debug {
            isDebuggable = true
            ndk {
                debugSymbolLevel = "FULL"
            }
            enableUnitTestCoverage = false
            enableAndroidTestCoverage = false

            if (keystores["debug"] != null) {
                signingConfig = signingConfigs["debug"]
            }
        }
        release {
            isDebuggable = false
            isMinifyEnabled = true
            isShrinkResources = false // Caused inconsistencies between local and CI builds
            vcsInfo.include = false // For reproducible builds independent from git history
            proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-project.txt")
            ndk {
                debugSymbolLevel = "FULL" // 'SYMBOL_TABLE'
            }

            if (keystores["release"] != null) {
                val releaseSigningConfig = signingConfigs["release"]
                productFlavors["store_google"].signingConfig = releaseSigningConfig
                productFlavors["store_google_work"].signingConfig = releaseSigningConfig
                productFlavors["store_threema"].signingConfig = releaseSigningConfig
                productFlavors["green"].signingConfig = releaseSigningConfig
                productFlavors["sandbox_work"].signingConfig = releaseSigningConfig
                productFlavors["none"].signingConfig = releaseSigningConfig
            }

            if (keystores["hms_release"] != null) {
                val hmsReleaseSigningConfig = signingConfigs["hms_release"]
                productFlavors["hms"].signingConfig = hmsReleaseSigningConfig
                productFlavors["hms_work"].signingConfig = hmsReleaseSigningConfig
            }

            if (keystores["onprem_release"] != null) {
                productFlavors["onprem"].signingConfig = signingConfigs["onprem_release"]
            }

            if (keystores["blue_release"] != null) {
                productFlavors["blue"].signingConfig = signingConfigs["blue_release"]
            }

            // Note: Libre release is signed with HSM, no config here
        }
    }

    packaging {
        jniLibs {
            // replacement for extractNativeLibs in AndroidManifest
            useLegacyPackaging = true
        }
        resources {
            excludes.addAll(
                setOf(
                    "META-INF/DEPENDENCIES.txt",
                    "META-INF/LICENSE.txt",
                    "META-INF/LICENSE.md",
                    "META-INF/LICENSE-notice.md",
                    "META-INF/NOTICE.txt",
                    "META-INF/NOTICE",
                    "META-INF/LICENSE",
                    "META-INF/DEPENDENCIES",
                    "META-INF/notice.txt",
                    "META-INF/license.txt",
                    "META-INF/dependencies.txt",
                    "META-INF/LGPL2.1",
                    "**/*.proto",
                    "DebugProbesKt.bin",
                ),
            )
        }
    }

    testOptions {
        // Disable animations in instrumentation tests
        animationsDisabled = true

        unitTests {
            all { test ->
                test.outputs.upToDateWhen { false }
                test.testLogging {
                    events("passed", "skipped", "failed", "standardOut", "standardError")
                    exceptionFormat = TestExceptionFormat.FULL
                }

                test.jvmArgs = test.jvmArgs!! + listOf(
                    "--add-opens=java.base/java.util=ALL-UNNAMED",
                    "--add-opens=java.base/java.util.stream=ALL-UNNAMED",
                    "--add-opens=java.base/java.lang=ALL-UNNAMED",
                    "-Xmx4096m",
                )
            }
            // By default, local unit tests throw an exception any time the code you are testing tries to access
            // Android platform APIs (unless you mock Android dependencies yourself or with a testing
            // framework like Mockk). However, you can enable the following property so that the test
            // returns either null or zero when accessing platform APIs, rather than throwing an exception.
            isReturnDefaultValues = true
        }
    }

    compileOptions {
        isCoreLibraryDesugaringEnabled = true
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }

    java {
        toolchain {
            languageVersion.set(JavaLanguageVersion.of(21))
        }
    }

    kotlin {
        jvmToolchain(21)
    }

    androidResources {
        noCompress.add("png")
    }

    lint {
        // if true, stop the gradle build if errors are found
        abortOnError = true
        // if true, check all issues, including those that are off by default
        checkAllWarnings = true
        // check dependencies
        checkDependencies = true
        // set to true to have all release builds run lint on issues with severity=fatal
        // and abort the build (controlled by abortOnError above) if fatal issues are found
        checkReleaseBuilds = true
        // turn off checking the given issue id's
        disable.addAll(setOf("TypographyFractions", "TypographyQuotes", "RtlHardcoded", "RtlCompat", "RtlEnabled"))
        // Set the severity of the given issues to error
        error.addAll(setOf("Wakelock", "TextViewEdits", "ResourceAsColor"))
        // Set the severity of the given issues to fatal (which means they will be
        // checked during release builds (even if the lint target is not included)
        fatal.addAll(setOf("NewApi", "InlinedApi", "LoggerName"))
        ignoreWarnings = false
        // if true, don't include source code lines in the error output
        noLines = false
        // if true, show all locations for an error, do not truncate lists, etc.
        showAll = true
        // Set the severity of the given issues to warning
        warning.add("MissingTranslation")
        // if true, treat all warnings as errors
        warningsAsErrors = false
        // file to write report to (if not specified, defaults to lint-results.xml)
        xmlOutput = file("lint-report.xml")
        // if true, generate an XML report for use by for example Jenkins
        xmlReport = true
    }

    buildFeatures {
        compose = true
        buildConfig = true
    }
}

composeCompiler {
    includeSourceInformation = true
    stabilityConfigurationFiles.add(rootProject.layout.projectDirectory.file("stability_config.conf"))
}

// Only build relevant buildType / flavor combinations
androidComponents {
    beforeVariants { variant ->
        val name = variant.name
        if (variant.buildType == "release" && ("green" in name || "sandbox_work" in name)) {
            variant.enable = false
        }
    }
}

dependencies {
    configurations.all {
        // Prefer modules that are part of this build (multi-project or composite build)
        // over external modules
        resolutionStrategy.preferProjectModules()

        // Alternatively, we can fail eagerly on version conflict to see the conflicts
        // resolutionStrategy.failOnVersionConflict()
    }

    coreLibraryDesugaring(libs.desugarJdkLibs)

    implementation(project(":domain"))
    implementation(project("::commonAndroid"))
    implementation(project(":common"))
    lintChecks(project(":lint-rules"))

    // Dependency Injection
    implementation(libs.koin.android)
    implementation(libs.koin.androidCompat)
    implementation(libs.koin.compose)
    testImplementation(libs.koin.test)
    testImplementation(libs.koin.test.junit4)

    implementation(libs.sqlcipher.android)

    implementation(libs.subsamplingScaleImageView)
    implementation(libs.opencsv)
    implementation(libs.zip4j)
    implementation(libs.taptargetview)
    implementation(libs.slf4j.api)
    implementation(libs.androidImageCropper)
    implementation(libs.fastscroll)
    implementation(libs.ezVcard)
    implementation(libs.gestureViews)

    // AndroidX / Jetpack support libraries
    implementation(libs.androidx.preference)
    implementation(libs.androidx.recyclerview)
    implementation(libs.androidx.palette)
    implementation(libs.androidx.swiperefreshlayout)
    implementation(libs.androidx.core)
    implementation(libs.androidx.appcompat)
    implementation(libs.androidx.constraintlayout)
    implementation(libs.androidx.biometric)
    implementation(libs.androidx.work.runtime)
    implementation(libs.androidx.fragment)
    implementation(libs.androidx.activity)
    implementation(libs.androidx.sqlite)
    implementation(libs.androidx.concurrent.futures)
    implementation(libs.androidx.camera2)
    implementation(libs.androidx.camera.lifecycle)
    implementation(libs.androidx.camera.view)
    implementation(libs.androidx.camera.video)
    implementation(libs.androidx.media)
    implementation(libs.androidx.media3.exoplayer)
    implementation(libs.androidx.media3.ui)
    implementation(libs.androidx.media3.session)
    implementation(libs.androidx.lifecycle.viewmodel)
    implementation(libs.androidx.lifecycle.livedata)
    implementation(libs.androidx.lifecycle.runtime)
    implementation(libs.androidx.lifecycle.viewmodel.savedstate)
    implementation(libs.androidx.lifecycle.service)
    implementation(libs.androidx.lifecycle.process)
    implementation(libs.androidx.lifecycle.commonJava8)
    implementation(libs.androidx.lifecycle.extensions)
    implementation(libs.androidx.paging.runtime)
    implementation(libs.androidx.sharetarget)
    implementation(libs.androidx.room.runtime)
    implementation(libs.androidx.window)
    implementation(libs.androidx.splashscreen)
    ksp(libs.androidx.room.compiler)

    // Jetpack Compose
    implementation(platform(libs.compose.bom))
    implementation(libs.androidx.material3)
    implementation(libs.androidx.ui.tooling.preview)
    implementation(libs.androidx.activity.compose)
    implementation(libs.androidx.lifecycle.viewmodel.compose)
    implementation(libs.androidx.lifecycle.runtime.compose)
    debugImplementation(libs.androidx.ui.tooling)
    debugImplementation(libs.androidx.ui.test.manifest)
    androidTestImplementation(platform(libs.compose.bom))

    implementation(libs.bcprov.jdk15to18)

    implementation(libs.material)
    implementation(libs.zxing)
    implementation(libs.libphonenumber)

    // webclient dependencies
    implementation(libs.msgpack.core)
    implementation(libs.jackson.core)
    implementation(libs.nvWebsocket.client)

    implementation(libs.saltyrtc.client) {
        exclude(group = "org.json")
    }

    implementation(libs.chunkedDc)
    implementation(libs.webrtcAndroid)
    implementation(libs.saltyrtc.taskWebrtc) {
        exclude(module = "saltyrtc-client")
    }

    // Glide components
    implementation(libs.glide)
    ksp(libs.glide.ksp)

    // Kotlin
    implementation(libs.kotlin.stdlib)
    implementation(libs.kotlinx.coroutines.android)
    implementation(libs.kotlinx.serialization.json)
    testImplementation(libs.kotlin.test)
    androidTestImplementation(libs.kotlin.test)

    // use leak canary in debug builds if requested
    if (project.hasProperty("leakCanary")) {
        debugImplementation(libs.leakcanary)
    }

    // test dependencies
    testImplementation(libs.junit)
    testImplementation(testFixtures(project(":domain")))

    // custom test helpers, shared between unit test and android tests
    testImplementation(project(":test-helpers"))
    androidTestImplementation(project(":test-helpers"))

    testImplementation(libs.mockk)
    androidTestImplementation(libs.mockkAndroid)

    // add JSON support to tests without mocking
    testImplementation(libs.json)

    testImplementation(libs.archunit.junit4)

    androidTestImplementation(testFixtures(project(":domain")))
    androidTestImplementation(libs.androidx.test.rules)
    androidTestImplementation(libs.fastlane.screengrab) {
        exclude(group = "androidx.annotation", module = "annotation")
    }
    androidTestImplementation(libs.androidx.espresso.core) {
        exclude(group = "androidx.annotation", module = "annotation")
    }
    androidTestImplementation(libs.androidx.test.runner) {
        exclude(group = "androidx.annotation", module = "annotation")
    }
    androidTestImplementation(libs.androidx.junit)
    androidTestImplementation(libs.androidx.espresso.contrib) {
        exclude(group = "androidx.annotation", module = "annotation")
        exclude(group = "androidx.appcompat", module = "appcompat")
        exclude(group = "androidx.legacy", module = "legacy-support-v4")
        exclude(group = "com.google.android.material", module = "material")
        exclude(group = "androidx.recyclerview", module = "recyclerview")
        exclude(group = "org.checkerframework", module = "checker")
        exclude(module = "protobuf-lite")
    }
    androidTestImplementation(libs.androidx.espresso.intents) {
        exclude(group = "androidx.annotation", module = "annotation")
    }
    androidTestImplementation(libs.androidx.test.uiautomator)
    androidTestImplementation(libs.androidx.test.core)
    androidTestImplementation(libs.kotlinx.coroutines.test)
    androidTestImplementation(libs.androidx.ui.test.junit4)
    testImplementation(libs.kotlinx.coroutines.test)

    // Google Play Services and related libraries
    "noneImplementation"(libs.playServices.base)
    "store_googleImplementation"(libs.playServices.base)
    "store_google_workImplementation"(libs.playServices.base)
    "store_threemaImplementation"(libs.playServices.base)
    "onpremImplementation"(libs.playServices.base)
    "greenImplementation"(libs.playServices.base)
    "sandbox_workImplementation"(libs.playServices.base)
    "blueImplementation"(libs.playServices.base)

    fun ExternalModuleDependency.excludeFirebaseDependencies() {
        exclude(group = "com.google.firebase", module = "firebase-core")
        exclude(group = "com.google.firebase", module = "firebase-analytics")
        exclude(group = "com.google.firebase", module = "firebase-measurement-connector")
    }
    "noneImplementation"(libs.firebase.messaging) { excludeFirebaseDependencies() }
    "store_googleImplementation"(libs.firebase.messaging) { excludeFirebaseDependencies() }
    "store_google_workImplementation"(libs.firebase.messaging) { excludeFirebaseDependencies() }
    "store_threemaImplementation"(libs.firebase.messaging) { excludeFirebaseDependencies() }
    "onpremImplementation"(libs.firebase.messaging) { excludeFirebaseDependencies() }
    "greenImplementation"(libs.firebase.messaging) { excludeFirebaseDependencies() }
    "sandbox_workImplementation"(libs.firebase.messaging) { excludeFirebaseDependencies() }
    "blueImplementation"(libs.firebase.messaging) { excludeFirebaseDependencies() }

    // Google Assistant Voice Action verification library
    "noneImplementation"(group = "", name = "libgsaverification-client", ext = "aar")
    "store_googleImplementation"(group = "", name = "libgsaverification-client", ext = "aar")
    "store_google_workImplementation"(group = "", name = "libgsaverification-client", ext = "aar")
    "onpremImplementation"(group = "", name = "libgsaverification-client", ext = "aar")
    "store_threemaImplementation"(group = "", name = "libgsaverification-client", ext = "aar")
    "greenImplementation"(group = "", name = "libgsaverification-client", ext = "aar")
    "sandbox_workImplementation"(group = "", name = "libgsaverification-client", ext = "aar")
    "blueImplementation"(group = "", name = "libgsaverification-client", ext = "aar")

    // Maplibre (may have transitive dependencies on Google location services)
    "noneImplementation"(libs.maplibre)
    "store_googleImplementation"(libs.maplibre)
    "store_google_workImplementation"(libs.maplibre)
    "store_threemaImplementation"(libs.maplibre)
    "libreImplementation"(libs.maplibre) {
        exclude(group = "com.google.android.gms")
    }
    "onpremImplementation"(libs.maplibre)
    "greenImplementation"(libs.maplibre)
    "sandbox_workImplementation"(libs.maplibre)
    "blueImplementation"(libs.maplibre)
    "hmsImplementation"(libs.maplibre)
    "hms_workImplementation"(libs.maplibre)

    // Huawei related libraries (only for hms* build variants)
    // Exclude agconnect dependency, we'll replace it with the vendored version below
    "hmsImplementation"(libs.hmsPush) {
        exclude(group = "com.huawei.agconnect")
    }
    "hms_workImplementation"(libs.hmsPush) {
        exclude(group = "com.huawei.agconnect")
    }
    "hmsImplementation"(group = "", name = "agconnect-core-1.9.1.301", ext = "aar")
    "hms_workImplementation"(group = "", name = "agconnect-core-1.9.1.301", ext = "aar")
}

// Define the cargo attributes. These will be used by the rust-android plugin that will create the
// 'cargoBuild' task that builds native libraries that will be added to the apk. Note that the
// kotlin bindings are created in the domain module. Building native libraries with rust-android
// cannot be done in any other module than 'app'.
cargo {
    prebuiltToolchains = true
    targetDirectory = "$projectDir/build/generated/source/libthreema"
    module = "$projectDir/../domain/libthreema" // must contain Cargo.toml
    libname = "libthreema" // must match the Cargo.toml's package name
    profile = "release"
    pythonCommand = "python3"
    targets = listOf("x86_64", "arm64", "arm", "x86")
    features {
        defaultAnd(arrayOf("uniffi"))
    }
    extraCargoBuildArguments = listOf("--lib", "--target-dir", "$projectDir/build/generated/source/libthreema", "--locked")
    verbose = false
}

afterEvaluate {
    // The `cargoBuild` task isn't available until after evaluation.
    android.applicationVariants.configureEach {
        val variantName = name.replaceFirstChar { it.uppercase() }
        // Set the dependency so that cargoBuild is executed before the native libs are merged
        tasks["merge${variantName}NativeLibs"].dependsOn(tasks["cargoBuild"])
    }
}

sonarqube {
    properties {
        property(
            "sonar.sources",
            listOf(
                "src/main/",
                "../scripts/",
                "../scripts-internal/",
            )
                .joinToString(separator = ", "),
        )
        property(
            "sonar.exclusions",
            listOf(
                "src/**/res/",
                "src/**/res-rendezvous/",
                "**/emojis/EmojiParser.kt",
                "**/emojis/EmojiSpritemap.kt",
            )
                .joinToString(separator = ", "),
        )
        property("sonar.tests", "src/test/")
        property("sonar.sourceEncoding", "UTF-8")
        property("sonar.verbose", "true")
        property("sonar.projectKey", "android-client")
        property("sonar.projectName", "Threema for Android")
    }
}

androidStem {
    includeLocalizedOnlyTemplates = true
}

tasks.register<Exec>("compileProto") {
    group = "build"
    description = "generate class bindings from protobuf files in the 'protobuf' directory"
    workingDir(project.projectDir)
    commandLine("./compile-proto.sh")
}
project.tasks.preBuild.dependsOn("compileProto")

tasks.withType<Test> {
    // Necessary to load the dynamic libthreema library in unit tests
    systemProperty("jna.library.path", "${project.projectDir}/../domain/libthreema/target/release")
}

// Set up Gradle tasks to fetch screenshots on UI test failures
// See https://medium.com/stepstone-tech/how-to-capture-screenshots-for-failed-ui-tests-9927eea6e1e4
val reportsDirectory = "${layout.buildDirectory}/reports/androidTests/connected"
val screenshotsDirectory = "/sdcard/testfailures/screenshots/"
val clearScreenshotsTask = tasks.register<Exec>("clearScreenshots") {
    executable = android.adbExecutable.toString()
    args("shell", "rm", "-r", screenshotsDirectory)
}
val createScreenshotsDirectoryTask = tasks.register<Exec>("createScreenshotsDirectory") {
    group = "reporting"
    executable = android.adbExecutable.toString()
    args("shell", "mkdir", "-p", screenshotsDirectory)
}
val fetchScreenshotsTask = tasks.register<Exec>("fetchScreenshots") {
    group = "reporting"
    executable = android.adbExecutable.toString()
    args("pull", "$screenshotsDirectory.", reportsDirectory)
    finalizedBy(clearScreenshotsTask)
    dependsOn(createScreenshotsDirectoryTask)
    doFirst {
        file(reportsDirectory).mkdirs()
    }
}
tasks.whenTaskAdded {
    if (name == "connectedDebugAndroidTest") {
        finalizedBy(fetchScreenshotsTask)
    }
}

// Let the compose compiler generate stability reports
tasks.withType<KotlinCompile>().configureEach {
    compilerOptions {
        val composeCompilerReportsPath = "${project.layout.buildDirectory.get().dir("compose_compiler").asFile.absolutePath}/reports"
        freeCompilerArgs.addAll(
            listOf(
                "-P",
                "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=$composeCompilerReportsPath",
            ),
        )
    }
}

[Verzeichnis aufwärts0.39unsichere VerbindungÜbersetzung europäischer Sprachen durch Browser2026-04-27]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge