tor-browser

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

build.gradle (20018B)


      1 import org.gradle.api.services.BuildServiceParameters
      2 import org.tomlj.Toml
      3 import org.tomlj.TomlParseResult
      4 import org.tomlj.TomlTable
      5 
      6 buildscript {
      7    repositories {
      8        mavenLocal()
      9 
     10        gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORIES.each { repository ->
     11            maven {
     12                url = repository
     13                if (gradle.mozconfig.substs.ALLOW_INSECURE_GRADLE_REPOSITORIES) {
     14                    allowInsecureProtocol = true
     15                }
     16            }
     17        }
     18    }
     19 
     20    dependencies {
     21        classpath libs.android.gradle.plugin
     22        classpath libs.tomlj
     23 
     24        // Used in mobile/android/fenix/app/build.gradle
     25        classpath libs.androidx.benchmark.gradle
     26        classpath libs.androidx.navigation.safeargs
     27        classpath libs.osslicenses.plugin
     28        classpath libs.mozilla.glean.gradle.plugin
     29    }
     30 }
     31 
     32 plugins {
     33    id 'ApkSizePlugin'
     34    id "mozac.ConfigPlugin"
     35    alias(libs.plugins.detekt)
     36    alias(libs.plugins.kotlin.android) apply false
     37    alias(libs.plugins.kotlin.compose) apply false
     38    alias(libs.plugins.ksp)
     39    alias(libs.plugins.spotless)
     40 }
     41 
     42 def tryInt = { string ->
     43    if (string == null) {
     44        return string
     45    }
     46    if (string.isInteger()) {
     47        return string as Integer
     48    }
     49    return string
     50 }
     51 
     52 abstract class VerifyGleanVersionTask extends DefaultTask {
     53    @InputFile
     54    final RegularFileProperty source = project.objects.fileProperty().convention(project.layout.projectDirectory.file("Cargo.lock"))
     55 
     56    @Input
     57    String expectedVersion = project.ext.gleanVersion
     58 
     59    @OutputFile
     60    final RegularFileProperty outputFile = project.objects.fileProperty()
     61 
     62    @TaskAction
     63    void verifyGleanVersion() {
     64        def foundVersion = getRustVersionFor(source.get().asFile, "glean")
     65        if (expectedVersion != foundVersion) {
     66          throw new GradleException("Mismatched Glean version, expected: '${expectedVersion}'," +
     67              " found '${foundVersion}'")
     68        } else {
     69            logger.lifecycle("verifyGleanVersion> expected version matches found version '${foundVersion}'")
     70        }
     71 
     72        outputFile.get().asFile.text = "glean-${foundVersion}"
     73    }
     74 
     75    // Parses the Cargo.lock and returns the version for the given package name.
     76    static String getRustVersionFor(file, packageName) {
     77        String version = null;
     78        TomlParseResult result = Toml.parse(file.getText());
     79        for (object in result.getArray("package").toList()) {
     80            def table = (TomlTable) object
     81            if (table.getString("name") == packageName) {
     82                if (version != null) {
     83                    throw new GradleException("Multiple versions for '${packageName}' found." +
     84                                                     " Ensure '${packageName}' is only included once.")
     85                }
     86                version = table.getString("version")
     87            }
     88        }
     89        return version
     90    }
     91 }
     92 
     93 tasks.register("verifyGleanVersion", VerifyGleanVersionTask) {
     94    outputFile.convention(layout.buildDirectory.file("glean/verifyGleanVersion.marker"))
     95 }
     96 
     97 allprojects {
     98    // Expose the per-object-directory configuration to all projects.
     99    ext {
    100        mozconfig = gradle.mozconfig
    101        topsrcdir = gradle.mozconfig.topsrcdir
    102        topobjdir = gradle.mozconfig.topobjdir
    103 
    104        gleanVersion = libs.versions.glean.get() // Verification done in verifyGleanVersion task
    105 
    106        artifactSuffix = getArtifactSuffix()
    107        versionName = getVersionName()
    108        versionCode = computeVersionCode()
    109        versionNumber = getVersionNumber()
    110        buildId = getBuildId()
    111 
    112        buildToolsVersion = mozconfig.substs.ANDROID_BUILD_TOOLS_VERSION
    113        compileSdkMajorVersion = tryInt(mozconfig.substs.ANDROID_COMPILE_SDK_MAJOR)
    114        compileSdkMinorVersion = tryInt(mozconfig.substs.ANDROID_COMPILE_SDK_MINOR)
    115        minSdkVersion = tryInt(mozconfig.substs.MOZ_ANDROID_MIN_SDK_VERSION)
    116        targetSdkVersion = tryInt(mozconfig.substs.ANDROID_TARGET_SDK)
    117        manifestPlaceholders = [
    118            ANDROID_TARGET_SDK: mozconfig.substs.ANDROID_TARGET_SDK,
    119            MOZ_ANDROID_MIN_SDK_VERSION: mozconfig.substs.MOZ_ANDROID_MIN_SDK_VERSION,
    120        ]
    121    }
    122 
    123    afterEvaluate {
    124        if (it.hasProperty('android')) {
    125            android {
    126                buildToolsVersion gradle.mozconfig.substs.ANDROID_BUILD_TOOLS_VERSION
    127                testOptions {
    128                    unitTests.includeAndroidResources = true
    129                }
    130            }
    131        }
    132    }
    133 
    134    repositories {
    135        mavenLocal()
    136 
    137        gradle.mozconfig.substs.GRADLE_MAVEN_REPOSITORIES.each { repository ->
    138            maven {
    139                url = repository
    140                if (gradle.mozconfig.substs.ALLOW_INSECURE_GRADLE_REPOSITORIES) {
    141                    allowInsecureProtocol = true
    142                }
    143            }
    144        }
    145    }
    146 
    147    // Use the semanticdb-javac and semanticdb-kotlinc plugins to generate semanticdb files for Searchfox
    148    if (mozconfig.substs.ENABLE_MOZSEARCH_PLUGIN || mozconfig.substs.DOWNLOAD_ALL_GRADLE_DEPENDENCIES) {
    149        def targetRoot = new File(topobjdir, "mozsearch_java_index")
    150 
    151        afterEvaluate {
    152            def addDependencyToConfigurationIfExists = { configurationName, dependency ->
    153                def configuration = configurations.findByName(configurationName)
    154                if (configuration != null) {
    155                    dependencies.add(configurationName, dependency)
    156                }
    157            }
    158 
    159            addDependencyToConfigurationIfExists("compileOnly", libs.semanticdb.java)
    160            addDependencyToConfigurationIfExists("testCompileOnly", libs.semanticdb.java)
    161            addDependencyToConfigurationIfExists("androidTestCompileOnly", libs.semanticdb.java)
    162            addDependencyToConfigurationIfExists("kotlinCompilerPluginClasspath", libs.semanticdb.kotlin)
    163        }
    164 
    165        tasks.withType(JavaCompile) {
    166            options.compilerArgs += [
    167                "-Xplugin:semanticdb -sourceroot:${topsrcdir} -targetroot:${targetRoot}",
    168            ]
    169        }
    170 
    171        tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) {
    172            compilerOptions.freeCompilerArgs.addAll([
    173                "-P", "plugin:semanticdb-kotlinc:sourceroot=${topsrcdir}".toString(),
    174                "-P", "plugin:semanticdb-kotlinc:targetroot=${targetRoot}".toString(),
    175            ])
    176        }
    177    }
    178 }
    179 
    180 abstract class MozconfigService implements BuildService<MozconfigService.Params>, AutoCloseable {
    181    interface Params extends BuildServiceParameters {
    182        MapProperty<String, Object> getMozconfigParam()
    183    }
    184 
    185    void close() {
    186    }
    187 
    188    Object getMozconfig() {
    189        return parameters.mozconfigParam.get()
    190    }
    191 }
    192 
    193 // Non-official versions are like "61.0a1" or "61.0b1", where "a1" and "b1"
    194 // are the milestone.
    195 // This simply strips that off, leaving "61.0" in this example.
    196 def getAppVersionWithoutMilestone() {
    197    return project.ext.mozconfig.substs.MOZ_APP_VERSION.replaceFirst(/[ab][0-9]/, "")
    198 }
    199 
    200 // This converts MOZ_APP_VERSION into an integer
    201 // version code.
    202 //
    203 // We take something like 58.1.2a1 and come out with 5800102
    204 // This gives us 3 digits for the major number, and 2 digits
    205 // each for the minor and build number. Beta and Release
    206 //
    207 // This must be synchronized with _compute_gecko_version(...) in /taskcluster/gecko_taskgraph/transforms/task.py
    208 def computeVersionCode() {
    209    String appVersion = getAppVersionWithoutMilestone()
    210 
    211    // Split on the dot delimiter, e.g. 58.1.1a1 -> ["58, "1", "1a1"]
    212    String[] parts = appVersion.split('\\.')
    213 
    214    assert parts.size() == 2 || parts.size() == 3
    215 
    216    // Major
    217    int code = Integer.parseInt(parts[0]) * 100000
    218 
    219    // Minor
    220    code += Integer.parseInt(parts[1]) * 100
    221 
    222    // Build
    223    if (parts.size() == 3) {
    224        code += Integer.parseInt(parts[2])
    225    }
    226 
    227    return code;
    228 }
    229 
    230 def getVersionName() {
    231    return "${mozconfig.substs.MOZ_APP_VERSION}-${mozconfig.substs.MOZ_UPDATE_CHANNEL}"
    232 }
    233 
    234 // Mimic Python: open(os.path.join(buildconfig.topobjdir, 'buildid.h')).readline().split()[2]
    235 def getBuildId() {
    236    return file("${topobjdir}/buildid.h").getText('utf-8').split()[2]
    237 }
    238 
    239 def getVersionNumber() {
    240    def appVersion = getAppVersionWithoutMilestone()
    241    def parts = appVersion.split('\\.')
    242    def version = parts[0] + "." + parts[1] + "." + getBuildId()
    243    def substs = project.ext.mozconfig.substs
    244    if (!substs.MOZILLA_OFFICIAL && !substs.MOZ_ANDROID_FAT_AAR_ARCHITECTURES) {
    245        // Use -SNAPSHOT versions locally to enable the local GeckoView substitution flow.
    246        version += "-SNAPSHOT"
    247    }
    248    return version
    249 }
    250 
    251 def getArtifactSuffix() {
    252    def substs = project.ext.mozconfig.substs
    253 
    254    def suffix = ""
    255    // Release artifacts don't specify the channel, for the sake of simplicity.
    256    if (substs.MOZ_UPDATE_CHANNEL != 'release') {
    257        suffix += "-${mozconfig.substs.MOZ_UPDATE_CHANNEL}"
    258    }
    259 
    260    return suffix
    261 }
    262 
    263 abstract class MachExec extends Exec {
    264    def MachExec() {
    265        // Bug 1543982: When invoking `mach build` recursively, the outer `mach
    266        // build` itself modifies the environment, causing configure to run
    267        // again.  This tries to restore the environment that the outer `mach
    268        // build` was invoked in.  See the comment in
    269        // $topsrcdir/settings.gradle.
    270        project.ext.mozconfig.mozconfig.env.unmodified.each { k, v -> environment.remove(k) }
    271        environment project.ext.mozconfig.orig_mozconfig.env.unmodified
    272        environment 'MOZCONFIG', project.ext.mozconfig.substs.MOZCONFIG
    273    }
    274 
    275    static def geckoBinariesOnlyIf(task, mozconfig) {
    276        // Never when Gradle was invoked within `mach build`.
    277        if ('1' == System.env.GRADLE_INVOKED_WITHIN_MACH_BUILD) {
    278            task.logger.lifecycle("Skipping task ${task.path} because: within `mach build`")
    279            return false
    280        }
    281 
    282        // Never for official builds.
    283        if (mozconfig.substs.MOZILLA_OFFICIAL) {
    284            task.logger.lifecycle("Skipping task ${task.path} because: MOZILLA_OFFICIAL")
    285            return false
    286        }
    287        if (mozconfig.substs.ENABLE_MOZSEARCH_PLUGIN) {
    288            task.logger.lifecycle("Skipping task ${task.path} because: ENABLE_MOZSEARCH_PLUGIN")
    289            return false
    290        }
    291 
    292        // Multi-l10n builds set `AB_CD=multi`, which isn't a valid locale, and
    293        // `MOZ_CHROME_MULTILOCALE`.  To avoid failures, if Gradle is invoked with
    294        // either, we don't invoke Make at all; this allows a multi-locale omnijar
    295        // to be consumed without modification.
    296        if ('multi' == System.env.AB_CD || System.env.MOZ_CHROME_MULTILOCALE) {
    297            task.logger.lifecycle("Skipping task ${task.path} because: AB_CD=multi")
    298            return false
    299        }
    300 
    301        // Single-locale l10n repacks set `IS_LANGUAGE_REPACK=1` and handle resource
    302        // and code generation themselves.
    303        if ('1' == System.env.IS_LANGUAGE_REPACK) {
    304            task.logger.lifecycle("Skipping task ${task.path} because: IS_LANGUAGE_REPACK")
    305            return false
    306        }
    307 
    308        task.logger.lifecycle("Executing task ${task.path}")
    309        return true
    310    }
    311 }
    312 
    313 task machBuildFaster(type: MachExec) {
    314    def mozconfigProvider = gradle.sharedServices.registerIfAbsent("mozconfig", MozconfigService) {
    315        parameters.mozconfigParam.set(project.ext.mozconfig)
    316    }
    317    usesService(mozconfigProvider)
    318    onlyIf { task -> MachExec.geckoBinariesOnlyIf(task, mozconfigProvider.get().getMozconfig()) }
    319 
    320    workingDir "${topsrcdir}"
    321 
    322    commandLine mozconfig.substs.PYTHON3
    323    args "${topsrcdir}/mach"
    324    args 'build'
    325    args 'faster'
    326 
    327    // Add `-v` if we're running under `--info` (or `--debug`).
    328    if (project.logger.isEnabled(LogLevel.INFO)) {
    329        args '-v'
    330    }
    331 
    332    standardOutput = System.out
    333    errorOutput = System.err
    334 }
    335 
    336 task machStagePackage(type: MachExec) {
    337    def mozconfigProvider = gradle.sharedServices.registerIfAbsent("mozconfig", MozconfigService) {
    338        parameters.mozconfigParam.set(project.ext.mozconfig)
    339    }
    340    usesService(mozconfigProvider)
    341    onlyIf { task -> MachExec.geckoBinariesOnlyIf(task, mozconfigProvider.get().getMozconfig()) }
    342    dependsOn rootProject.machBuildFaster
    343 
    344    workingDir "${topobjdir}"
    345 
    346    commandLine mozconfig.substs.PYTHON3
    347    args "${topsrcdir}/mach"
    348    args 'build'
    349    args 'stage-package'
    350 
    351    outputs.file "${topobjdir}/dist/geckoview/assets/omni.ja"
    352 
    353    outputs.file "${topobjdir}/dist/geckoview/assets/${mozconfig.substs.ANDROID_CPU_ARCH}/libxul.so"
    354    outputs.file "${topobjdir}/dist/geckoview/lib/${mozconfig.substs.ANDROID_CPU_ARCH}/libmozglue.so"
    355 
    356    // Force running `stage-package`.
    357    outputs.upToDateWhen { false }
    358 
    359    standardOutput = System.out
    360    errorOutput = System.err
    361 }
    362 
    363 afterEvaluate {
    364    subprojects { project ->
    365        tasks.withType(JavaCompile) {
    366            // Add compiler args for all code except third-party code.
    367            options.compilerArgs += [
    368                // Turn on all warnings, except...
    369                "-Xlint:all",
    370                // Deprecation, because we do use deprecated API for compatibility.
    371                "-Xlint:-deprecation",
    372                // Serial, because we don't use Java serialization.
    373                "-Xlint:-serial",
    374                // Classfile, because javac has a bug with MethodParameters attributes
    375                // with Java 7. https://bugs.openjdk.java.net/browse/JDK-8190452
    376                "-Xlint:-classfile"]
    377 
    378            // In GeckoView java projects only, turn all remaining warnings
    379            // into errors unless marked by @SuppressWarnings.
    380            def projectName = project.getName()
    381            if (projectName.startsWith('geckoview')
    382                || projectName == 'annotations'
    383                || projectName == 'exoplayer2'
    384                || projectName == 'messaging_example'
    385                || projectName == 'port_messaging_example'
    386                || projectName == 'test_runner'
    387            ) {
    388                options.compilerArgs += [
    389                        "-Werror"
    390                ]
    391            }
    392        }
    393 
    394        project.configurations.configureEach {
    395            // Dependencies can't depend on a different major version of Glean than A-C itself.
    396            resolutionStrategy.eachDependency { details ->
    397                if (details.requested.group == 'org.mozilla.telemetry'
    398                        && details.requested.name.contains('glean') ) {
    399                    def requested = details.requested.version.tokenize(".")
    400                        def defined = project.ext.gleanVersion.tokenize(".")
    401                        // Check the major version
    402                        if (requested[0] != defined[0]) {
    403                            throw new AssertionError("Cannot resolve to a single Glean version. Requested: ${details.requested.version}, A-C uses: ${libs.mozilla.glean}")
    404                        } else {
    405                            // Enforce that all (transitive) dependencies are using the defined Glean version
    406                            details.useVersion project.ext.gleanVersion
    407                        }
    408                }
    409            }
    410 
    411            resolutionStrategy.capabilitiesResolution.withCapability("org.mozilla.telemetry:glean-native") {
    412                def toBeSelected = candidates.find { it.id instanceof ModuleComponentIdentifier && it.id.module.contains('geckoview') }
    413                if (toBeSelected != null) {
    414                    select(toBeSelected)
    415                }
    416                because 'use GeckoView Glean instead of standalone Glean'
    417            }
    418        }
    419    }
    420 }
    421 
    422 apply plugin: 'idea'
    423 
    424 idea {
    425    project {
    426        languageLevel = '1.8'
    427    }
    428 
    429    module {
    430        // Object directories take a huge amount of time for IntelliJ to index.
    431        // Exclude them.  Convention is that object directories start with obj.
    432        // IntelliJ is clever and will not exclude the parts of the object
    433        // directory that are referenced, if there are any.  In practice,
    434        // indexing the entirety of the tree is taking too long, so exclude all
    435        // but mobile/.
    436        def topsrcdirURI = file(topsrcdir).toURI()
    437        excludeDirs += files(file(topsrcdir)
    438            .listFiles({it.isDirectory()} as FileFilter)
    439            .collect({topsrcdirURI.relativize(it.toURI()).toString()}) // Relative paths.
    440            .findAll({!it.equals('mobile/')}))
    441 
    442        // If topobjdir is below topsrcdir, hide only some portions of that tree.
    443        def topobjdirURI = file(topobjdir).toURI()
    444        if (!topsrcdirURI.relativize(topobjdirURI).isAbsolute()) {
    445            excludeDirs -= file(topobjdir)
    446            excludeDirs += files(file(topobjdir).listFiles())
    447            excludeDirs -= file("${topobjdir}/gradle")
    448        }
    449    }
    450 }
    451 
    452 subprojects { project ->
    453    // Perform spotless lint in GeckoView projects only.
    454    // Sync with spotless_projects in mobile/android/geckoview/gradle.configure.
    455    def projectName = project.getName()
    456    if (projectName.startsWith('geckoview')
    457            || projectName == 'annotations'
    458            || projectName == 'exoplayer2'
    459            || projectName == 'messaging_example'
    460            || projectName == 'port_messaging_example'
    461            || projectName == 'test_runner'
    462    ) {
    463        apply plugin: "com.diffplug.spotless"
    464 
    465        spotless {
    466            lineEndings = com.diffplug.spotless.LineEnding.UNIX
    467            java {
    468                target project.fileTree(project.projectDir) {
    469                    include '**/*.java'
    470                    exclude '**/thirdparty/**'
    471                }
    472                googleJavaFormat(libs.versions.google.java.format.get())
    473            }
    474            kotlin {
    475                target project.fileTree(project.projectDir) {
    476                    include '**/*.kt'
    477                    exclude '**/thirdparty/**'
    478                }
    479                ktlint("${libs.versions.ktlint.get()}").setEditorConfigPath("${topsrcdir}/mobile/android/geckoview/.editorconfig")
    480            }
    481        }
    482 
    483        // Work around https://github.com/diffplug/spotless/issues/1958 by
    484        // explicitly depending on Spotless plugin dependencies.
    485        project.configurations {
    486            googleJavaFormat
    487        }
    488 
    489        project.dependencies {
    490            googleJavaFormat libs.google.java.format
    491        }
    492    }
    493 
    494    afterEvaluate {
    495        // Our vendored copy of exoplayer2 hits build failures when targeting Java 17.
    496        // Given our intent to remove it in the near future, just leave it alone here.
    497        if (it.hasProperty('android') && projectName != 'exoplayer2') {
    498            kotlin {
    499                jvmToolchain(config.jvmTargetCompatibility)
    500            }
    501        }
    502 
    503        if (it.hasProperty('android')
    504                && (project.projectDir.absolutePath.contains("android-components")
    505                || projectName == "fenix"
    506                || projectName == "focus-android")
    507        ) {
    508            dependencies {
    509                lintChecks project.project(':components:tooling-lint')
    510            }
    511 
    512            android {
    513                // Copied from subbproject's build.gradle
    514                lint {
    515                    baseline = file("${project.projectDir}/lint-baseline.xml")
    516                }
    517            }
    518        }
    519    }
    520 
    521    project.configurations.configureEach {
    522        resolutionStrategy.capabilitiesResolution.withCapability("org.mozilla.telemetry:glean-native") {
    523            def toBeSelected = candidates.find {
    524                it.id instanceof ProjectComponentIdentifier && it.id.projectName.contains('geckoview')
    525            }
    526            if (toBeSelected != null) {
    527                select(toBeSelected)
    528            }
    529            because 'use GeckoView Glean instead of standalone Glean'
    530        }
    531    }
    532 }
    533 
    534 tasks.register("lint-a-c") {
    535    subprojects.each{
    536        if (it.tasks.findByName("lint") != null && it.projectDir.absolutePath.contains("android-components")) {
    537            dependsOn it.tasks.named("lint")
    538        }
    539    }
    540 }