import com.android.build.api.variant.FilterConfiguration import java.time.format.DateTimeFormatter import static org.mozilla.conventions.BuildConfigKt.getBuildId plugins { id 'com.android.application' id 'org.mozilla.conventions.zip-test-reports' id 'org.mozilla.nimbus-gradle-plugin' id 'ApkSizePlugin' id 'jacoco' alias(libs.plugins.androidx.safeargs) alias(libs.plugins.dependency.analysis) alias(libs.plugins.kotlin.compose) alias(libs.plugins.kotlin.parcelize) alias(libs.plugins.kotlin.serialization) alias(libs.plugins.ksp) alias(libs.plugins.protobuf.plugin) alias(libs.plugins.python.envs.plugin) } // The OSS-licenses plugin is only needed for shipping builds, so skip it during // `mach lint` (which always passes `-Pandroid-lint`) to avoid unnecessary overhead. if (gradle.mozconfig.substs.MOZILLA_OFFICIAL && !project.hasProperty('android-lint')) { apply plugin: 'com.google.android.gms.oss-licenses-plugin' } apply from: 'benchmark.gradle' def isAppBundle = gradle.startParameter.taskNames.any { it.toLowerCase().contains("bundle") } 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") } compileSdk { version = release(config.compileSdkMajorVersion) { minorApiLevel = config.compileSdkMinorVersion } } defaultConfig { ksp { arg("room.schemaLocation", "$projectDir/schemas".toString()) } applicationId "org.mozilla" minSdk config.minSdkVersion targetSdk 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() buildConfigField "boolean", "IS_BENCHMARK_BUILD", "false" } 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 building locally, just use debug signing. In official automation builds, the // files are signed using a separate service and not within gradle. // Also skip during buildconfig linting, and skip if the `disableDebugSigning` Gradle property is set. if (!project.hasProperty("disableDebugSigning") && !gradle.hasProperty("localProperties.disableDebugSigning") && !gradle.mozconfig.substs.MOZ_AUTOMATION && System.getenv("MOZ_BUILD_CONFIG_LINT") != "1") { 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 shrinkResources = false minifyEnabled = false applicationIdSuffix ".fenix" signingConfig = signingConfigs.debug debuggable false buildConfigField "boolean", "IS_BENCHMARK_BUILD", "true" } } buildFeatures { buildConfig = true compose = true resValues = true viewBinding = 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 { animationsDisabled = true execution = 'ANDROIDX_TEST_ORCHESTRATOR' testCoverage { jacocoVersion = libs.versions.jacoco.get() } 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" } includeAndroidResources = true returnDefaultValues = true } } sourceSets { androidTest { resources.srcDirs += ['src/androidTest/resources'] assets.srcDirs += files("$projectDir/schemas".toString()) } 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 "armeabi-v7a", "arm64-v8a", "x86_64" if (gradle.mozconfig.substs.MOZILLA_OFFICIAL || System.getenv("MOZ_BUILD_CONFIG_LINT") == "1") { universalApk true } } } } bundle { 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 } // Exclude native libraries for unsupported architectures from third-party dependencies packaging { jniLibs { excludes += ["**/armeabi/*.so", "**/mips/*.so", "**/mips64/*.so", "**/x86/*.so"] if (!isAppBundle) { useLegacyPackaging = true } } } namespace = 'org.mozilla.fenix' } // ------------------------------------------------------------------------------------------------- // Generate version codes for builds // ------------------------------------------------------------------------------------------------- androidComponents { onVariants(selector().all()) { variant -> def buildType = variant.buildType project.logger.debug("----------------------------------------------") project.logger.debug("Variant name: " + variant.name) project.logger.debug("Build type: " + buildType) project.logger.debug("Flavor: " + variant.flavorName) // This list must stay in sync with the build types that set USE_RELEASE_VERSIONING=true in // their buildConfigField. The legacy variant API exposed buildConfigFields on the build type // object so we could read the flag directly; onVariants only gives us the build type name, so // we gate on the names instead. if (buildType in ['nightly', 'beta', 'release', 'benchmark']) { // 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 = buildType == 'nightly' ? "${Config.nightlyVersionName(project)}" : "${Config.releaseVersionName(project)}" project.logger.debug("versionName override: $versionName") variant.outputs.each { output -> // 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 abi = output.filters.find { it.filterType == FilterConfiguration.FilterType.ABI }?.identifier ?: "universal" def versionCodeOverride = Config.generateFennecVersionCode(abi) project.logger.debug("versionCode for $abi = $versionCodeOverride") output.versionName.set(versionName) output.versionCode.set(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.versionName.set(versionName) } } } } // ------------------------------------------------------------------------------------------------- // BuildConfig: Sentry, Crash Reporting, Telemetry, and various tokens. // // Non-debug values are set on defaultConfig; the debug build type (the only IS_DEBUG build type) // overrides the ones that differ. These were previously set per-variant via the legacy // applicationVariants API, which AGP 9 removes. // ------------------------------------------------------------------------------------------------- android.defaultConfig.with { buildConfigField 'boolean', 'CRASH_REPORTING', 'true' buildConfigField 'boolean', 'TELEMETRY', 'true' // Reading the sentry token from a local file (if it exists). In a release task on taskcluster it will be available. buildConfigField 'String', 'SENTRY_TOKEN', 'null' try { def token = new File("${rootDir}/.sentry_token").text.trim() buildConfigField 'String', 'SENTRY_TOKEN', '"' + token + '"' } catch (FileNotFoundException ignored) {} // Setting buildDate with every build ID changes the generated BuildConfig and slows down the // build, so debug builds (overridden below) use a fixed string instead. def buildDate = LocalDateTime.parse(getBuildId(gradle.mozconfig.topobjdir, providers.environmentVariable("MOZ_BUILD_DATE").getOrNull()), DateTimeFormatter.ofPattern("yyyyMMddHHmmss")).toString() buildConfigField 'String', 'BUILD_DATE', '"' + buildDate + '"' try { def token = new File("${rootDir}/.adjust_token").text.trim() buildConfigField 'String', 'ADJUST_TOKEN', '"' + token + '"' } catch (FileNotFoundException ignored) { buildConfigField 'String', 'ADJUST_TOKEN', 'null' } try { def url = new File("${rootDir}/.nimbus").text.trim() buildConfigField 'String', 'NIMBUS_ENDPOINT', '"' + url + '"' } catch (FileNotFoundException ignored) { buildConfigField 'String', 'NIMBUS_ENDPOINT', 'null' } buildConfigField 'boolean', 'LEAKCANARY', 'false' // The remaining fields do not depend on the build type. try { def token = new File("${rootDir}/.mls_token").text.trim() buildConfigField 'String', 'MLS_TOKEN', '"' + token + '"' } catch (FileNotFoundException ignored) { buildConfigField 'String', 'MLS_TOKEN', '""' } try { def token = new File("${rootDir}/.gps_integrity_token").text.trim() buildConfigField 'String', 'GPS_INTEGRITY_TOKEN', '"' + token + '"' } catch (FileNotFoundException ignored) { buildConfigField 'String', 'GPS_INTEGRITY_TOKEN', '""' } if (gradle.hasProperty("localProperties.glean.custom.server.url")) { def url = gradle.getProperty("localProperties.glean.custom.server.url") buildConfigField 'String', 'GLEAN_CUSTOM_URL', url } else { buildConfigField 'String', 'GLEAN_CUSTOM_URL', 'null' } if (project.hasProperty("official") || gradle.hasProperty("localProperties.official")) { buildConfigField 'Boolean', 'MOZILLA_OFFICIAL', 'true' } else { buildConfigField 'Boolean', 'MOZILLA_OFFICIAL', 'false' } try { def token = new File("${rootDir}/.wallpaper_url").text.trim() buildConfigField 'String', 'WALLPAPER_URL', '"' + token + '"' } catch (FileNotFoundException ignored) { buildConfigField 'String', 'WALLPAPER_URL', '""' } } android.buildTypes.debug.with { buildConfigField 'boolean', 'CRASH_REPORTING', 'false' buildConfigField 'boolean', 'TELEMETRY', 'false' buildConfigField 'String', 'BUILD_DATE', '"debug build"' buildConfigField 'String', 'SENTRY_TOKEN', 'null' buildConfigField 'String', 'ADJUST_TOKEN', 'null' if (gradle.hasProperty("localProperties.nimbus.remote-settings.url")) { def url = gradle.getProperty("localProperties.nimbus.remote-settings.url") buildConfigField 'String', 'NIMBUS_ENDPOINT', '"' + url + '"' } else { buildConfigField 'String', 'NIMBUS_ENDPOINT', 'null' } if (project.hasProperty("disableLeakCanary") || gradle.hasProperty("localProperties.disableLeakCanary")) { buildConfigField "boolean", "LEAKCANARY", "false" } else { buildConfigField "boolean", "LEAKCANARY", "true" } } // 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" 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 = [ debug: "developer", nightly: "nightly", beta: "beta", release: "release", benchmark: "nightly", ] // 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 } dependencies { implementation project('longfox') implementation project(':components:browser-engine-gecko') implementation project(':components:compose-awesomebar') implementation project(':components:compose-base') implementation project(':components:compose-browser-toolbar') implementation project(':components:compose-cfr') implementation project(':components:concept-awesomebar') implementation project(':components:concept-base') implementation project(':components:concept-engine') implementation project(':components:concept-menu') implementation project(':components:concept-push') implementation project(':components:concept-storage') implementation project(':components:concept-sync') implementation project(':components:concept-tabstray') implementation project(':components:concept-toolbar') implementation project(':components:browser-domains') implementation project(':components:browser-icons') implementation project(':components:browser-menu') implementation project(':components:browser-session-storage') implementation project(':components:browser-state') implementation project(':components:browser-storage-sync') implementation project(':components:browser-tabstray') implementation project(':components:browser-thumbnails') implementation project(':components:browser-toolbar') implementation project(':components:feature-accounts') implementation project(':components:feature-accounts-push') implementation project(':components:feature-addons') implementation project(':components:feature-app-links') implementation project(':components:feature-autofill') implementation project(':components:feature-awesomebar') implementation project(':components:feature-contextmenu') implementation project(':components:feature-customtabs') implementation project(':components:feature-downloads') implementation project(':components:feature-findinpage') implementation project(':components:feature-fxsuggest') implementation project(':components:feature-importer') implementation project(':components:feature-password-importer') implementation project(':components:feature-intent') implementation project(':components:feature-ipprotection') implementation project(':components:feature-logins') implementation project(':components:feature-media') implementation project(':components:feature-privatemode') implementation project(':components:feature-prompts') implementation project(':components:feature-protection-dashboard') implementation project(':components:feature-push') implementation project(':components:feature-pwa') implementation project(':components:feature-qr') implementation project(':components:feature-readerview') implementation project(':components:feature-recentlyclosed') implementation project(':components:feature-search') implementation project(':components:feature-session') implementation project(':components:feature-share') implementation project(':components:feature-sitepermissions') implementation project(':components:feature-summarize') implementation project(':components:feature-syncedtabs') implementation project(':components:feature-toolbar') implementation project(':components:feature-tab-collections') implementation project(':components:feature-tabs') implementation project(':components:feature-top-sites') implementation project(':components:feature-webauthn') implementation project(':components:feature-webcompat') implementation project(':components:feature-webcompat-reporter') implementation project(':components:feature-webnotifications') implementation project(':components:service-digitalassetlinks') implementation project(':components:service-firefox-accounts') implementation project(":components:service-firefox-relay") implementation project(':components:service-glean') implementation project(':components:service-location') implementation project(':components:service-mars') implementation project(':components:service-merino-manifest') implementation project(':components:service-nimbus') implementation project(':components:service-pocket') implementation project(':components:service-sync-autofill') implementation project(':components:service-sync-logins') implementation project(':components:support-appservices') implementation project(':components:support-base') implementation project(':components:support-images') implementation project(':components:support-ktx') implementation project(':components:support-license') implementation project(':components:support-locale') implementation project(':components:support-remotesettings') implementation project(':components:support-utils') implementation project(':components:support-webextensions') implementation project(':components:lib-publicsuffixlist') implementation project(':components:ui-animation') implementation project(':components:ui-icons') implementation project(':components:ui-colors') implementation project(':components:ui-tabcounter') implementation project(':components:ui-widgets') implementation project(':components:lib-bookmarks-file') implementation project(':components:lib-bookmark-parser-jsoup') implementation project(':components:lib-passwords-file') implementation project(':components:lib-crash') implementation project(':components:lib-crash-sentry') implementation project(':components:lib-dataprotect') implementation project(':components:lib-push-firebase') implementation project(':components:lib-state') implementation project(':components:lib-accelerometer-sensormanager') implementation project(':components:lib-integrity-googleplay') implementation project(':components:lib-llm-mlpa') implementation project(':components:lib-shake') implementation project(':components:lib-ai-controls') implementation ComponentsDependencies.mozilla_appservices_syncmanager implementation ComponentsDependencies.mozilla_appservices_merino implementation libs.accompanist.drawablepainter implementation libs.adjust implementation libs.androidx.activity implementation libs.androidx.annotation implementation libs.androidx.appcompat implementation libs.androidx.biometric implementation platform(libs.androidx.compose.bom) implementation libs.androidx.compose.animation implementation libs.androidx.compose.foundation implementation libs.androidx.compose.material.icons implementation libs.androidx.compose.material3 implementation libs.androidx.compose.material3.adaptive implementation libs.androidx.compose.material3.adaptivelayout implementation libs.androidx.compose.material3.adaptivenavigation implementation libs.androidx.compose.material3.adaptivenavigationsuite implementation libs.androidx.compose.material3.windowsizeclass 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.splashscreen implementation libs.androidx.datastore implementation libs.androidx.datastore.preferences implementation libs.androidx.emoji2 implementation libs.androidx.exifinterface implementation libs.androidx.fragment implementation libs.androidx.fragment.compose 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.navigation.compose implementation libs.androidx.navigation.fragment implementation libs.androidx.navigation.ui implementation libs.androidx.navigation3.runtime implementation libs.androidx.navigation3.ui implementation libs.androidx.paging implementation libs.androidx.preference 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.google.material implementation libs.installreferrer implementation libs.kotlinx.coroutines implementation libs.kotlinx.serialization.json implementation libs.mozilla.glean implementation libs.play.review implementation libs.play.review.ktx implementation libs.play.services.ads.id implementation libs.protobuf.javalite implementation libs.sentry implementation libs.androidx.documentfile implementation libs.zxing implementation libs.androidx.room.runtime ksp libs.androidx.room.compiler debugImplementation libs.androidx.compose.ui.tooling debugImplementation libs.leakcanary // Exclude Mockito to prevent classpath conflicts with MockK in tests. testImplementation (project(':components:support-test')) { exclude group: 'org.mockito' } testImplementation project(':components:support-test-fakes') testImplementation project(':components:support-test-libstate') testImplementation testFixtures(project(':components:support-utils')) testImplementation ComponentsDependencies.mozilla_appservices_full_megazord_libsForTests testImplementation libs.androidx.compose.ui.test testImplementation libs.androidx.test.core testImplementation libs.androidx.test.junit testImplementation libs.androidx.test.runner testImplementation libs.androidx.work.testing testImplementation libs.kotlin.test testImplementation libs.kotlinx.coroutines.test testImplementation libs.mockk testImplementation libs.mozilla.glean.forUnitTests testImplementation libs.robolectric androidTestImplementation project(':components:support-android-test') androidTestImplementation libs.androidx.benchmark.junit4 // We need a version constraint due to AGP silently trying to downgrade the version otherwise. // https://issuetracker.google.com/issues/349628366 constraints { implementation(libs.androidx.concurrent) } androidTestImplementation libs.androidx.concurrent androidTestImplementation platform(libs.androidx.compose.bom) androidTestImplementation libs.androidx.compose.ui.test androidTestImplementation libs.androidx.test.core androidTestImplementation(libs.androidx.test.espresso.contrib) { exclude module: 'protobuf-lite' } androidTestImplementation libs.androidx.test.espresso.core androidTestImplementation libs.androidx.test.espresso.idling.resource androidTestImplementation libs.androidx.test.espresso.intents 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 androidTestImplementation libs.androidx.work.testing androidTestImplementation libs.kotlin.test androidTestImplementation libs.androidx.room.testing androidTestImplementation libs.leakcanary.instrumentation androidTestImplementation libs.mockk.android androidTestImplementation libs.mockwebserver androidTestImplementation libs.okhttp androidTestRuntimeOnly libs.okio constraints { // Various AndroidX dependencies pull in 1.1.1 transitively; OkHttp 5 requires 1.2.0. implementation(libs.androidx.startup.runtime) // okhttp is test-only; prevents older releases being resolved on the main classpath. implementation(libs.okio) // espresso-contrib pulls hamcrest 2.2 only at runtime; align the test compile classpath to match. androidTestImplementation(libs.hamcrest.core) androidTestImplementation(libs.hamcrest.library) } androidTestUtil libs.androidx.test.orchestrator lintChecks project(':components: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.*'] } androidComponents { onVariants(selector().all()) { 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}", 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 } } } } // Work around generated SafeArgs code hitting UNSAFE_CAST_RELYING_ON_NULL warnings with // Kotlin 2.4+ (https://issuetracker.google.com/issues/458082829). Suppress as // generateSafeArgs' own final action, not a separate finalizedBy task: finalizedBy isn't // ordered before the Kotlin compile that consumes the output, so it races and fails under // -Werror. tasks.matching { it.name.startsWith("generateSafeArgs") }.configureEach { doLast { outputs.files.asFileTree.matching { include "**/*.kt" }.each { File f -> if (!f.text.contains('@file:Suppress("UNSAFE_CAST_RELYING_ON_NULL")')) { f.text = """@file:Suppress("UNSAFE_CAST_RELYING_ON_NULL") ${f.text}""" } } } } 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 } mozilla { ktlintSourcePaths = ["src/**/*.kt", "!**/build/**/*.kt"] } tasks.register("detekt", Exec) { group = "verification" description = "Run detekt static analysis." workingDir file("${gradle.mozconfig.topsrcdir}") commandLine "./mach", "gradle", "-p", "mobile/android/fenix", ":detekt" }