plugins { // Gradle plugin portal alias(libs.plugins.tripletPlay) alias(libs.plugins.android.application) alias(libs.plugins.amazonappstorepublisher) alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.parcelize) alias(libs.plugins.kotlin.serialization) alias(libs.plugins.keeper) id 'idea' } repositories { google() mavenCentral() maven { url "https://jitpack.io" } } keeper { traceReferences { // Silence missing definitions arguments.set(["--map-diagnostics:MissingDefinitionsDiagnostic", "error", "none"]) } } idea { module { downloadJavadoc = System.getenv("CI") != "true" downloadSources = System.getenv("CI") != "true" } } def homePath = System.properties['user.home'] /** * @return the current git hash * @example edf739d95bad7b370a6ed4398d46723f8219b3cd */ static def gitCommitHash() { "git rev-parse HEAD".execute().text.trim() } android { namespace "com.ichi2.anki" compileSdk libs.versions.compileSdk.get().toInteger() buildFeatures { buildConfig = true aidl = true } if (rootProject.testReleaseBuild) { testBuildType "release" } else { testBuildType "debug" } defaultConfig { applicationId "com.ichi2.anki" buildConfigField "Boolean", "CI", (System.getenv("CI") == "true").toString() buildConfigField "String", "ACRA_URL", '"https://ankidroid.org/acra/report"' buildConfigField "String", "BACKEND_VERSION", "\"${libs.versions.ankiBackend.get()}\"" buildConfigField "Boolean", "ENABLE_LEAK_CANARY", "false" buildConfigField "Boolean", "ALLOW_UNSAFE_MIGRATION", "false" buildConfigField "String", "GIT_COMMIT_HASH", "\"${gitCommitHash()}\"" buildConfigField "long", "BUILD_TIME", System.currentTimeMillis().toString() resValue "string", "app_name", "AnkiDroid" // The version number is of the form: // ..[dev|alpha|beta|] // The is only present for alpha and beta releases (e.g., 2.0.4alpha2 or 2.0.4beta4), developer builds do // not have a build number (e.g., 2.0.4dev) and official releases only have three components (e.g., 2.0.4). // // The version code is derived from the version name as follows: // AbbCCtDD // A: 1-digit decimal number representing the major version // bb: 2-digit decimal number representing the minor version // CC: 2-digit decimal number representing the maintenance version // t: 1-digit decimal number representing the type of the build // 0: developer build // 1: alpha release // 2: beta release // 3: public release // DD: 2-digit decimal number representing the build // 00 for internal builds and public releases // alpha/beta build number for alpha/beta releases // // This ensures the correct ordering between the various types of releases (dev < alpha < beta < release) which is // needed for upgrades to be offered correctly. versionCode=21900108 versionName="2.19alpha8" minSdk libs.versions.minSdk.get().toInteger() // Stays until this is in a release: https://github.com/google/desugar_jdk_libs/commit/c01a5446ca13586b801dbba4d83c6821337b3cc2 if (testReleaseBuild && minSdk < 24) { minSdk 24 } // After #13695: change .tests_emulator.yml targetSdk libs.versions.targetSdk.get().toInteger() testApplicationId "com.ichi2.anki.tests" vectorDrawables.useSupportLibrary = true testInstrumentationRunner 'com.ichi2.testutils.NewCollectionPathTestRunner' } signingConfigs { release { storeFile file(System.getenv("KEYSTOREPATH") ?: "${homePath}/src/android-keystore") storePassword System.getenv("KEYSTOREPWD") ?: System.getenv("KSTOREPWD") keyAlias System.getenv("KEYALIAS") ?: "nrkeystorealias" keyPassword System.getenv("KEYPWD") } } buildTypes { named('debug') { versionNameSuffix "-debug" debuggable true applicationIdSuffix ".debug" splits.abi.universalApk = true // Build universal APK for debug always // Check Crash Reports page on developer wiki for info on ACRA testing // buildConfigField "String", "ACRA_URL", '"https://918f7f55-f238-436c-b34f-c8b5f1331fe5-bluemix.cloudant.com/acra-ankidroid/_design/acra-storage/_update/report"' if (project.rootProject.file('local.properties').exists()) { Properties localProperties = new Properties() localProperties.load(project.rootProject.file('local.properties').newDataInputStream()) // #6009 Allow optional disabling of JaCoCo for general build (assembleDebug). // jacocoDebug task was slow, hung, and wasn't required unless I wanted coverage testCoverageEnabled localProperties['enable_coverage'] != "false" // not profiled: optimization for build times if (localProperties['enable_languages'] == "false") { android.defaultConfig.resConfigs "en" } // allows the scoped storage migration when the user is not logged in if (localProperties["allow_unsafe_migration"] != null) { buildConfigField "Boolean", "ALLOW_UNSAFE_MIGRATION", localProperties["allow_unsafe_migration"] } // allow disabling leak canary if (localProperties["enable_leak_canary"] != null) { buildConfigField "Boolean", "ENABLE_LEAK_CANARY", localProperties["enable_leak_canary"] } else { buildConfigField "Boolean", "ENABLE_LEAK_CANARY", "true" } } else { testCoverageEnabled true } // make the icon red if in debug mode resValue 'color', 'anki_foreground_icon_color_0', "#FFFF0000" resValue 'color', 'anki_foreground_icon_color_1', "#FFFF0000" resValue "string", "applicationId", "${defaultConfig.applicationId}${applicationIdSuffix}" } named('release') { testCoverageEnabled = testReleaseBuild minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' testProguardFile 'proguard-test-rules.pro' splits.abi.universalApk = universalApkEnabled // Build universal APK for release with `-Duniversal-apk=true` signingConfig signingConfigs.release // syntax: assembleRelease -PcustomSuffix="suffix" -PcustomName="New name" if (project.hasProperty("customSuffix")) { // the suffix needs a '.' at the start applicationIdSuffix project.property("customSuffix").replaceFirst(/^\.*/, ".") resValue "string", "applicationId", "${defaultConfig.applicationId}${applicationIdSuffix}" } else { resValue "string", "applicationId", defaultConfig.applicationId } if (project.hasProperty("customName")) { resValue "string", "app_name", project.property("customName") } resValue 'color', 'anki_foreground_icon_color_0', "#FF29B6F6" resValue 'color', 'anki_foreground_icon_color_1', "#FF0288D1" } } /** * Product Flavors are used for Amazon App Store and Google Play Store. * This is because we cannot use Camera Permissions in Amazon App Store (for FireTv etc...) * Therefore, different AndroidManifest for Camera Permissions is used in Amazon flavor. * * This flavor block must stay in sync with the same block in testlib/build.gradle.kts */ flavorDimensions += "appStore" productFlavors { create('play') { dimension "appStore" } create('amazon') { dimension "appStore" } // A 'full' build has no restrictions on storage/camera. Distributed on GitHub/F-Droid create('full') { dimension "appStore" } } /** * Set this to true to create five separate APKs instead of one: * - 2 APKs that only work on ARM/ARM64 devices * - 2 APKs that only works on x86/x86_64 devices * - a universal APK that works on all devices * The advantage is the size of most APKs is reduced by about 2.5MB. * Upload all the APKs to the Play Store and people will download * the correct one based on the CPU architecture of their device. */ def enableSeparateBuildPerCPUArchitecture = true splits { abi { reset() enable enableSeparateBuildPerCPUArchitecture //universalApk enableUniversalApk // set in debug + release config blocks above include "armeabi-v7a", "x86", "arm64-v8a", "x86_64" } } // applicationVariants are e.g. debug, release applicationVariants.configureEach { variant -> // We want the same version stream for all ABIs in debug but for release we can split them if (variant.buildType.name == 'release') { variant.outputs.configureEach { output -> // For each separate APK per architecture, set a unique version code as described here: // https://developer.android.com/studio/build/configure-apk-splits.html def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4] def outputFile = output.outputFile if (outputFile != null && outputFile.name.endsWith('.apk')) { def abi = output.getFilter("ABI") if (abi != null) { // null for the universal-debug, universal-release variants // From: https://developer.android.com/studio/publish/versioning#appversioning // "Warning: The greatest value Google Play allows for versionCode is 2100000000" // AnkiDroid versionCodes have a budget 8 digits (through AnkiDroid 9) // This style does ABI version code ranges with the 9th digit as 0-4. // This consumes ~20% of the version range space, w/50 years of versioning at our major-version pace output.versionCodeOverride = // ex: 321200106 = 3 * 100000000 + 21200106 versionCodes.get(abi) * 100000000 + defaultConfig.versionCode } } } } } testOptions { animationsDisabled true kotlinOptions { freeCompilerArgs += [ '-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi', ] } } compileOptions { sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 coreLibraryDesugaringEnabled true } kotlinOptions { jvmTarget = JavaVersion.VERSION_11 } packagingOptions { resources { excludes += ['META-INF/DEPENDENCIES'] } } } play { serviceAccountCredentials.set(file("${homePath}/src/AnkiDroid-GCP-Publish-Credentials.json")) track.set('alpha') } amazon { securityProfile = file("${homePath}/src/AnkiDroid-Amazon-Publish-Security-Profile.json") applicationId = "amzn1.devportal.mobileapp.524a424d314931494c55383833305539" pathToApks = [ file("./build/outputs/apk/amazon/release/AnkiDroid-amazon-universal-release.apk") ] replaceEdit = true } // Install Git pre-commit hook for Ktlint tasks.register('installGitHook', Copy) { from new File(rootProject.rootDir, 'pre-commit') into { new File(rootProject.rootDir, '.git/hooks') } fileMode 0755 } // to run manually: `./gradlew installGitHook` tasks.named('preBuild').configure { dependsOn('installGitHook') } tasks.register('copyTestLibIntoAndroidTest', Copy) { into new File(rootProject.rootDir, 'AnkiDroid/src/androidTest/java/com/ichi2/testutils') from new File(rootProject.rootDir, 'testlib/src/main/java/com/ichi2/testutils') into ('common') { from 'common' } } tasks.named('preBuild').configure { dependsOn('copyTestLibIntoAndroidTest') } tasks.named('runKtlintCheckOverAndroidTestSourceSet').configure { mustRunAfter('copyTestLibIntoAndroidTest') } // Issue 11078 - some emulators run, but run zero tests, and still report success tasks.register('assertNonzeroAndroidTests') { doLast { // androidTest currently creates one .xml file per emulator with aggregate results in this dir File folder = file("./build/outputs/androidTest-results/connected/flavors/play") File[] listOfFiles = folder.listFiles({ d, f -> f ==~ /.*.xml/ } as FilenameFilter) for (File file : listOfFiles) { // The aggregate results file currently contains a line with this pattern holding test count String[] matches = file.readLines().findAll { it.contains('