substitute-local-geckoview.gradle
author Mozilla Releng Treescript <release+treescript@mozilla.org>
Fri, 12 Aug 2022 06:57:48 +0000
changeset 626816 154884e84ec90429d9da51ffd3d15783c3c463fa
parent 606290 113560f6f02ba74a5b2975ca21135948030265f0
permissions -rw-r--r--
no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD be -> afc894cc8a2bfa5952b7eaea75db922784c472bf es-CL -> f8bcb10180e0156d9a5151466bc8dd1d51fa2cc9 gd -> 95f8f41fb34a130587832c1938bd5f21280d5dee ko -> 3c1b9e49e104efc8a1de7e939295cf9c5472c579 nb-NO -> 81ef8f4f998ea1b62d216662cc8ec9f4c7003ec5 pt-BR -> c8a82c754c0a21709c4c84e1d62aa730310d9925 pt-PT -> a5d6a5d3aa769d9482bcc92a6b5db9781f224698 zh-TW -> 394ea8e6784beab76da85936d986533bb31614d2

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Substitute a local GeckoView AAR into a consuming Gradle project.
//
// To use this, in a consuming Gradle project's `/build.gradle` add a stanza like:
//
// ext.topsrcdir = '/absolute/path/to/mozilla-central'; apply from: "${ext.topsrcdir}/substitute-local-geckoview.gradle"
//
// The object directory will be determined using `mach environment` and will agree with `./mach
// gradle` and Android Studio.  Or, specify the exact object directory with a stanza like:
//
// ext.topsrcdir = '/absolute/path/to/mozilla-central'
// ext.topobjdir = '/absolute/path/to/objdir'
// apply from: "${ext.topsrcdir}/substitute-local-geckoview.gradle"
//
// Substitution works with artifact and non-artifact builds.
//
// If you get errors about .jar files not being found, ensure that the consuming
// application is using a recent Android-Gradle plugin (say, 3.4+).  There were
// issues with Jetifier, and issues with .jar vs. .aar extensions, in older
// versions.

import groovy.json.JsonSlurper

def log(message) {
    logger.lifecycle("[substitute-local-geckoview] ${message}")
}

def warn(message) {
    logger.warn("[substitute-local-geckoview] Warning: ${message}")
}

if (!project.ext.has('topsrcdir')) {
    throw new GradleException("ext.topsrcdir must be specified to substitute for a local GeckoView")
}

/**
 * Loads the mozconfig and returns any variables derived from it, avoiding side effects.
 *
 * This method is relatively slow because it calls mach, which starts a python interpreter, will
 * becomes very slow if called for numerous subprojects. Therefore, it should only be called once
 * per build.
 */
def loadMozconfig() {
    apply from: "${topsrcdir}/mobile/android/gradle/mach_env.gradle"

    // Cribbed from https://hg.mozilla.org/mozilla-central/file/tip/settings.gradle.  When run in
    // topobjdir, `mach environment` correctly finds the mozconfig corresponding to that object
    // directory.
    def commandLine = ["${topsrcdir}/mach", "environment", "--format", "json", "--verbose"]
    def proc = commandLine.execute(
            machEnv(topsrcdir),
            new File(ext.has('topobjdir') ? ext.get('topobjdir') : topsrcdir))
    def standardOutput = new ByteArrayOutputStream()
    def standardError = new ByteArrayOutputStream()
    proc.consumeProcessOutput(standardOutput, standardError)
    proc.waitFor()

    // Only show the output if something went wrong.
    if (proc.exitValue() != 0) {
        throw new GradleException("Process '${commandLine}' finished with non-zero exit value ${proc.exitValue()}:\n\n"
                + "stdout:\n${standardOutput.toString()}\n\n"
                + "stderr:\n${standardError.toString()}")
    }

    def slurper = new JsonSlurper()
    def mozconfig = slurper.parseText(standardOutput.toString())

    if (topsrcdir != mozconfig.topsrcdir) {
        throw new GradleException("Specified topsrcdir ('${topsrcdir}') is not mozconfig topsrcdir ('${mozconfig.topsrcdir}')")
    }

    def topobjdir
    if (ext.has('topobjdir')) {
        topobjdir = ext.topobjdir
    } else {
        topobjdir = mozconfig.topobjdir
        log("Found topobjdir ${topobjdir} from topsrcdir ${topsrcdir}")
    }

    if (mozconfig.substs.MOZ_BUILD_APP != 'mobile/android') {
        throw new GradleException("Building with Gradle is only supported for GeckoView, i.e., MOZ_BUILD_APP == 'mobile/android'.")
    }

    log("Will substitute GeckoView (geckoview-{nightly,beta}) with local GeckoView (geckoview-default) from ${topobjdir}/gradle/build/mobile/android/geckoview/maven")

    if (!mozconfig.substs.COMPILE_ENVIRONMENT) {
        log("To update the local GeckoView, run `./mach gradle geckoview:publishWithGeckoBinariesDebugPublicationToMavenRepository` in ${topsrcdir}")
    } else {
        log("To update the local GeckoView, run `./mach build binaries && ./mach gradle geckoview:publishWithGeckoBinariesDebugPublicationToMavenRepository` in ${topsrcdir}")
    }

    return [mozconfig, topobjdir]
}

