import com.android.build.api.variant.FilterConfiguration import java.time.format.DateTimeFormatter buildscript { repositories { gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORIES.each { repository -> maven { url = repository if (gradle.mozconfig.substs.ALLOW_INSECURE_GRADLE_REPOSITORIES) { allowInsecureProtocol = true } } } } dependencies { classpath libs.plugin.serialization } } plugins { alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.compose) alias(libs.plugins.python.envs.plugin) alias(libs.plugins.protobuf.plugin) } apply plugin: 'com.android.application' apply plugin: 'kotlin-parcelize' apply plugin: 'jacoco' apply plugin: 'androidx.navigation.safeargs.kotlin' apply plugin: 'kotlinx-serialization' if (gradle.mozconfig.substs.MOZILLA_OFFICIAL) { apply plugin: 'com.google.android.gms.oss-licenses-plugin' } import groovy.json.JsonOutput import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import static org.gradle.api.tasks.testing.TestResult.ResultType apply from: 'benchmark.gradle' def isAppBundle = gradle.startParameter.taskNames.any { it.toLowerCase().contains("bundle") } // Mimic Python: open(os.path.join(buildconfig.topobjdir, 'buildid.h')).readline().split()[2] def getBuildId() { if (System.env.MOZ_BUILD_DATE) { if (System.env.MOZ_BUILD_DATE.length() == 14) { return System.env.MOZ_BUILD_DATE } logger.warn("Ignoring invalid MOZ_BUILD_DATE: ${System.env.MOZ_BUILD_DATE}") } return file("${gradle.mozconfig.topobjdir}/buildid.h").getText('utf-8').split()[2] } android { project.maybeConfigForJetpackBenchmark(it) if (project.hasProperty("testBuildType")) { // Allowing to configure the test build type via command line flag (./gradlew -PtestBuildType=beta ..) // in order to run UI tests against other build variants than debug in automation. testBuildType project.property("testBuildType") } defaultConfig { applicationId "org.mozilla" minSdkVersion config.minSdkVersion compileSdk config.compileSdkVersion targetSdkVersion config.targetSdkVersion versionCode 1 versionName Config.generateDebugVersionName() vectorDrawables.useSupportLibrary = true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunnerArguments clearPackageData: 'true' testInstrumentationRunnerArguments "detect-leaks": 'true' resValue "bool", "IS_DEBUG", "false" buildConfigField "boolean", "USE_RELEASE_VERSIONING", "false" // Blank in debug builds so that tests are deterministic. buildConfigField "String", "VCS_HASH", "\"\"" // This should be the "public" base URL of AMO. buildConfigField "String", "AMO_BASE_URL", "\"https://addons.mozilla.org\"" buildConfigField "String", "AMO_COLLECTION_NAME", "\"Extensions-for-Android\"" buildConfigField "String", "AMO_COLLECTION_USER", "\"mozilla\"" // This should be the base URL used to call the AMO API. buildConfigField "String", "AMO_SERVER_URL", "\"https://services.addons.mozilla.org\"" def deepLinkSchemeValue = "fenix-dev" buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\"" manifestPlaceholders = [ "deepLinkScheme": deepLinkSchemeValue ] buildConfigField "String[]", "SUPPORTED_LOCALE_ARRAY", getSupportedLocales() } def releaseTemplate = { // We allow disabling optimization by passing `-PdisableOptimization` to gradle. This is used // in automation for UI testing non-debug builds. shrinkResources = !project.hasProperty("disableOptimization") minifyEnabled = !project.hasProperty("disableOptimization") proguardFiles = [getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'] matchingFallbacks = ['release'] // Use on the "release" build type in dependencies (AARs) // Present in release builds. if (gradle.mozconfig.substs.MOZ_INCLUDE_SOURCE_INFO) { buildConfigField("String", "VCS_HASH", "\"" + gradle.mozconfig.source_repo.MOZ_SOURCE_STAMP + "\"") } if (gradle.hasProperty("localProperties.autosignReleaseWithDebugKey") || gradle.mozconfig.substs.DEVELOPER_OPTIONS) { signingConfig signingConfigs.debug } if (gradle.hasProperty("localProperties.debuggable") || gradle.mozconfig.substs.MOZ_ANDROID_DEBUGGABLE) { debuggable true } } buildTypes { debug { shrinkResources = false minifyEnabled = false applicationIdSuffix ".fenix.debug" resValue "bool", "IS_DEBUG", "true" pseudoLocalesEnabled = true } nightly releaseTemplate >> { applicationIdSuffix ".fenix" buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true" def deepLinkSchemeValue = "fenix-nightly" buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\"" manifestPlaceholders.putAll([ "deepLinkScheme": deepLinkSchemeValue ]) } beta releaseTemplate >> { buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true" applicationIdSuffix ".firefox_beta" def deepLinkSchemeValue = "fenix-beta" buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\"" manifestPlaceholders.putAll([ // This release type is meant to replace Firefox (Beta channel) and therefore needs to inherit // its sharedUserId for all eternity. See: // https://searchfox.org/mozilla-esr68/search?q=moz_android_shared_id&case=false®exp=false&path= // Shipping an app update without sharedUserId can have // fatal consequences. For example see: // - https://issuetracker.google.com/issues/36924841 // - https://issuetracker.google.com/issues/36905922 "sharedUserId": "org.mozilla.firefox.sharedID", "deepLinkScheme": deepLinkSchemeValue, ]) } release releaseTemplate >> { buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true" applicationIdSuffix ".firefox" def deepLinkSchemeValue = "fenix" buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\"" manifestPlaceholders.putAll([ // This release type is meant to replace Firefox (Release channel) and therefore needs to inherit // its sharedUserId for all eternity. See: // https://searchfox.org/mozilla-esr68/search?q=moz_android_shared_id&case=false®exp=false&path= // Shipping an app update without sharedUserId can have // fatal consequences. For example see: // - https://issuetracker.google.com/issues/36924841 // - https://issuetracker.google.com/issues/36905922 "sharedUserId": "org.mozilla.firefox.sharedID", "deepLinkScheme": deepLinkSchemeValue, ]) } benchmark releaseTemplate >> { initWith buildTypes.nightly applicationIdSuffix ".fenix" signingConfig = signingConfigs.debug debuggable false } } buildFeatures { viewBinding = true buildConfig = true } androidResources { // All JavaScript code used internally by GeckoView is packaged in a // file called omni.ja. If this file is compressed in the APK, // GeckoView must uncompress it before it can do anything else which // causes a significant delay on startup. noCompress 'ja' // manifest.template.json is converted to manifest.json at build time. // No need to package the template in the APK. ignoreAssetsPattern = "manifest.template.json" } testOptions { execution = 'ANDROIDX_TEST_ORCHESTRATOR' unitTests.includeAndroidResources = true animationsDisabled = true } flavorDimensions.add("product") productFlavors { fenix { dimension "product" } } sourceSets { androidTest { resources.srcDirs += ['src/androidTest/resources'] } if (project.hasProperty('baselineProfilePath')) { main { baselineProfiles.srcDirs(project.property('baselineProfilePath')) } } } splits { abi { enable = !isAppBundle reset() // As gradle is unable to pick the right apk to install when multiple apks are generated // while running benchmark tests or generating baseline profiles. To circumvent this, // this flag is passed to make sure only one apk is generated so gradle can pick that one. if (project.hasProperty("benchmarkTest")) { include "arm64-v8a" } else { include "x86", "armeabi-v7a", "arm64-v8a", "x86_64" } } } bundle { // Profiler issues require us to temporarily package native code compressed to // match the previous APK packaging. // https://bugzilla.mozilla.org/show_bug.cgi?id=1865634 packaging { jniLibs { it.useLegacyPackaging = true } } language { // Because we have runtime language selection we will keep all strings and languages // in the base APKs. enableSplit = false } } lint { lintConfig = file("lint.xml") baseline = file("lint-baseline.xml") sarifReport = true sarifOutput = file("../build/reports/lint/lint-report.sarif.json") abortOnError = false } // https://issuetracker.google.com/issues/379732901 packaging { resources { excludes += ["META-INF/LICENSE.md", "META-INF/LICENSE-notice.md", "META-INF/versions/9/OSGI-INF/MANIFEST.MF"] } jniLibs { useLegacyPackaging = true } } testOptions { unitTests.returnDefaultValues = true unitTests.all { // We keep running into memory issues when running our tests. With this config we // reserve more memory and also create a new process after every 80 test classes. This // is a band-aid solution and eventually we should try to find and fix the leaks // instead. :) forkEvery = 80 maxHeapSize = "3072m" minHeapSize = "1024m" } } buildFeatures { compose = true } compileOptions { coreLibraryDesugaringEnabled = true } namespace = 'org.mozilla.fenix' } android.applicationVariants.configureEach { variant -> // ------------------------------------------------------------------------------------------------- // Generate version codes for builds // ------------------------------------------------------------------------------------------------- def isDebug = variant.buildType.resValues['bool/IS_DEBUG']?.value ?: false def useReleaseVersioning = variant.buildType.buildConfigFields['USE_RELEASE_VERSIONING']?.value ?: false project.logger.debug("----------------------------------------------") project.logger.debug("Variant name: " + variant.name) project.logger.debug("Application ID: " + [variant.applicationId, variant.buildType.applicationIdSuffix].findAll().join()) project.logger.debug("Build type: " + variant.buildType.name) project.logger.debug("Flavor: " + variant.flavorName) project.logger.debug("Telemetry enabled: " + !isDebug) if (useReleaseVersioning) { // The Google Play Store does not allow multiple APKs for the same app that all have the // same version code. Therefore we need to have different version codes for our ARM and x86 // builds. def versionName = variant.buildType.name == 'nightly' ? "${Config.nightlyVersionName(project)}" : "${Config.releaseVersionName(project)}" project.logger.debug("versionName override: $versionName") variant.outputs.each { output -> def abi = output.getFilter(FilterConfiguration.FilterType.ABI.name()) if (isAppBundle) { abi = "AAB" } // We use the same version code generator, that we inherited from Fennec, across all channels - even on // channels that never shipped a Fennec build. def versionCodeOverride = Config.generateFennecVersionCode(abi, isAppBundle ) project.logger.debug("versionCode for $abi = $versionCodeOverride") if (versionName != null) { output.versionNameOverride = versionName } output.versionCodeOverride = versionCodeOverride } } else if (gradle.hasProperty("localProperties.branchBuild.fenix.version")) { def versionName = gradle.getProperty("localProperties.branchBuild.fenix.version") project.logger.debug("versionName override: $versionName") variant.outputs.each { output -> output.versionNameOverride = versionName } } // ------------------------------------------------------------------------------------------------- // BuildConfig: Set variables for Sentry, Crash Reporting, and Telemetry // ------------------------------------------------------------------------------------------------- buildConfigField 'String', 'SENTRY_TOKEN', 'null' if (!isDebug) { buildConfigField 'boolean', 'CRASH_REPORTING', 'true' // Reading sentry token from local file (if it exists). In a release task on taskcluster it will be available. try { def token = new File("${rootDir}/.sentry_token").text.trim() buildConfigField 'String', 'SENTRY_TOKEN', '"' + token + '"' } catch (FileNotFoundException ignored) {} } else { buildConfigField 'boolean', 'CRASH_REPORTING', 'false' } if (!isDebug) { buildConfigField 'boolean', 'TELEMETRY', 'true' } else { buildConfigField 'boolean', 'TELEMETRY', 'false' } // Setting buildDate with every build ID changes the generated BuildConfig, which slows down the // build. Only do this for non-debug builds, to speed-up builds produced during local development. if (isDebug) { buildConfigField 'String', 'BUILD_DATE', '"debug build"' } else { def buildDate = LocalDateTime.parse(getBuildId(), DateTimeFormatter.ofPattern("yyyyMMddHHmmss")).toString() buildConfigField 'String', 'BUILD_DATE', '"' + buildDate + '"' } // ------------------------------------------------------------------------------------------------- // Adjust: Read token from local file if it exists (Only release builds) // ------------------------------------------------------------------------------------------------- project.logger.debug("Adjust token: ") if (!isDebug) { try { def token = new File("${rootDir}/.adjust_token").text.trim() buildConfigField 'String', 'ADJUST_TOKEN', '"' + token + '"' project.logger.debug("(Added from .adjust_token file)") } catch (FileNotFoundException ignored) { buildConfigField 'String', 'ADJUST_TOKEN', 'null' project.logger.debug("X_X") } } else { buildConfigField 'String', 'ADJUST_TOKEN', 'null' project.logger.debug("--") } // ------------------------------------------------------------------------------------------------- // MLS: Read token from local file if it exists // ------------------------------------------------------------------------------------------------- project.logger.debug("MLS token: ") try { def token = new File("${rootDir}/.mls_token").text.trim() buildConfigField 'String', 'MLS_TOKEN', '"' + token + '"' project.logger.debug("(Added from .mls_token file)") } catch (FileNotFoundException ignored) { buildConfigField 'String', 'MLS_TOKEN', '""' project.logger.debug("X_X") } // ------------------------------------------------------------------------------------------------- // Nimbus: Read endpoint from local.properties of a local file if it exists // ------------------------------------------------------------------------------------------------- project.logger.debug("Nimbus endpoint: ") if (!isDebug) { try { def url = new File("${rootDir}/.nimbus").text.trim() buildConfigField 'String', 'NIMBUS_ENDPOINT', '"' + url + '"' project.logger.debug("(Added from .nimbus file)") } catch (FileNotFoundException ignored) { buildConfigField 'String', 'NIMBUS_ENDPOINT', 'null' project.logger.debug("X_X") } } else if (gradle.hasProperty("localProperties.nimbus.remote-settings.url")) { def url=gradle.getProperty("localProperties.nimbus.remote-settings.url") buildConfigField 'String', 'NIMBUS_ENDPOINT', '"' + url + '"' project.logger.debug("(Added from local.properties file)") } else { buildConfigField 'String', 'NIMBUS_ENDPOINT', 'null' project.logger.debug("--") } // ------------------------------------------------------------------------------------------------- // Glean: Read custom server URL from local.properties of a local file if it exists // ------------------------------------------------------------------------------------------------- project.logger.debug("Glean custom server URL: ") if (gradle.hasProperty("localProperties.glean.custom.server.url")) { def url=gradle.getProperty("localProperties.glean.custom.server.url") buildConfigField 'String', 'GLEAN_CUSTOM_URL', url project.logger.debug("(Added from local.properties file)") } else { buildConfigField 'String', 'GLEAN_CUSTOM_URL', 'null' project.logger.debug("--") } // ------------------------------------------------------------------------------------------------- // BuildConfig: Set flag for official builds; similar to MOZILLA_OFFICIAL in mozilla-central. // ------------------------------------------------------------------------------------------------- if (project.hasProperty("official") || gradle.hasProperty("localProperties.official")) { buildConfigField 'Boolean', 'MOZILLA_OFFICIAL', 'true' } else { buildConfigField 'Boolean', 'MOZILLA_OFFICIAL', 'false' } // ------------------------------------------------------------------------------------------------- // BuildConfig: Set remote wallpaper URL using local file if it exists // ------------------------------------------------------------------------------------------------- project.logger.debug("Wallpaper URL: ") try { def token = new File("${rootDir}/.wallpaper_url").text.trim() buildConfigField 'String', 'WALLPAPER_URL', '"' + token + '"' project.logger.debug("(Added from .wallpaper_url file)") } catch (FileNotFoundException ignored) { buildConfigField 'String', 'WALLPAPER_URL', '""' project.logger.debug("--") } // ------------------------------------------------------------------------------------------------- // BuildConfig: Set the Pocket consumer key from a local file if it exists // ------------------------------------------------------------------------------------------------- project.logger.debug("Pocket consumer key: ") try { def token = new File("${rootDir}/.pocket_consumer_key").text.trim() buildConfigField 'String', 'POCKET_CONSUMER_KEY', '"' + token + '"' project.logger.debug("(Added from .pocket_consumer_key file)") } catch (FileNotFoundException ignored) { buildConfigField 'String', 'POCKET_CONSUMER_KEY', '""' project.logger.debug("--") } // ------------------------------------------------------------------------------------------------- // BuildConfig: Set flag to disable LeakCanary in debug (on CI builds) // ------------------------------------------------------------------------------------------------- if (isDebug) { if (project.hasProperty("disableLeakCanary") || gradle.hasProperty("localProperties.disableLeakCanary")) { buildConfigField "boolean", "LEAKCANARY", "false" project.logger.debug("LeakCanary enabled in debug: false") } else { buildConfigField "boolean", "LEAKCANARY", "true" project.logger.debug("LeakCanary enabled in debug: true") } } else { buildConfigField "boolean", "LEAKCANARY", "false" } } // Generate Kotlin code for the Fenix Glean metrics. ext { // Enable expiration by major version. gleanExpireByVersion = Config.majorVersion(project) gleanNamespace = "mozilla.telemetry.glean" gleanPythonEnvDir = gradle.mozconfig.substs.GRADLE_GLEAN_PARSER_VENV } apply plugin: "org.mozilla.telemetry.glean-gradle-plugin" apply plugin: "org.mozilla.appservices.nimbus-gradle-plugin" nimbus { // The path to the Nimbus feature manifest file manifestFile = "nimbus.fml.yaml" // The fully qualified class name for the generated features. // Map from the variant name to the channel as experimenter and nimbus understand it. // If nimbus's channels were accurately set up well for this project, then this // shouldn't be needed. channels = [ fenixDebug: "developer", fenixNightly: "nightly", fenixBeta: "beta", fenixRelease: "release", fenixBenchmark: "developer", ] // This is generated by the FML and should be checked into git. // It will be fetched by Experimenter (the Nimbus experiment website) // and used to inform experiment configuration. experimenterManifest = ".experimenter.yaml" applicationServicesDir = gradle.hasProperty('localProperties.autoPublish.application-services.dir') ? gradle.getProperty('localProperties.autoPublish.application-services.dir') : null } tasks.withType(KotlinCompile).configureEach { kotlinOptions.freeCompilerArgs += "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi" } dependencies { coreLibraryDesugaring libs.desugar.jdk.libs implementation libs.kotlin.json implementation project(':browser-engine-gecko') implementation libs.kotlin.coroutines testImplementation libs.testing.coroutines implementation libs.accompanist.drawablepainter implementation libs.thirdparty.sentry implementation project(':compose-awesomebar') implementation project(':compose-base') implementation project(':compose-browser-toolbar') implementation project(':compose-cfr') implementation project(':concept-awesomebar') implementation project(':concept-base') implementation project(':concept-engine') implementation project(':concept-menu') implementation project(':concept-push') implementation project(':concept-storage') implementation project(':concept-sync') implementation project(':concept-toolbar') implementation project(':concept-tabstray') implementation project(':browser-domains') implementation project(':browser-icons') implementation project(':browser-menu') implementation project(':browser-menu2') implementation project(':browser-session-storage') implementation project(':browser-state') implementation project(':browser-storage-sync') implementation project(':browser-tabstray') implementation project(':browser-thumbnails') implementation project(':browser-toolbar') implementation project(':feature-addons') implementation project(':feature-accounts') implementation project(':feature-app-links') implementation project(':feature-autofill') implementation project(':feature-awesomebar') implementation project(':feature-contextmenu') implementation project(':feature-customtabs') implementation project(':feature-downloads') implementation project(':feature-fxsuggest') implementation project(':feature-intent') implementation project(':feature-media') implementation project(':feature-prompts') implementation project(':feature-push') implementation project(':feature-privatemode') implementation project(':feature-pwa') implementation project(':feature-qr') implementation project(':feature-search') implementation project(':feature-session') implementation project(':feature-syncedtabs') implementation project(':feature-toolbar') implementation project(':feature-tabs') implementation project(':feature-findinpage') implementation project(':feature-logins') implementation project(':feature-sitepermissions') implementation project(':feature-readerview') implementation project(':feature-tab-collections') implementation project(':feature-recentlyclosed') implementation project(':feature-top-sites') implementation project(':feature-share') implementation project(':feature-accounts-push') implementation project(':feature-webauthn') implementation project(':feature-webcompat') implementation project(':feature-webnotifications') implementation project(':feature-webcompat-reporter') implementation project(':service-pocket') implementation project(':service-mars') implementation project(':service-digitalassetlinks') implementation project(':service-sync-autofill') implementation project(':service-sync-logins') implementation project(':service-firefox-accounts') implementation project(':service-glean') implementation libs.mozilla.glean implementation project(':service-location') implementation project(':service-nimbus') implementation project(':support-webextensions') implementation project(':support-base') implementation project(':support-remotesettings') implementation project(':support-rusterrors') implementation project(':support-images') implementation project(':support-ktx') implementation project(':support-rustlog') implementation project(':support-utils') implementation project(':support-locale') implementation project(':ui-colors') implementation project(':ui-icons') implementation project(':lib-publicsuffixlist') implementation project(':ui-widgets') implementation project(':ui-tabcounter') implementation project(':lib-crash') implementation project(':lib-crash-sentry') implementation project(':lib-push-firebase') implementation project(':lib-state') implementation project(':lib-dataprotect') testImplementation project(':support-test-fakes') debugImplementation libs.leakcanary debugImplementation libs.androidx.compose.ui.tooling implementation libs.androidx.activity implementation libs.androidx.activity.ktx implementation libs.androidx.annotation implementation libs.androidx.appcompat implementation libs.androidx.biometric implementation platform(libs.androidx.compose.bom) androidTestImplementation platform(libs.androidx.compose.bom) implementation libs.androidx.compose.animation implementation libs.androidx.compose.foundation implementation libs.androidx.compose.material implementation libs.androidx.compose.ui implementation libs.androidx.compose.ui.tooling.preview implementation libs.androidx.constraintlayout implementation libs.androidx.coordinatorlayout implementation libs.androidx.core implementation libs.androidx.core.ktx implementation libs.androidx.core.splashscreen implementation libs.androidx.datastore implementation libs.androidx.datastore.preferences implementation libs.androidx.fragment implementation libs.androidx.lifecycle.common implementation libs.androidx.lifecycle.livedata implementation libs.androidx.lifecycle.process implementation libs.androidx.lifecycle.runtime implementation libs.androidx.lifecycle.service implementation libs.androidx.lifecycle.viewmodel implementation libs.androidx.localbroadcastmanager implementation libs.androidx.navigation.compose implementation libs.androidx.navigation.fragment implementation libs.androidx.navigation.ui implementation libs.androidx.paging implementation libs.androidx.preferences implementation libs.androidx.profileinstaller implementation libs.androidx.recyclerview implementation libs.androidx.swiperefreshlayout implementation libs.androidx.transition implementation libs.androidx.viewpager2 implementation libs.androidx.work.runtime implementation libs.protobuf.javalite implementation libs.google.material implementation(libs.adjust) implementation(libs.installreferrer) // Required for the Google Advertising ID implementation libs.play.services.ads.id // Required for in-app reviews implementation libs.play.review implementation libs.play.review.ktx implementation ComponentsDependencies.mozilla_appservices_init_rust_components androidTestImplementation(libs.androidx.espresso.contrib) { exclude module: 'protobuf-lite' } androidTestImplementation libs.androidx.espresso.core androidTestImplementation libs.androidx.espresso.idling.resource androidTestImplementation libs.androidx.espresso.intents androidTestImplementation libs.androidx.test.core androidTestImplementation libs.androidx.test.junit androidTestImplementation libs.androidx.test.monitor androidTestImplementation libs.androidx.test.rules androidTestImplementation libs.androidx.test.runner androidTestImplementation libs.androidx.test.uiautomator androidTestUtil libs.androidx.test.orchestrator androidTestImplementation libs.testing.leakcanary androidTestImplementation libs.androidx.benchmark.junit4 androidTestImplementation libs.androidx.compose.ui.test androidTestImplementation libs.androidx.work.testing androidTestImplementation libs.testing.mockwebserver testImplementation project(':support-test') testImplementation project(':support-test-libstate') testImplementation libs.androidx.test.junit testImplementation libs.androidx.work.testing testImplementation ComponentsDependencies.mozilla_appservices_full_megazord_libsForTests testImplementation (libs.testing.robolectric) { exclude group: 'org.apache.maven' } testImplementation libs.testing.maven.ant.tasks implementation project(':support-rusthttp') androidTestImplementation libs.testing.mockk.android testImplementation libs.testing.mockk // For the initial release of Glean 19, we require consumer applications to // depend on a separate library for unit tests. This will be removed in future releases. testImplementation "org.mozilla.telemetry:glean-native-forUnitTests:${project.ext.glean_version}" lintChecks project(':tooling-lint') } protobuf { protoc { artifact = libs.protobuf.compiler.get() } // Generates the java Protobuf-lite code for the Protobufs in this project. See // https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation // for more information. generateProtoTasks { all().each { task -> task.builtins { java { option 'lite' } } } } } if (project.hasProperty("coverage")) { tasks.withType(Test).configureEach { jacoco.includeNoLocationClasses = true jacoco.excludes = ['jdk.internal.*'] } jacoco { toolVersion = libs.versions.jacoco.get() } android.applicationVariants.configureEach { variant -> tasks.register("jacoco${variant.name.capitalize()}TestReport", JacocoReport) { dependsOn "test${variant.name.capitalize()}UnitTest" reports { xml.required = true html.required = true } def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*', '**/*$[0-9].*'] def kotlinDebugTree = fileTree(dir: "$project.layout.buildDirectory/tmp/kotlin-classes/${variant.name}", excludes: fileFilter) def javaDebugTree = fileTree(dir: "$project.layout.buildDirectory/intermediates/classes/${variant.flavorName}/${variant.buildType.name}", excludes: fileFilter) def mainSrc = "$project.projectDir/src/main/java" sourceDirectories.setFrom(files([mainSrc])) classDirectories.setFrom(files([kotlinDebugTree, javaDebugTree])) executionData.setFrom(fileTree(dir: project.layout.buildDirectory, includes: [ "jacoco/test${variant.name.capitalize()}UnitTest.exec", 'outputs/code-coverage/connected/*coverage.ec' ])) } } android { buildTypes { debug { testCoverageEnabled true } } } } // ------------------------------------------------------------------------------------------------- // Task for printing APK information for the requested variant // Usage: "./gradlew printVariants // ------------------------------------------------------------------------------------------------- tasks.register('printVariants') { def variants = project.provider { android.applicationVariants.collect { variant -> [ apks: variant.outputs.collect { output -> [ abi: output.getFilter(FilterConfiguration.FilterType.ABI.name()), fileName: output.outputFile.name ]}, build_type: variant.buildType.name, name: variant.name, ]}} doLast { // AndroidTest is a special case not included above variants.get().add([ apks: [[ abi: 'noarch', fileName: 'app-debug-androidTest.apk', ]], build_type: 'androidTest', name: 'androidTest', ]) println 'variants: ' + JsonOutput.toJson(variants.get()) } } afterEvaluate { // Format test output. Ported from AC #2401 tasks.withType(Test).configureEach { systemProperty "robolectric.logging", "stdout" systemProperty "logging.test-mode", "true" testLogging.events = [] beforeSuite { descriptor -> if (descriptor.getClassName() != null) { println("\nSUITE: " + descriptor.getClassName()) } } beforeTest { descriptor -> println(" TEST: " + descriptor.getName()) } onOutput { descriptor, event -> it.logger.lifecycle(" " + event.message.trim()) } afterTest { descriptor, result -> switch (result.getResultType()) { case ResultType.SUCCESS: println(" SUCCESS") break case ResultType.FAILURE: def testId = descriptor.getClassName() + "." + descriptor.getName() println(" TEST-UNEXPECTED-FAIL | " + testId + " | " + result.getException()) break case ResultType.SKIPPED: println(" SKIPPED") break } it.logger.lifecycle("") } } } if (gradle.hasProperty('localProperties.dependencySubstitutions.geckoviewTopsrcdir')) { if (gradle.hasProperty('localProperties.dependencySubstitutions.geckoviewTopobjdir')) { ext.topobjdir = gradle."localProperties.dependencySubstitutions.geckoviewTopobjdir" } ext.topsrcdir = gradle."localProperties.dependencySubstitutions.geckoviewTopsrcdir" apply from: "${topsrcdir}/substitute-local-geckoview.gradle" } android.applicationVariants.configureEach { variant -> tasks.register("apkSize${variant.name.capitalize()}", ApkSizeTask) { variantName = variant.name apks = variant.outputs.collect { output -> output.outputFile.name } dependsOn "package${variant.name.capitalize()}" } } def getSupportedLocales() { // This isn't running as a task, instead the array is build when the gradle file is parsed. // https://github.com/mozilla-mobile/fenix/issues/14175 def foundLocales = new StringBuilder() foundLocales.append("new String[]{") fileTree("src/main/res").visit { FileVisitDetails details -> if (details.file.path.endsWith("${File.separator}strings.xml")) { def languageCode = details.file.parent.tokenize(File.separator).last().replaceAll('values-', '').replaceAll('-r', '-') languageCode = (languageCode == "values") ? "en-US" : languageCode foundLocales.append("\"").append(languageCode).append("\"").append(",") } } foundLocales.append("}") def foundLocalesString = foundLocales.toString().replaceAll(',}', '}') return foundLocalesString }