tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

build.gradle (41718B)


      1 import com.android.build.api.variant.FilterConfiguration
      2 import com.google.android.gms.oss.licenses.plugin.DependencyTask
      3 import groovy.json.JsonOutput
      4 
      5 import java.time.format.DateTimeFormatter
      6 
      7 import static org.gradle.api.tasks.testing.TestResult.ResultType
      8 
      9 buildscript {
     10    repositories {
     11        gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORIES.each { repository ->
     12            maven {
     13                url = repository
     14                if (gradle.mozconfig.substs.ALLOW_INSECURE_GRADLE_REPOSITORIES) {
     15                    allowInsecureProtocol = true
     16                }
     17            }
     18        }
     19    }
     20 
     21    dependencies {
     22        classpath libs.kotlin.serialization
     23    }
     24 }
     25 
     26 plugins {
     27    alias(libs.plugins.kotlin.android)
     28    alias(libs.plugins.kotlin.compose)
     29    alias(libs.plugins.python.envs.plugin)
     30    alias(libs.plugins.protobuf.plugin)
     31    id 'org.mozilla.nimbus-gradle-plugin'
     32 }
     33 
     34 apply plugin: 'com.android.application'
     35 apply plugin: 'kotlin-parcelize'
     36 apply plugin: 'jacoco'
     37 apply plugin: 'androidx.navigation.safeargs.kotlin'
     38 apply plugin: 'kotlinx-serialization'
     39 
     40 if (gradle.mozconfig.substs.MOZILLA_OFFICIAL) {
     41    apply plugin: 'com.google.android.gms.oss-licenses-plugin'
     42 }
     43 
     44 apply from: 'benchmark.gradle'
     45 
     46 ext.getAbi = { output ->
     47    // Assume that if the ABI isn't set, it's an app bundle or universal APK
     48    output.getFilter(FilterConfiguration.FilterType.ABI.name()) ?: "universal"
     49 }
     50 
     51 def isAppBundle = gradle.startParameter.taskNames.any { it.toLowerCase().contains("bundle") }
     52 
     53 // Mimic Python: open(os.path.join(buildconfig.topobjdir, 'buildid.h')).readline().split()[2]
     54 def getBuildId() {
     55    if (System.env.MOZ_BUILD_DATE) {
     56        if (System.env.MOZ_BUILD_DATE.length() == 14) {
     57            return System.env.MOZ_BUILD_DATE
     58        }
     59        logger.warn("Ignoring invalid MOZ_BUILD_DATE: ${System.env.MOZ_BUILD_DATE}")
     60    }
     61    return file("${gradle.mozconfig.topobjdir}/buildid.h").getText('utf-8').split()[2]
     62 }
     63 
     64 def obtainTestBuildType() {
     65    def result = "debug";
     66    if (project.hasProperty("testBuildType")) {
     67        result = project.getProperties().get("testBuildType")
     68    }
     69    result
     70 }
     71 
     72 android {
     73    testBuildType obtainTestBuildType()
     74 
     75    project.maybeConfigForJetpackBenchmark(it)
     76    if (project.hasProperty("testBuildType")) {
     77        // Allowing to configure the test build type via command line flag (./gradlew -PtestBuildType=beta ..)
     78        // in order to run UI tests against other build variants than debug in automation.
     79        testBuildType project.property("testBuildType")
     80    }
     81 
     82    compileSdk { version = release(config.compileSdkMajorVersion) { minorApiLevel = config.compileSdkMinorVersion } }
     83 
     84    defaultConfig {
     85        applicationId "org.torproject"
     86        minSdk config.minSdkVersion
     87        targetSdk config.targetSdkVersion
     88        versionCode 1
     89        versionName Config.generateDebugVersionName()
     90        vectorDrawables.useSupportLibrary = true
     91        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
     92        testInstrumentationRunnerArguments clearPackageData: 'true'
     93        testInstrumentationRunnerArguments "detect-leaks": 'true'
     94        resValue "bool", "IS_DEBUG", "false"
     95        buildConfigField "boolean", "USE_RELEASE_VERSIONING", "false"
     96        // Blank in debug builds so that tests are deterministic.
     97        buildConfigField "String", "VCS_HASH", "\"\""
     98        // This should be the "public" base URL of AMO.
     99        buildConfigField "String", "AMO_BASE_URL", "\"https://addons.mozilla.org\""
    100        buildConfigField "String", "AMO_COLLECTION_NAME", "\"Extensions-for-Android\""
    101        buildConfigField "String", "AMO_COLLECTION_USER", "\"mozilla\""
    102        // This should be the base URL used to call the AMO API.
    103        buildConfigField "String", "AMO_SERVER_URL", "\"https://services.addons.mozilla.org\""
    104 
    105        def deepLinkSchemeValue = "torbrowser-dev"
    106        buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
    107 
    108        manifestPlaceholders = [
    109                "deepLinkScheme": deepLinkSchemeValue
    110        ]
    111 
    112        buildConfigField "String[]", "SUPPORTED_LOCALE_ARRAY", getSupportedLocales()
    113        buildConfigField "boolean", "IS_BENCHMARK_BUILD", "false"
    114 
    115        buildConfigField "boolean", "DATA_COLLECTION_DISABLED", "true"
    116    }
    117 
    118    def releaseTemplate = {
    119        // We allow disabling optimization by passing `-PdisableOptimization` to gradle. This is used
    120        // in automation for UI testing non-debug builds.
    121        shrinkResources = !project.hasProperty("disableOptimization")
    122        minifyEnabled = !project.hasProperty("disableOptimization")
    123        proguardFiles = [getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro']
    124        matchingFallbacks = ['release'] // Use on the "release" build type in dependencies (AARs)
    125 
    126        // Present in release builds.
    127        if (gradle.mozconfig.substs.MOZ_INCLUDE_SOURCE_INFO) {
    128            buildConfigField("String", "VCS_HASH", "\"" + gradle.mozconfig.source_repo.MOZ_SOURCE_STAMP + "\"")
    129        }
    130 
    131 // If building locally, just use debug signing. In official automation builds, the
    132 // files are signed using a separate service and not within gradle.
    133        if (!gradle.mozconfig.substs.MOZ_AUTOMATION) {
    134            signingConfig = signingConfigs.debug
    135        }
    136 
    137        if (gradle.hasProperty("localProperties.debuggable") ||
    138            gradle.mozconfig.substs.MOZ_ANDROID_DEBUGGABLE) {
    139            debuggable true
    140        }
    141    }
    142 
    143    buildTypes {
    144        debug {
    145            shrinkResources = false
    146            minifyEnabled = false
    147            applicationIdSuffix ".torbrowser_debug"
    148            resValue "bool", "IS_DEBUG", "true"
    149            pseudoLocalesEnabled = true
    150        }
    151        nightly releaseTemplate >> {
    152            applicationIdSuffix ".torbrowser_nightly"
    153            buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
    154            def deepLinkSchemeValue = "torbrowser-nightly"
    155            buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
    156            manifestPlaceholders.putAll([
    157                    "deepLinkScheme": deepLinkSchemeValue
    158            ])
    159        }
    160        beta releaseTemplate >> {
    161            buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
    162            applicationIdSuffix ".torbrowser_alpha"
    163            def deepLinkSchemeValue = "torbrowser-alpha"
    164            buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
    165            manifestPlaceholders.putAll([
    166                    // This release type is meant to replace Firefox (Beta channel) and therefore needs to inherit
    167                    // its sharedUserId for all eternity. See:
    168                    // https://searchfox.org/mozilla-esr68/search?q=moz_android_shared_id&case=false&regexp=false&path=
    169                    // Shipping an app update without sharedUserId can have
    170                    // fatal consequences. For example see:
    171                    //  - https://issuetracker.google.com/issues/36924841
    172                    //  - https://issuetracker.google.com/issues/36905922
    173                    "sharedUserId": "org.torproject.torbrowser_alpha.sharedID",
    174                    "deepLinkScheme": deepLinkSchemeValue,
    175            ])
    176        }
    177        release releaseTemplate >> {
    178            buildConfigField "boolean", "USE_RELEASE_VERSIONING", "true"
    179            applicationIdSuffix ".torbrowser"
    180            def deepLinkSchemeValue = "torbrowser"
    181            buildConfigField "String", "DEEP_LINK_SCHEME", "\"$deepLinkSchemeValue\""
    182            manifestPlaceholders.putAll([
    183                    // This release type is meant to replace Firefox (Release channel) and therefore needs to inherit
    184                    // its sharedUserId for all eternity. See:
    185                    // https://searchfox.org/mozilla-esr68/search?q=moz_android_shared_id&case=false&regexp=false&path=
    186                    // Shipping an app update without sharedUserId can have
    187                    // fatal consequences. For example see:
    188                    //  - https://issuetracker.google.com/issues/36924841
    189                    //  - https://issuetracker.google.com/issues/36905922
    190                    "sharedUserId": "org.torproject.torbrowser.sharedID",
    191                    "deepLinkScheme": deepLinkSchemeValue,
    192            ])
    193        }
    194        benchmark releaseTemplate >> {
    195            initWith buildTypes.nightly
    196            shrinkResources = false
    197            minifyEnabled = false
    198            applicationIdSuffix ".fenix"
    199            signingConfig = signingConfigs.debug
    200            debuggable false
    201            buildConfigField "boolean", "IS_BENCHMARK_BUILD", "true"
    202        }
    203    }
    204 
    205    buildFeatures {
    206        viewBinding = true
    207        buildConfig = true
    208    }
    209 
    210    androidResources {
    211        // All JavaScript code used internally by GeckoView is packaged in a
    212        // file called omni.ja. If this file is compressed in the APK,
    213        // GeckoView must uncompress it before it can do anything else which
    214        // causes a significant delay on startup.
    215        noCompress 'ja'
    216 
    217        // manifest.template.json is converted to manifest.json at build time.
    218        // No need to package the template in the APK.
    219        ignoreAssetsPattern = "manifest.template.json"
    220    }
    221 
    222    testOptions {
    223        animationsDisabled = true
    224        execution = 'ANDROIDX_TEST_ORCHESTRATOR'
    225        testCoverage {
    226            jacocoVersion = libs.versions.jacoco.get()
    227        }
    228        unitTests {
    229            all {
    230                // We keep running into memory issues when running our tests. With this config we
    231                // reserve more memory and also create a new process after every 80 test classes. This
    232                // is a band-aid solution and eventually we should try to find and fix the leaks
    233                // instead. :)
    234                forkEvery = 80
    235                maxHeapSize = "3072m"
    236                minHeapSize = "1024m"
    237            }
    238            includeAndroidResources = true
    239            returnDefaultValues = true
    240        }
    241    }
    242 
    243 
    244    sourceSets {
    245        androidTest {
    246            resources.srcDirs += ['src/androidTest/resources']
    247        }
    248        if (project.hasProperty('baselineProfilePath')) {
    249            main {
    250                baselineProfiles.srcDirs(project.property('baselineProfilePath'))
    251            }
    252        }
    253    }
    254 
    255    splits {
    256        abi {
    257            enable = !isAppBundle
    258 
    259            reset()
    260 
    261            // As gradle is unable to pick the right apk to install when multiple apks are generated
    262            // while running benchmark tests or generating baseline profiles. To circumvent this,
    263            // this flag is passed to make sure only one apk is generated so gradle can pick that one.
    264            if (project.hasProperty("benchmarkTest")) {
    265                include "arm64-v8a"
    266            } else {
    267                include "armeabi-v7a", "arm64-v8a", "x86_64"
    268                if (gradle.mozconfig.substs.MOZILLA_OFFICIAL) {
    269                    universalApk true
    270                }
    271            }
    272        }
    273    }
    274 
    275    bundle {
    276        language {
    277            // Because we have runtime language selection we will keep all strings and languages
    278            // in the base APKs.
    279            enableSplit = false
    280        }
    281    }
    282 
    283    // Exclude native libraries for unsupported architectures from third-party dependencies
    284    packagingOptions {
    285        jniLibs {
    286            excludes += ["**/armeabi/*.so", "**/mips/*.so", "**/mips64/*.so", "**/x86/*.so"]
    287        }
    288    }
    289 
    290    lint {
    291        lintConfig = file("lint.xml")
    292        baseline = file("lint-baseline.xml")
    293        sarifReport = true
    294        sarifOutput = file("../build/reports/lint/lint-report.sarif.json")
    295        abortOnError = false
    296    }
    297 
    298    packaging {
    299        if (!isAppBundle) {
    300            jniLibs {
    301                useLegacyPackaging = true
    302            }
    303        }
    304        resources {
    305            excludes += ["META-INF/LICENSE.md", "META-INF/LICENSE-notice.md"]
    306        }
    307    }
    308 
    309    buildFeatures {
    310        compose = true
    311    }
    312 
    313    namespace = 'org.mozilla.fenix'
    314 }
    315 
    316 android.applicationVariants.configureEach { variant ->
    317 
    318 // -------------------------------------------------------------------------------------------------
    319 // Generate version codes for builds
    320 // -------------------------------------------------------------------------------------------------
    321 
    322    def isDebug = variant.buildType.resValues['bool/IS_DEBUG']?.value ?: false
    323    def isDataCollectionDisabled = variant.buildType.buildConfigFields['DATA_COLLECTION_DISABLED']?.value ?: true
    324    def isDebugOrDCD = isDebug || isDataCollectionDisabled
    325    def useReleaseVersioning = variant.buildType.buildConfigFields['USE_RELEASE_VERSIONING']?.value ?: false
    326 
    327    // When this is set, a-s doesn't attempt to download NIMBUS_FML.
    328    if (gradle.mozconfig.substs.NIMBUS_FML) {
    329        System.setProperty("nimbusFml",  gradle.mozconfig.substs.NIMBUS_FML)
    330    }
    331 
    332    def disableTor = false
    333    if (project.hasProperty("disableTor")) {
    334        disableTor = project.getProperty("disableTor")
    335    }
    336 
    337    project.logger.debug("----------------------------------------------")
    338    project.logger.debug("Variant name:      " + variant.name)
    339    project.logger.debug("Application ID:    " + [variant.applicationId, variant.buildType.applicationIdSuffix].findAll().join())
    340    project.logger.debug("Build type:        " + variant.buildType.name)
    341    project.logger.debug("Flavor:            " + variant.flavorName)
    342    project.logger.debug("Telemetry enabled: " + !isDebugOrDCD)
    343    project.logger.debug("nimbusFml:         " + System.getProperty("nimbusFml"))
    344    project.logger.debug("Tor is disabled:   " + disableTor)
    345 
    346    buildConfigField "boolean", "DISABLE_TOR", "$disableTor"
    347 
    348    if (useReleaseVersioning) {
    349        // The Google Play Store does not allow multiple APKs for the same app that all have the
    350        // same version code. Therefore we need to have different version codes for our ARM and x86
    351        // builds.
    352 
    353        def versionName = variant.buildType.name == 'nightly' ?
    354                "${Config.nightlyVersionName(project)}" :
    355                "${Config.releaseVersionName(project)}"
    356        project.logger.debug("versionName override: $versionName")
    357 
    358        variant.outputs.each { output ->
    359            // We use the same version code generator, that we inherited from Fennec, across all channels - even on
    360            // channels that never shipped a Fennec build.
    361            def abi = getAbi(output)
    362            def versionCodeOverride = Config.generateFennecVersionCode(abi)
    363 
    364            project.logger.debug("versionCode for $abi = $versionCodeOverride")
    365 
    366            if (versionName != null) {
    367                output.versionNameOverride = versionName
    368            }
    369            output.versionCodeOverride = versionCodeOverride
    370        }
    371    } else if (gradle.hasProperty("localProperties.branchBuild.fenix.version")) {
    372        def versionName = gradle.getProperty("localProperties.branchBuild.fenix.version")
    373        project.logger.debug("versionName override: $versionName")
    374        variant.outputs.each { output ->
    375            output.versionNameOverride = versionName
    376        }
    377    }
    378 
    379 // -------------------------------------------------------------------------------------------------
    380 // BuildConfig: Set variables for Sentry, Crash Reporting, and Telemetry
    381 // -------------------------------------------------------------------------------------------------
    382 
    383    buildConfigField 'String', 'SENTRY_TOKEN', 'null'
    384    if (!isDebugOrDCD) {
    385        buildConfigField 'boolean', 'CRASH_REPORTING', 'true'
    386        // Reading sentry token from local file (if it exists). In a release task on taskcluster it will be available.
    387        try {
    388            def token = new File("${rootDir}/.sentry_token").text.trim()
    389            buildConfigField 'String', 'SENTRY_TOKEN', '"' + token + '"'
    390        } catch (FileNotFoundException ignored) {}
    391    } else {
    392        buildConfigField 'boolean', 'CRASH_REPORTING', 'false'
    393    }
    394 
    395    if (!isDebugOrDCD) {
    396        buildConfigField 'boolean', 'TELEMETRY', 'true'
    397    } else {
    398        buildConfigField 'boolean', 'TELEMETRY', 'false'
    399    }
    400 
    401    // Setting buildDate with every build ID changes the generated BuildConfig, which slows down the
    402    // build. Only do this for non-debug builds, to speed-up builds produced during local development.
    403    if (isDebug) {
    404        buildConfigField 'String', 'BUILD_DATE', '"debug build"'
    405    } else {
    406        def buildDate = LocalDateTime.parse(getBuildId(), DateTimeFormatter.ofPattern("yyyyMMddHHmmss")).toString()
    407        buildConfigField 'String', 'BUILD_DATE', '"' + buildDate + '"'
    408    }
    409 
    410 // -------------------------------------------------------------------------------------------------
    411 // Adjust: Read token from local file if it exists (Only release builds)
    412 // -------------------------------------------------------------------------------------------------
    413 
    414    project.logger.debug("Adjust token: ")
    415 
    416    if (!isDebug) {
    417        try {
    418            def token = new File("${rootDir}/.adjust_token").text.trim()
    419            buildConfigField 'String', 'ADJUST_TOKEN', '"' + token + '"'
    420            project.logger.debug("(Added from .adjust_token file)")
    421        } catch (FileNotFoundException ignored) {
    422            buildConfigField 'String', 'ADJUST_TOKEN', 'null'
    423            project.logger.debug("X_X")
    424        }
    425    } else {
    426        buildConfigField 'String', 'ADJUST_TOKEN', 'null'
    427        project.logger.debug("--")
    428    }
    429 
    430 // -------------------------------------------------------------------------------------------------
    431 // MLS: Read token from local file if it exists
    432 // -------------------------------------------------------------------------------------------------
    433 
    434    project.logger.debug("MLS token: ")
    435 
    436    try {
    437        def token = new File("${rootDir}/.mls_token").text.trim()
    438        buildConfigField 'String', 'MLS_TOKEN', '"' + token + '"'
    439        project.logger.debug("(Added from .mls_token file)")
    440    } catch (FileNotFoundException ignored) {
    441        buildConfigField 'String', 'MLS_TOKEN', '""'
    442        project.logger.debug("X_X")
    443    }
    444 
    445 // -------------------------------------------------------------------------------------------------
    446 // Nimbus: Read endpoint from local.properties of a local file if it exists
    447 // -------------------------------------------------------------------------------------------------
    448 
    449    project.logger.debug("Nimbus endpoint: ")
    450 
    451    if (!isDebug) {
    452        try {
    453            def url = new File("${rootDir}/.nimbus").text.trim()
    454            buildConfigField 'String', 'NIMBUS_ENDPOINT', '"' + url + '"'
    455            project.logger.debug("(Added from .nimbus file)")
    456        } catch (FileNotFoundException ignored) {
    457            buildConfigField 'String', 'NIMBUS_ENDPOINT', 'null'
    458            project.logger.debug("X_X")
    459        }
    460    } else if (gradle.hasProperty("localProperties.nimbus.remote-settings.url")) {
    461        def url=gradle.getProperty("localProperties.nimbus.remote-settings.url")
    462        buildConfigField 'String', 'NIMBUS_ENDPOINT', '"' + url + '"'
    463        project.logger.debug("(Added from local.properties file)")
    464    } else {
    465        buildConfigField 'String', 'NIMBUS_ENDPOINT', 'null'
    466        project.logger.debug("--")
    467    }
    468 
    469 // -------------------------------------------------------------------------------------------------
    470 // Glean: Read custom server URL from local.properties of a local file if it exists
    471 // -------------------------------------------------------------------------------------------------
    472 
    473    project.logger.debug("Glean custom server URL: ")
    474 
    475    if (gradle.hasProperty("localProperties.glean.custom.server.url")) {
    476        def url=gradle.getProperty("localProperties.glean.custom.server.url")
    477        buildConfigField 'String', 'GLEAN_CUSTOM_URL', url
    478        project.logger.debug("(Added from local.properties file)")
    479    } else {
    480        buildConfigField 'String', 'GLEAN_CUSTOM_URL', 'null'
    481        project.logger.debug("--")
    482    }
    483 
    484 // -------------------------------------------------------------------------------------------------
    485 // BuildConfig: Set flag for official builds; similar to MOZILLA_OFFICIAL in mozilla-central.
    486 // -------------------------------------------------------------------------------------------------
    487 
    488    if (project.hasProperty("official") || gradle.hasProperty("localProperties.official")) {
    489        buildConfigField 'Boolean', 'MOZILLA_OFFICIAL', 'true'
    490    } else {
    491        buildConfigField 'Boolean', 'MOZILLA_OFFICIAL', 'false'
    492    }
    493 
    494 // -------------------------------------------------------------------------------------------------
    495 // BuildConfig: Set remote wallpaper URL using local file if it exists
    496 // -------------------------------------------------------------------------------------------------
    497 
    498    project.logger.debug("Wallpaper URL: ")
    499 
    500    try {
    501        def token = new File("${rootDir}/.wallpaper_url").text.trim()
    502        buildConfigField 'String', 'WALLPAPER_URL', '"' + token + '"'
    503        project.logger.debug("(Added from .wallpaper_url file)")
    504    } catch (FileNotFoundException ignored) {
    505        buildConfigField 'String', 'WALLPAPER_URL', '""'
    506        project.logger.debug("--")
    507    }
    508 
    509 // -------------------------------------------------------------------------------------------------
    510 // BuildConfig: Set the Pocket consumer key from a local file if it exists
    511 // -------------------------------------------------------------------------------------------------
    512 
    513    project.logger.debug("Pocket consumer key: ")
    514 
    515    try {
    516        def token = new File("${rootDir}/.pocket_consumer_key").text.trim()
    517        buildConfigField 'String', 'POCKET_CONSUMER_KEY', '"' + token + '"'
    518        project.logger.debug("(Added from .pocket_consumer_key file)")
    519    } catch (FileNotFoundException ignored) {
    520        buildConfigField 'String', 'POCKET_CONSUMER_KEY', '""'
    521        project.logger.debug("--")
    522    }
    523 
    524 // -------------------------------------------------------------------------------------------------
    525 // BuildConfig: Set flag to disable LeakCanary in debug (on CI builds)
    526 // -------------------------------------------------------------------------------------------------
    527 
    528    if (isDebug) {
    529        if (project.hasProperty("disableLeakCanary") || gradle.hasProperty("localProperties.disableLeakCanary")) {
    530            buildConfigField "boolean", "LEAKCANARY", "false"
    531            project.logger.debug("LeakCanary enabled in debug: false")
    532        } else {
    533            buildConfigField "boolean", "LEAKCANARY", "true"
    534            project.logger.debug("LeakCanary enabled in debug: true")
    535        }
    536    } else {
    537        buildConfigField "boolean", "LEAKCANARY", "false"
    538    }
    539 }
    540 
    541 // Generate Kotlin code for the Fenix Glean metrics.
    542 ext {
    543    // Enable expiration by major version.
    544    gleanExpireByVersion = Config.majorVersion(project)
    545    gleanNamespace = "mozilla.telemetry.glean"
    546    gleanPythonEnvDir = gradle.mozconfig.substs.GRADLE_GLEAN_PARSER_VENV
    547 }
    548 apply plugin: "org.mozilla.telemetry.glean-gradle-plugin"
    549 
    550 nimbus {
    551    // The path to the Nimbus feature manifest file
    552    manifestFile = "nimbus.fml.yaml"
    553    // The fully qualified class name for the generated features.
    554    // Map from the variant name to the channel as experimenter and nimbus understand it.
    555    // If nimbus's channels were accurately set up well for this project, then this
    556    // shouldn't be needed.
    557    channels = [
    558            debug: "developer",
    559            nightly: "nightly",
    560            beta: "beta",
    561            release: "release",
    562            benchmark: "developer",
    563    ]
    564    // This is generated by the FML and should be checked into git.
    565    // It will be fetched by Experimenter (the Nimbus experiment website)
    566    // and used to inform experiment configuration.
    567    experimenterManifest = ".experimenter.yaml"
    568    applicationServicesDir = gradle.hasProperty('localProperties.autoPublish.application-services.dir')
    569            ? gradle.getProperty('localProperties.autoPublish.application-services.dir') : null
    570 }
    571 
    572 dependencies {
    573    implementation project(':components:browser-engine-gecko')
    574    implementation project(':components:compose-awesomebar')
    575    implementation project(':components:compose-base')
    576    implementation project(':components:compose-browser-toolbar')
    577    implementation project(':components:compose-cfr')
    578    implementation project(':components:concept-awesomebar')
    579    implementation project(':components:concept-base')
    580    implementation project(':components:concept-engine')
    581    implementation project(':components:concept-menu')
    582    implementation project(':components:concept-push')
    583    implementation project(':components:concept-storage')
    584    implementation project(':components:concept-sync')
    585    implementation project(':components:concept-tabstray')
    586    implementation project(':components:concept-toolbar')
    587    implementation project(':components:browser-domains')
    588    implementation project(':components:browser-icons')
    589    implementation project(':components:browser-menu')
    590    implementation project(':components:browser-menu2')
    591    implementation project(':components:browser-session-storage')
    592    implementation project(':components:browser-state')
    593    implementation project(':components:browser-storage-sync')
    594    implementation project(':components:browser-tabstray')
    595    implementation project(':components:browser-thumbnails')
    596    implementation project(':components:browser-toolbar')
    597    implementation project(':components:feature-accounts')
    598    implementation project(':components:feature-accounts-push')
    599    implementation project(':components:feature-addons')
    600    implementation project(':components:feature-app-links')
    601    implementation project(':components:feature-autofill')
    602    implementation project(':components:feature-awesomebar')
    603    implementation project(':components:feature-contextmenu')
    604    implementation project(':components:feature-customtabs')
    605    implementation project(':components:feature-downloads')
    606    implementation project(':components:feature-findinpage')
    607    implementation project(':components:feature-fxsuggest')
    608    implementation project(':components:feature-intent')
    609    implementation project(':components:feature-logins')
    610    implementation project(':components:feature-media')
    611    implementation project(':components:feature-privatemode')
    612    implementation project(':components:feature-prompts')
    613    implementation project(':components:feature-push')
    614    implementation project(':components:feature-pwa')
    615    implementation project(':components:feature-qr')
    616    implementation project(':components:feature-readerview')
    617    implementation project(':components:feature-recentlyclosed')
    618    implementation project(':components:feature-search')
    619    implementation project(':components:feature-session')
    620    implementation project(':components:feature-share')
    621    implementation project(':components:feature-sitepermissions')
    622    implementation project(':components:feature-syncedtabs')
    623    implementation project(':components:feature-toolbar')
    624    implementation project(':components:feature-tab-collections')
    625    implementation project(':components:feature-tabs')
    626    implementation project(':components:feature-top-sites')
    627    implementation project(':components:feature-webauthn')
    628    // implementation project(':components:feature-webcompat')
    629    // implementation project(':components:feature-webcompat-reporter')
    630    implementation project(':components:feature-webnotifications')
    631    implementation project(':components:service-digitalassetlinks')
    632    implementation project(':components:service-firefox-accounts')
    633    implementation project(':components:service-glean')
    634    implementation project(':components:service-location')
    635    implementation project(':components:service-mars')
    636    implementation project(':components:service-nimbus')
    637    implementation project(':components:service-pocket')
    638    implementation project(':components:service-sync-autofill')
    639    implementation project(':components:service-sync-logins')
    640    implementation project(':components:support-appservices')
    641    implementation project(':components:support-base')
    642    implementation project(':components:support-images')
    643    implementation project(':components:support-ktx')
    644    implementation project(':components:support-locale')
    645    implementation project(':components:support-remotesettings')
    646    implementation project(':components:support-utils')
    647    implementation project(':components:support-webextensions')
    648    implementation project(':components:lib-publicsuffixlist')
    649    implementation project(':components:ui-icons')
    650    implementation project(':components:ui-colors')
    651    implementation project(':components:ui-tabcounter')
    652    implementation project(':components:ui-widgets')
    653    implementation project(':components:lib-crash')
    654    implementation project(':components:lib-dataprotect')
    655    implementation project(':components:lib-push-firebase')
    656    implementation project(':components:lib-state')
    657 
    658    implementation ComponentsDependencies.mozilla_appservices_syncmanager
    659    implementation libs.accompanist.drawablepainter
    660    implementation libs.androidx.activity
    661    implementation libs.androidx.activity.ktx
    662    implementation libs.androidx.annotation
    663    implementation libs.androidx.appcompat
    664    implementation libs.androidx.biometric
    665    implementation platform(libs.androidx.compose.bom)
    666    implementation libs.androidx.compose.animation
    667    implementation libs.androidx.compose.foundation
    668    implementation libs.androidx.compose.material.icons
    669    implementation libs.androidx.compose.material3
    670    implementation libs.androidx.compose.material3.adaptive
    671    implementation libs.androidx.compose.material3.adaptivelayout
    672    implementation libs.androidx.compose.material3.adaptivenavigation
    673    implementation libs.androidx.compose.material3.adaptivenavigationsuite
    674    implementation libs.androidx.compose.material3.windowsizeclass
    675    implementation libs.androidx.compose.ui
    676    implementation libs.androidx.compose.ui.tooling.preview
    677    implementation libs.androidx.constraintlayout
    678    implementation libs.androidx.coordinatorlayout
    679    implementation libs.androidx.core
    680    implementation libs.androidx.core.ktx
    681    implementation libs.androidx.core.splashscreen
    682    implementation libs.androidx.datastore
    683    implementation libs.androidx.datastore.preferences
    684    implementation libs.androidx.fragment
    685    implementation libs.androidx.fragment.compose
    686    implementation libs.androidx.lifecycle.common
    687    implementation libs.androidx.lifecycle.livedata
    688    implementation libs.androidx.lifecycle.process
    689    implementation libs.androidx.lifecycle.runtime
    690    implementation libs.androidx.lifecycle.service
    691    implementation libs.androidx.lifecycle.viewmodel
    692    implementation libs.androidx.localbroadcastmanager
    693    implementation libs.androidx.navigation.compose
    694    implementation libs.androidx.navigation.fragment
    695    implementation libs.androidx.navigation.ui
    696    implementation libs.androidx.navigation3.ui
    697    implementation libs.androidx.navigation3.runtime
    698    implementation libs.androidx.paging
    699    implementation libs.androidx.preferences
    700    implementation libs.androidx.profileinstaller
    701    implementation libs.androidx.recyclerview
    702    implementation libs.androidx.swiperefreshlayout
    703    implementation libs.androidx.transition
    704    implementation libs.androidx.viewpager2
    705    implementation libs.androidx.work.runtime
    706    implementation libs.google.material
    707    implementation libs.installreferrer
    708    implementation libs.kotlinx.coroutines
    709    implementation libs.kotlinx.serialization.json
    710    implementation libs.mozilla.glean
    711    implementation libs.play.review
    712    implementation libs.play.review.ktx
    713    implementation libs.protobuf.javalite
    714 
    715    debugImplementation libs.androidx.compose.ui.tooling
    716    debugImplementation libs.leakcanary
    717 
    718    testImplementation project(':components:support-test')
    719    testImplementation project(':components:support-test-fakes')
    720    testImplementation project(':components:support-test-libstate')
    721    testImplementation testFixtures(project(':components:feature-downloads'))
    722 
    723    testImplementation ComponentsDependencies.mozilla_appservices_full_megazord_libsForTests
    724 
    725    testImplementation libs.androidx.compose.ui.test
    726    testImplementation libs.androidx.test.core
    727    testImplementation libs.androidx.test.junit
    728    testImplementation libs.androidx.work.testing
    729    testImplementation libs.kotlinx.coroutines.test
    730    testImplementation libs.mockk
    731    testImplementation libs.mozilla.glean.forUnitTests
    732    testImplementation libs.robolectric
    733 
    734    androidTestImplementation libs.androidx.benchmark.junit4
    735    // We need a version constraint due to AGP silently trying to downgrade the version otherwise.
    736    // https://issuetracker.google.com/issues/349628366
    737    constraints {
    738        implementation(libs.androidx.concurrent)
    739    }
    740    androidTestImplementation libs.androidx.concurrent
    741    androidTestImplementation platform(libs.androidx.compose.bom)
    742    androidTestImplementation libs.androidx.compose.ui.test
    743    androidTestImplementation libs.androidx.test.core
    744    androidTestImplementation(libs.androidx.test.espresso.contrib) {
    745        exclude module: 'protobuf-lite'
    746    }
    747    androidTestImplementation libs.androidx.test.espresso.core
    748    androidTestImplementation libs.androidx.test.espresso.idling.resource
    749    androidTestImplementation libs.androidx.test.espresso.intents
    750    androidTestImplementation libs.androidx.test.junit
    751    androidTestImplementation libs.androidx.test.monitor
    752    androidTestImplementation libs.androidx.test.rules
    753    androidTestImplementation libs.androidx.test.runner
    754    androidTestImplementation libs.androidx.test.uiautomator
    755    androidTestImplementation libs.androidx.work.testing
    756    androidTestImplementation libs.leakcanary.instrumentation
    757    androidTestImplementation libs.mockk.android
    758    androidTestImplementation libs.mockwebserver
    759 
    760    androidTestUtil libs.androidx.test.orchestrator
    761 
    762    lintChecks project(':components:tooling-lint')
    763 
    764    // Tor Expert Bundle
    765    implementation files(gradle.mozconfig.substs.TOR_EXPERT_BUNDLE_AAR)
    766 }
    767 
    768 protobuf {
    769    protoc {
    770        artifact = libs.protobuf.compiler.get()
    771    }
    772 
    773    // Generates the java Protobuf-lite code for the Protobufs in this project. See
    774    // https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation
    775    // for more information.
    776    generateProtoTasks {
    777        all().each { task ->
    778            task.builtins {
    779                java {
    780                    option 'lite'
    781                }
    782            }
    783        }
    784    }
    785 }
    786 
    787 if (project.hasProperty("coverage")) {
    788    tasks.withType(Test).configureEach {
    789        jacoco.includeNoLocationClasses = true
    790        jacoco.excludes = ['jdk.internal.*']
    791    }
    792 
    793    android.applicationVariants.configureEach { variant ->
    794        tasks.register("jacoco${variant.name.capitalize()}TestReport", JacocoReport) {
    795            dependsOn "test${variant.name.capitalize()}UnitTest"
    796 
    797            reports {
    798                xml.required = true
    799                html.required = true
    800            }
    801 
    802            def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*',
    803                              '**/*Test*.*', 'android/**/*.*', '**/*$[0-9].*']
    804            def kotlinDebugTree = fileTree(dir: "$project.layout.buildDirectory/tmp/kotlin-classes/${variant.name}", excludes: fileFilter)
    805            def javaDebugTree = fileTree(dir: "$project.layout.buildDirectory/intermediates/classes/${variant.flavorName}/${variant.buildType.name}",
    806                    excludes: fileFilter)
    807            def mainSrc = "$project.projectDir/src/main/java"
    808 
    809            sourceDirectories.setFrom(files([mainSrc]))
    810            classDirectories.setFrom(files([kotlinDebugTree, javaDebugTree]))
    811            executionData.setFrom(fileTree(dir: project.layout.buildDirectory, includes: [
    812                "jacoco/test${variant.name.capitalize()}UnitTest.exec",
    813                'outputs/code-coverage/connected/*coverage.ec'
    814            ]))
    815        }
    816    }
    817 
    818    android {
    819        buildTypes {
    820            debug {
    821                testCoverageEnabled = true
    822            }
    823        }
    824    }
    825 }
    826 
    827 // -------------------------------------------------------------------------------------------------
    828 // Task for printing APK information for the requested variant
    829 // Usage: "./gradlew printVariants
    830 // -------------------------------------------------------------------------------------------------
    831 tasks.register('printVariants') {
    832    def variants = project.provider {
    833        android.applicationVariants.collect { variant -> [
    834            apks: variant.outputs.collect { output -> [
    835                    abi: getAbi(output),
    836                    fileName: output.outputFile.name
    837            ]},
    838            build_type: variant.buildType.name,
    839            name: variant.name,
    840    ]}}
    841    doLast {
    842        // AndroidTest is a special case not included above
    843        variants.get().add([
    844            apks: [[
    845                abi: 'noarch',
    846                fileName: 'app-debug-androidTest.apk',
    847            ]],
    848            build_type: 'androidTest',
    849            name: 'androidTest',
    850        ])
    851        println 'variants: ' + JsonOutput.toJson(variants.get())
    852    }
    853 }
    854 
    855 // Work around generated SafeArgs code hitting USELESS_CAST warnings with Kotlin 2.3+
    856 // https://issuetracker.google.com/issues/458082829
    857 def safeArgsSrc = fileTree(layout.buildDirectory.dir("generated/source/navigation-args")) {
    858    include "**/*.kt"
    859 }
    860 
    861 tasks.register("suppressUselessCastInSafeArgs") {
    862    inputs.files(safeArgsSrc)
    863    outputs.upToDateWhen { false }
    864 
    865    doLast {
    866        safeArgsSrc.each { File f ->
    867            if (!f.text.contains('@file:Suppress("USELESS_CAST")')) {
    868                f.text =
    869 """@file:Suppress("USELESS_CAST")
    870 
    871 ${f.text}"""
    872            }
    873        }
    874    }
    875 }
    876 
    877 tasks.matching { it.name.startsWith("generateSafeArgs") }.configureEach {
    878    finalizedBy tasks.named("suppressUselessCastInSafeArgs")
    879 }
    880 
    881 afterEvaluate {
    882 
    883    // Format test output. Ported from AC #2401
    884    tasks.withType(Test).configureEach {
    885        systemProperty "robolectric.logging", "stdout"
    886        systemProperty "logging.test-mode", "true"
    887 
    888        testLogging.events = []
    889 
    890        beforeSuite { descriptor ->
    891            if (descriptor.getClassName() != null) {
    892                println("\nSUITE: " + descriptor.getClassName())
    893            }
    894        }
    895 
    896        beforeTest { descriptor ->
    897            println("  TEST: " + descriptor.getName())
    898        }
    899 
    900        onOutput { descriptor, event ->
    901            it.logger.lifecycle("    " + event.message.trim())
    902        }
    903 
    904        afterTest { descriptor, result ->
    905            switch (result.getResultType()) {
    906                case ResultType.SUCCESS:
    907                    println("  SUCCESS")
    908                    break
    909 
    910                case ResultType.FAILURE:
    911                    def testId = descriptor.getClassName() + "." + descriptor.getName()
    912                    println("  TEST-UNEXPECTED-FAIL | " + testId + " | " + result.getException())
    913                    break
    914 
    915                case ResultType.SKIPPED:
    916                    println("  SKIPPED")
    917                    break
    918            }
    919            it.logger.lifecycle("")
    920        }
    921    }
    922 }
    923 
    924 if (gradle.hasProperty('localProperties.dependencySubstitutions.geckoviewTopsrcdir')) {
    925    if (gradle.hasProperty('localProperties.dependencySubstitutions.geckoviewTopobjdir')) {
    926        ext.topobjdir = gradle."localProperties.dependencySubstitutions.geckoviewTopobjdir"
    927    }
    928    ext.topsrcdir = gradle."localProperties.dependencySubstitutions.geckoviewTopsrcdir"
    929    apply from: "${topsrcdir}/substitute-local-geckoview.gradle"
    930 }
    931 
    932 android.applicationVariants.configureEach { variant ->
    933    tasks.register("apkSize${variant.name.capitalize()}", ApkSizeTask) {
    934        variantName = variant.name
    935        apks = variant.outputs.collect { output -> output.outputFile.name }
    936        dependsOn "package${variant.name.capitalize()}"
    937    }
    938 }
    939 
    940 def getSupportedLocales() {
    941    // This isn't running as a task, instead the array is build when the gradle file is parsed.
    942    // https://github.com/mozilla-mobile/fenix/issues/14175
    943    def foundLocales = new StringBuilder()
    944    def languageCodes = []
    945    foundLocales.append("new String[]{")
    946 
    947    fileTree("src/main/res").visit { FileVisitDetails details ->
    948        if (details.file.path.endsWith("${File.separator}torbrowser_strings.xml")) {
    949            def languageCode = details.file.parent.tokenize(File.separator).last().replaceAll('values-', '').replaceAll('-r', '-')
    950            languageCode = (languageCode == "values") ? "en-US" : languageCode
    951            languageCodes.add(languageCode)
    952        }
    953    }
    954 
    955    // The order of files in a `FileTree` is not stable, even on a single
    956    // computer. Thus we need to sort the `languageCode`s. See: fenix#40083.
    957    languageCodes.sort()
    958    languageCodes.each {
    959        foundLocales.append("\"").append(it).append("\"").append(",")
    960    }
    961 
    962    foundLocales.append("}")
    963    def foundLocalesString = foundLocales.toString().replaceAll(',}', '}')
    964    return foundLocalesString
    965 }
    966 
    967 // Fix for Gradle 9 strictness: Ensure CleanUp runs before DependencyTask
    968 tasks.withType(DependencyTask).configureEach { task ->
    969    // The OSS plugin creates a "LicensesCleanUp" task for every "DependencyTask"
    970    // e.g., nightlyOssDependencyTask -> nightlyOssLicensesCleanUp
    971    def cleanUpTaskName = task.name.replace("DependencyTask", "LicensesCleanUp")
    972 
    973    // make the dependency task depend on its cleanup task.
    974    task.dependsOn(tasks.named(cleanUpTaskName))
    975 }