// This script is expected to be called for every subproject in the build (in ac, this is over 100)
// but loadMozconfig should only be called once per build (see the javadoc) so we store the output
// of that call as a global variable and re-use it when this script is called again.
def LOAD_MOZCONFIG_CACHE = "substitute-local-geckoview-mozconfig-cache"
if (!rootProject.ext.has(LOAD_MOZCONFIG_CACHE)) {
    rootProject.ext.set(LOAD_MOZCONFIG_CACHE, loadMozconfig())
}
def (mozconfig, topobjdir) = rootProject.ext.get(LOAD_MOZCONFIG_CACHE)

repositories {
    maven {
        name "Local GeckoView Maven repository"
        url "${topobjdir}/gradle/maven"
    }
}

configurations.all { config ->
    // Like `geckoview-nightly` for a multi-architecture fat AAR or
    // `geckoview-nightly-armeabi-v7a` for an architecture-specific AAR.
    def geckoviewModules = [
        'geckoview-nightly',
        'geckoview-nightly-armeabi-v7a',
        'geckoview-nightly-arm64-v8a',
        'geckoview-nightly-x86',
        'geckoview-nightly-x86_64',
        'geckoview-beta',
        'geckoview-beta-armeabi-v7a',
        'geckoview-beta-arm64-v8a',
        'geckoview-beta-x86',
        'geckoview-beta-x86_64',
    ]

    def geckoviewOmniModules = [
        'geckoview-nightly-omni',
        'geckoview-nightly-omni-armeabi-v7a',
        'geckoview-nightly-omni-arm64-v8a',
        'geckoview-nightly-omni-x86',
        'geckoview-nightly-omni-x86_64',
        'geckoview-beta-omni',
        'geckoview-beta-omni-armeabi-v7a',
        'geckoview-beta-omni-arm64-v8a',
        'geckoview-beta-omni-x86',
        'geckoview-beta-omni-x86_64',
    ]

    if (config.isCanBeResolved()) {
        config.resolutionStrategy { strategy ->
            dependencySubstitution {
                all { dependency ->
                    // We could restrict based on target architecture, but there doesn't seem to
                    // be much advantage to doing so right now.

                    if (!(dependency.requested instanceof ModuleComponentSelector)) {
                        // We can only substitute for a module: we're never going to substitute
                        // for a project.
                        return
                    }

                    def group = dependency.requested.group
                    def module = dependency.requested.module
                    if (group == 'org.mozilla.geckoview'
                          && (geckoviewModules.contains(module) || geckoviewOmniModules.contains(module))) {
                        def name = ''
                        def isLite = mozconfig.substs.MOZ_ANDROID_GECKOVIEW_LITE

                        if (isLite) {
                          name = 'geckoview-default'
                        } else {
                          name = 'geckoview-default-omni'
                        }

                        if (geckoviewModules.contains(module) && !isLite) {
                          warn("Substituting a geckoview omni build into a lite dependency. Add ac_add_options --enable-geckoview-lite to ${mozconfig.mozconfig.path} to fix this.")
                        } else if (geckoviewOmniModules.contains(module) && isLite) {
                          // Substituting lite into omni is unlikely to work at
                          // all so we just error out here.
                          throw new GradleException("Substituting a geckoview lite build into an omni dependency. Remove ac_add_options --enable-geckoview-lite in ${mozconfig.mozconfig.path} to fix this.")
                        }

                        log("Substituting ${group}:${dependency.requested.module} with local GeckoView ${group}:${name} in ${config}")

                        dependency.useTarget([group: group, name: name, version: '+'])

                        // We substitute with a dynamic version ('+').  It seems that Gradle
                        // discovers the underlying AAR is out of date correctly based on file
                        // timestamp already, but let's try to avoid some class of cache
                        // invalidation error while we're here.
                        strategy.cacheDynamicVersionsFor 0, 'seconds'
                        strategy.cacheChangingModulesFor 0, 'seconds'
                    }
                }
            }
        }
    }
}