summaryrefslogtreecommitdiffstats
path: root/mobile/android/gradle
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /mobile/android/gradle
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--mobile/android/gradle.configure617
-rw-r--r--mobile/android/gradle.py60
-rw-r--r--mobile/android/gradle/debug_level.gradle17
-rw-r--r--mobile/android/gradle/dotgradle-offline/gradle.properties3
-rw-r--r--mobile/android/gradle/dotgradle-offline/init.gradle4
-rw-r--r--mobile/android/gradle/dotgradle-online/gradle.properties3
-rw-r--r--mobile/android/gradle/dotgradle-online/init.gradle4
-rw-r--r--mobile/android/gradle/mach_env.gradle29
-rw-r--r--mobile/android/gradle/product_flavors.gradle17
-rw-r--r--mobile/android/gradle/with_gecko_binaries.gradle357
10 files changed, 1111 insertions, 0 deletions
diff --git a/mobile/android/gradle.configure b/mobile/android/gradle.configure
new file mode 100644
index 0000000000..09c69e124a
--- /dev/null
+++ b/mobile/android/gradle.configure
@@ -0,0 +1,617 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+# If --with-gradle is specified, build mobile/android with Gradle. If no
+# Gradle binary is specified use the in tree Gradle wrapper. The wrapper
+# downloads and installs Gradle, which is good for local developers but not
+# good in automation.
+option(
+ "--without-gradle",
+ nargs="?",
+ help="Disable building mobile/android with Gradle "
+ "(argument: location of binary or wrapper (gradle/gradlew))",
+)
+
+
+@depends("--with-gradle")
+def with_gradle(value):
+ if not value:
+ die(
+ "Building --without-gradle is no longer supported: "
+ "see https://bugzilla.mozilla.org/show_bug.cgi?id=1414415."
+ )
+
+ if value:
+ return True
+
+
+@depends(host, "--with-gradle", build_environment)
+@imports(_from="os.path", _import="isfile")
+def gradle(host, value, build_env):
+ if len(value):
+ gradle = value[0]
+ else:
+ gradle = os.path.join(build_env.topsrcdir, "gradlew")
+ if host.os == "WINNT":
+ gradle = gradle + ".bat"
+
+ # TODO: verify that $GRADLE is executable.
+ if not isfile(gradle):
+ die("GRADLE must be executable: %s", gradle)
+
+ return gradle
+
+
+set_config("GRADLE", gradle)
+
+
+@dependable
+@imports(_from="itertools", _import="chain")
+def gradle_android_build_config():
+ def capitalize(s):
+ # str.capitalize lower cases trailing letters.
+ if s:
+ return s[0].upper() + s[1:]
+ else:
+ return s
+
+ def variant(productFlavors, buildType):
+ return namespace(
+ productFlavors=productFlavors,
+ buildType=buildType,
+ # Like 'WithoutGeckoBinariesDebug'
+ name="".join(capitalize(t) for t in chain(productFlavors, (buildType,))),
+ )
+
+ return namespace(
+ geckoview=namespace(
+ variant=variant(("withGeckoBinaries",), "debug"),
+ ),
+ geckoview_example=namespace(
+ variant=variant(("withGeckoBinaries",), "debug"),
+ ),
+ )
+
+
+@depends(gradle_android_build_config)
+def gradle_android_intermediates_folder(build_config):
+ """Path to intermediates classes folder."""
+
+ def uncapitalize(s):
+ if s:
+ return s[0].lower() + s[1:]
+ else:
+ return s
+
+ def capitalize(s):
+ # str.capitalize lower cases trailing letters.
+ if s:
+ return s[0].upper() + s[1:]
+ else:
+ return s
+
+ productFlavor = uncapitalize(
+ "".join(capitalize(f) for f in build_config.geckoview.variant.productFlavors)
+ )
+ buildType = uncapitalize(build_config.geckoview.variant.buildType)
+
+ return (
+ "gradle/build/mobile/android/geckoview/intermediates/javac/{}{}/classes".format(
+ productFlavor,
+ capitalize(buildType),
+ )
+ )
+
+
+set_config(
+ "GRADLE_ANDROID_GECKOVIEW_APILINT_FOLDER", gradle_android_intermediates_folder
+)
+
+
+@depends(gradle_android_build_config)
+def gradle_android_geckoview_test_runner_bundle(build_config):
+ """Path to intermediates classes folder."""
+
+ def uncapitalize(s):
+ if s:
+ return s[0].lower() + s[1:]
+ else:
+ return s
+
+ def capitalize(s):
+ # str.capitalize lower cases trailing letters.
+ if s:
+ return s[0].upper() + s[1:]
+ else:
+ return s
+
+ productFlavor = uncapitalize(
+ "".join(capitalize(f) for f in build_config.geckoview.variant.productFlavors)
+ )
+ buildType = uncapitalize(build_config.geckoview.variant.buildType)
+ variant = uncapitalize(build_config.geckoview.variant.name)
+
+ return "gradle/build/mobile/android/test_runner/outputs/bundle/{}/test_runner-{}-{}.aab".format(
+ variant,
+ productFlavor,
+ buildType,
+ )
+
+
+set_config(
+ "GRADLE_ANDROID_GECKOVIEW_TEST_RUNNER_BUNDLE",
+ gradle_android_geckoview_test_runner_bundle,
+)
+
+
+@depends(gradle_android_build_config)
+def gradle_android_geckoview_example_bundle(build_config):
+ """Path to intermediates classes folder."""
+
+ def uncapitalize(s):
+ if s:
+ return s[0].lower() + s[1:]
+ else:
+ return s
+
+ def capitalize(s):
+ # str.capitalize lower cases trailing letters.
+ if s:
+ return s[0].upper() + s[1:]
+ else:
+ return s
+
+ productFlavor = uncapitalize(
+ "".join(capitalize(f) for f in build_config.geckoview.variant.productFlavors)
+ )
+ buildType = uncapitalize(build_config.geckoview.variant.buildType)
+ variant = uncapitalize(build_config.geckoview.variant.name)
+
+ return "gradle/build/mobile/android/geckoview_example/outputs/bundle/{}/geckoview_example-{}-{}.aab".format(
+ variant,
+ productFlavor,
+ buildType,
+ )
+
+
+set_config(
+ "GRADLE_ANDROID_GECKOVIEW_EXAMPLE_BUNDLE", gradle_android_geckoview_example_bundle
+)
+
+
+@depends(gradle_android_build_config)
+def gradle_android_variant_name(build_config):
+ """Like "withoutGeckoBinariesDebug"."""
+
+ def uncapitalize(s):
+ if s:
+ return s[0].lower() + s[1:]
+ else:
+ return s
+
+ return namespace(
+ geckoview=uncapitalize(build_config.geckoview.variant.name),
+ )
+
+
+set_config(
+ "GRADLE_ANDROID_GECKOVIEW_VARIANT_NAME", gradle_android_variant_name.geckoview
+)
+
+
+@depends(gradle_android_build_config)
+def gradle_android_app_tasks(build_config):
+ """Gradle tasks run by |mach android assemble-app|."""
+ return [
+ "geckoview:generateJNIWrappersForGenerated{geckoview.variant.name}".format(
+ geckoview=build_config.geckoview
+ ),
+ ]
+
+
+set_config("GRADLE_ANDROID_APP_TASKS", gradle_android_app_tasks)
+
+
+@dependable
+def gradle_android_generate_sdk_bindings_tasks():
+ """Gradle tasks run by |mach android generate-sdk-bindings|."""
+ return [
+ "geckoview:generateSDKBindings",
+ ]
+
+
+set_config(
+ "GRADLE_ANDROID_GENERATE_SDK_BINDINGS_TASKS",
+ gradle_android_generate_sdk_bindings_tasks,
+)
+
+
+@depends(gradle_android_build_config)
+def gradle_android_generate_generated_jni_wrappers_tasks(build_config):
+ """Gradle tasks run by |mach android generate-generated-jni-wrappers|."""
+ return [
+ "geckoview:generateJNIWrappersForGenerated{geckoview.variant.name}".format(
+ geckoview=build_config.geckoview
+ ),
+ ]
+
+
+set_config(
+ "GRADLE_ANDROID_GENERATE_GENERATED_JNI_WRAPPERS_TASKS",
+ gradle_android_generate_generated_jni_wrappers_tasks,
+)
+
+
+@depends(gradle_android_build_config)
+def gradle_android_test_tasks(build_config):
+ """Gradle tasks run by |mach android test|."""
+ return [
+ "geckoview:test{geckoview.variant.name}UnitTest".format(
+ geckoview=build_config.geckoview
+ ),
+ ]
+
+
+set_config("GRADLE_ANDROID_TEST_TASKS", gradle_android_test_tasks)
+
+
+@depends(gradle_android_build_config)
+def gradle_android_lint_tasks(build_config):
+ """Gradle tasks run by |mach android lint|."""
+ return [
+ "geckoview:lint{geckoview.variant.name}".format(
+ geckoview=build_config.geckoview
+ ),
+ ]
+
+
+set_config("GRADLE_ANDROID_LINT_TASKS", gradle_android_lint_tasks)
+
+
+@depends(gradle_android_build_config)
+def gradle_android_api_lint_tasks(build_config):
+ """Gradle tasks run by |mach android api-lint|."""
+ return [
+ "geckoview:apiLint{geckoview.variant.name}".format(
+ geckoview=build_config.geckoview
+ ),
+ ]
+
+
+set_config("GRADLE_ANDROID_API_LINT_TASKS", gradle_android_api_lint_tasks)
+
+
+set_config(
+ "GRADLE_ANDROID_FORMAT_LINT_FIX_TASKS", ["spotlessJavaApply", "spotlessKotlinApply"]
+)
+
+
+@dependable
+def gradle_android_format_lint_check_tasks():
+ return ["spotlessJavaCheck", "spotlessKotlinCheck"]
+
+
+set_config(
+ "GRADLE_ANDROID_FORMAT_LINT_CHECK_TASKS", gradle_android_format_lint_check_tasks
+)
+
+set_config(
+ "GRADLE_ANDROID_FORMAT_LINT_FOLDERS",
+ [
+ "mobile/android/annotations",
+ "mobile/android/geckoview",
+ "mobile/android/geckoview_example",
+ "mobile/android/test_runner",
+ "mobile/android/examples/messaging_example",
+ "mobile/android/examples/port_messaging_example",
+ ],
+)
+
+
+@depends(gradle_android_build_config)
+def gradle_android_checkstyle_tasks(build_config):
+ """Gradle tasks run by |mach android checkstyle|."""
+ return [
+ "geckoview:checkstyle{geckoview.variant.name}".format(
+ geckoview=build_config.geckoview
+ ),
+ ]
+
+
+set_config("GRADLE_ANDROID_CHECKSTYLE_TASKS", gradle_android_checkstyle_tasks)
+
+
+@depends(gradle_android_build_config)
+def gradle_android_checkstyle_output_files(build_config):
+ def uncapitalize(s):
+ if s:
+ return s[0].lower() + s[1:]
+ else:
+ return s
+
+ variant = uncapitalize(build_config.geckoview.variant.name)
+
+ """Output folder for checkstyle"""
+ return [
+ "gradle/build/mobile/android/geckoview/reports/checkstyle/{}.xml".format(
+ variant
+ ),
+ ]
+
+
+set_config(
+ "GRADLE_ANDROID_CHECKSTYLE_OUTPUT_FILES", gradle_android_checkstyle_output_files
+)
+
+
+option(
+ "--disable-android-bundle",
+ help="{Enable|Disable} AAB build.",
+)
+
+imply_option("--disable-android-bundle", False, when="--enable-address-sanitizer")
+
+
+@depends(gradle_android_build_config, "--disable-android-bundle")
+def gradle_android_archive_geckoview_tasks(build_config, aab_enabled):
+ """Gradle tasks run by |mach android archive-geckoview|."""
+ tasks = [
+ "geckoview:assemble{geckoview.variant.name}".format(
+ geckoview=build_config.geckoview
+ ),
+ "geckoview:assemble{geckoview.variant.name}AndroidTest".format(
+ geckoview=build_config.geckoview
+ ),
+ "test_runner:assemble{geckoview_example.variant.name}".format(
+ geckoview_example=build_config.geckoview_example
+ ),
+ "geckoview_example:assemble{geckoview_example.variant.name}".format(
+ geckoview_example=build_config.geckoview_example
+ ),
+ "messaging_example:assemble{geckoview_example.variant.name}".format(
+ geckoview_example=build_config.geckoview_example
+ ),
+ "port_messaging_example:assemble{geckoview_example.variant.name}".format(
+ geckoview_example=build_config.geckoview_example
+ ),
+ "geckoview:publish{geckoview.variant.name}PublicationToMavenRepository".format(
+ geckoview=build_config.geckoview
+ ),
+ "exoplayer2:publishDebugPublicationToMavenRepository",
+ ]
+
+ if aab_enabled:
+ tasks += [
+ "test_runner:bundle{geckoview_example.variant.name}".format(
+ geckoview_example=build_config.geckoview_example
+ ),
+ "geckoview_example:bundle{geckoview_example.variant.name}".format(
+ geckoview_example=build_config.geckoview_example
+ ),
+ ]
+ return tasks
+
+
+set_config(
+ "GRADLE_ANDROID_ARCHIVE_GECKOVIEW_TASKS", gradle_android_archive_geckoview_tasks
+)
+
+
+@depends(gradle_android_build_config)
+def gradle_android_geckoview_docs_tasks(build_config):
+ """Gradle tasks run by |mach android geckoview-docs|."""
+ return [
+ "geckoview:javadoc{geckoview.variant.name}".format(
+ geckoview=build_config.geckoview
+ ),
+ ]
+
+
+set_config("GRADLE_ANDROID_GECKOVIEW_DOCS_TASKS", gradle_android_geckoview_docs_tasks)
+
+
+@depends(gradle_android_build_config)
+def gradle_android_geckoview_docs_archive_tasks(build_config):
+ """Gradle tasks run by |mach android geckoview-docs --archive| or |... --upload."""
+ return [
+ "geckoview:javadocCopyJar{geckoview.variant.name}".format(
+ geckoview=build_config.geckoview
+ ),
+ ]
+
+
+set_config(
+ "GRADLE_ANDROID_GECKOVIEW_DOCS_ARCHIVE_TASKS",
+ gradle_android_geckoview_docs_archive_tasks,
+)
+
+
+@depends(gradle_android_build_config)
+def gradle_android_geckoview_docs_output_files(build_config):
+ """Output files for GeckoView javadoc."""
+
+ def uncapitalize(s):
+ if s:
+ return s[0].lower() + s[1:]
+ else:
+ return s
+
+ variant = uncapitalize(build_config.geckoview.variant.name)
+
+ return [
+ "gradle/build/mobile/android/geckoview/reports/javadoc-results-{}.json".format(
+ variant
+ ),
+ ]
+
+
+set_config(
+ "GRADLE_ANDROID_GECKOVIEW_DOCS_OUTPUT_FILES",
+ gradle_android_geckoview_docs_output_files,
+)
+
+
+@depends(gradle_android_build_config)
+def gradle_android_archive_coverage_artifacts_tasks(build_config):
+ """Gradle tasks run by |mach android archive-coverage-artifacts|."""
+ return [
+ "geckoview:archiveClassfiles{geckoview.variant.name}".format(
+ geckoview=build_config.geckoview
+ ),
+ "geckoview:copyCoverageDependencies",
+ ]
+
+
+set_config(
+ "GRADLE_ANDROID_ARCHIVE_COVERAGE_ARTIFACTS_TASKS",
+ gradle_android_archive_coverage_artifacts_tasks,
+)
+
+
+@depends(gradle_android_build_config)
+def gradle_android_build_geckoview_example_tasks(build_config):
+ """Gradle tasks run by |mach android build-geckoview_example|."""
+ return [
+ "geckoview_example:assemble{geckoview_example.variant.name}".format(
+ geckoview_example=build_config.geckoview_example
+ ),
+ "geckoview_example:bundle{geckoview_example.variant.name}".format(
+ geckoview_example=build_config.geckoview_example
+ ),
+ "geckoview:assemble{geckoview.variant.name}AndroidTest".format(
+ geckoview=build_config.geckoview
+ ),
+ "test_runner:assemble{geckoview.variant.name}".format(
+ geckoview=build_config.geckoview
+ ),
+ "test_runner:bundle{geckoview.variant.name}".format(
+ geckoview=build_config.geckoview
+ ),
+ ]
+
+
+set_config(
+ "GRADLE_ANDROID_BUILD_GECKOVIEW_EXAMPLE_TASKS",
+ gradle_android_build_geckoview_example_tasks,
+)
+
+
+@depends(gradle_android_build_config)
+def gradle_android_install_geckoview_test_runner_tasks(build_config):
+ """Gradle tasks run by |mach android install-geckoview-test_runner|."""
+ return [
+ "test_runner:install{geckoview.variant.name}".format(
+ geckoview=build_config.geckoview
+ ),
+ ]
+
+
+set_config(
+ "GRADLE_ANDROID_INSTALL_GECKOVIEW_TEST_RUNNER_TASKS",
+ gradle_android_install_geckoview_test_runner_tasks,
+)
+
+
+@depends(gradle_android_build_config)
+def gradle_android_install_geckoview_test_tasks(build_config):
+ """Gradle tasks run by |mach android install-geckoview-test|."""
+ return [
+ "geckoview:install{geckoview.variant.name}AndroidTest".format(
+ geckoview=build_config.geckoview
+ ),
+ ]
+
+
+set_config(
+ "GRADLE_ANDROID_INSTALL_GECKOVIEW_TEST_TASKS",
+ gradle_android_install_geckoview_test_tasks,
+)
+
+
+@depends(gradle_android_build_config)
+def gradle_android_install_geckoview_example_tasks(build_config):
+ """Gradle tasks run by |mach android install-geckoview_example|."""
+ return [
+ "geckoview_example:install{geckoview_example.variant.name}".format(
+ geckoview_example=build_config.geckoview_example
+ ),
+ ]
+
+
+set_config(
+ "GRADLE_ANDROID_INSTALL_GECKOVIEW_EXAMPLE_TASKS",
+ gradle_android_install_geckoview_example_tasks,
+)
+
+
+@depends(
+ gradle_android_api_lint_tasks,
+ gradle_android_format_lint_check_tasks,
+ gradle_android_checkstyle_tasks,
+)
+@imports(_from="itertools", _import="chain")
+def gradle_android_dependencies_tasks(*tasks):
+ """Gradle tasks run by |mach android dependencies|."""
+ # The union, plus a bit more, of all of the Gradle tasks
+ # invoked by the android-* automation jobs.
+ def withoutGeckoBinaries(task):
+ return task.replace("withGeckoBinaries", "withoutGeckoBinaries")
+
+ return list(withoutGeckoBinaries(t) for t in chain(*tasks))
+
+
+set_config("GRADLE_ANDROID_DEPENDENCIES_TASKS", gradle_android_dependencies_tasks)
+
+
+# Automation uses this to change log levels, not use the daemon, and use
+# offline mode.
+option(env="GRADLE_FLAGS", default="", help="Flags to pass to Gradle.")
+
+
+@depends("GRADLE_FLAGS")
+def gradle_flags(value):
+ return value[0] if value else ""
+
+
+set_config("GRADLE_FLAGS", gradle_flags)
+
+# Automation will set this to (file:///path/to/local, ...) via the mozconfig.
+# Local developer default is (maven.google.com).
+option(
+ env="GRADLE_MAVEN_REPOSITORIES",
+ nargs="+",
+ default=(
+ "https://maven.mozilla.org/maven2/",
+ "https://maven.google.com/",
+ "https://repo.maven.apache.org/maven2/",
+ "https://plugins.gradle.org/m2/",
+ ),
+ help="Comma-separated URLs of Maven repositories containing Gradle dependencies.",
+)
+
+option(
+ "--allow-insecure-gradle-repositories",
+ help="Gradle is allowed to connect to insecure Maven repositories.",
+)
+
+set_config(
+ "ALLOW_INSECURE_GRADLE_REPOSITORIES",
+ True,
+ when="--allow-insecure-gradle-repositories",
+)
+
+
+@depends("GRADLE_MAVEN_REPOSITORIES")
+@imports(_from="os.path", _import="isdir")
+def gradle_maven_repositories(values):
+ if not values:
+ die("GRADLE_MAVEN_REPOSITORIES must not be empty")
+ if not all(values):
+ die("GRADLE_MAVEN_REPOSITORIES entries must not be empty")
+ return values
+
+
+set_config("GRADLE_MAVEN_REPOSITORIES", gradle_maven_repositories)
diff --git a/mobile/android/gradle.py b/mobile/android/gradle.py
new file mode 100644
index 0000000000..9f310ddb7b
--- /dev/null
+++ b/mobile/android/gradle.py
@@ -0,0 +1,60 @@
+# 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/.
+
+import os
+import subprocess
+import sys
+from contextlib import contextmanager
+
+import mozpack.path as mozpath
+from mozbuild.util import ensureParentDir, lock_file
+
+
+@contextmanager
+def gradle_lock(topobjdir, max_wait_seconds=600):
+ # Building the same Gradle root project with multiple concurrent processes
+ # is not well supported, so we use a simple lock file to serialize build
+ # steps.
+ lock_path = "{}/gradle/mach_android.lockfile".format(topobjdir)
+ ensureParentDir(lock_path)
+ lock_instance = lock_file(lock_path, max_wait=max_wait_seconds)
+
+ try:
+ yield
+ finally:
+ del lock_instance
+
+
+def android(verb, *args):
+ import buildconfig
+
+ with gradle_lock(buildconfig.topobjdir):
+ cmd = [
+ sys.executable,
+ mozpath.join(buildconfig.topsrcdir, "mach"),
+ "android",
+ verb,
+ ]
+ cmd.extend(args)
+ env = dict(os.environ)
+ # Confusingly, `MACH` is set only within `mach build`.
+ if env.get("MACH"):
+ env["GRADLE_INVOKED_WITHIN_MACH_BUILD"] = "1"
+ if env.get("LD_LIBRARY_PATH"):
+ del env["LD_LIBRARY_PATH"]
+ subprocess.check_call(cmd, env=env)
+
+ return 0
+
+
+def assemble_app(dummy_output_file, *inputs):
+ return android("assemble-app")
+
+
+def generate_sdk_bindings(dummy_output_file, *args):
+ return android("generate-sdk-bindings", *args)
+
+
+def generate_generated_jni_wrappers(dummy_output_file, *args):
+ return android("generate-generated-jni-wrappers", *args)
diff --git a/mobile/android/gradle/debug_level.gradle b/mobile/android/gradle/debug_level.gradle
new file mode 100644
index 0000000000..a9537da327
--- /dev/null
+++ b/mobile/android/gradle/debug_level.gradle
@@ -0,0 +1,17 @@
+/* -*- Mode: Groovy; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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/. */
+
+// Bug 1353055 - Strip 'vars' debugging information to agree with moz.build.
+ext.configureVariantDebugLevel = { variant ->
+ // Like 'debug' or 'release'.
+ def buildType = variant.buildType.name
+
+ // The default is 'lines,source,vars', which includes debugging information
+ // that is quite large: roughly 500kb for Fennec. Therefore we remove
+ // 'vars' unless we're producing a debug build, where it is useful.
+ if (!'debug'.equals(buildType) || mozconfig.substs.MOZILLA_OFFICIAL) {
+ variant.javaCompileProvider.get().options.debugOptions.debugLevel = 'lines,source'
+ }
+}
diff --git a/mobile/android/gradle/dotgradle-offline/gradle.properties b/mobile/android/gradle/dotgradle-offline/gradle.properties
new file mode 100644
index 0000000000..3f77ec9a2f
--- /dev/null
+++ b/mobile/android/gradle/dotgradle-offline/gradle.properties
@@ -0,0 +1,3 @@
+// Per https://docs.gradle.org/current/userguide/build_environment.html, this
+// overrides the gradle.properties in topsrcdir.
+org.gradle.daemon=false
diff --git a/mobile/android/gradle/dotgradle-offline/init.gradle b/mobile/android/gradle/dotgradle-offline/init.gradle
new file mode 100644
index 0000000000..8f06472aed
--- /dev/null
+++ b/mobile/android/gradle/dotgradle-offline/init.gradle
@@ -0,0 +1,4 @@
+// From https://discuss.gradle.org/t/enable-offline-mode-using-gradle-properties/12134/2.
+startParameter.offline = true
+// Sadly, this doesn't work: see http://stackoverflow.com/a/19686585.
+// startParameter.logLevel = org.gradle.api.logging.LogLevel.INFO
diff --git a/mobile/android/gradle/dotgradle-online/gradle.properties b/mobile/android/gradle/dotgradle-online/gradle.properties
new file mode 100644
index 0000000000..3f77ec9a2f
--- /dev/null
+++ b/mobile/android/gradle/dotgradle-online/gradle.properties
@@ -0,0 +1,3 @@
+// Per https://docs.gradle.org/current/userguide/build_environment.html, this
+// overrides the gradle.properties in topsrcdir.
+org.gradle.daemon=false
diff --git a/mobile/android/gradle/dotgradle-online/init.gradle b/mobile/android/gradle/dotgradle-online/init.gradle
new file mode 100644
index 0000000000..dbba0e3da5
--- /dev/null
+++ b/mobile/android/gradle/dotgradle-online/init.gradle
@@ -0,0 +1,4 @@
+// From https://discuss.gradle.org/t/enable-offline-mode-using-gradle-properties/12134/2.
+startParameter.offline = false
+// Sadly, this doesn't work: see http://stackoverflow.com/a/19686585.
+// startParameter.logLevel = org.gradle.api.logging.LogLevel.INFO
diff --git a/mobile/android/gradle/mach_env.gradle b/mobile/android/gradle/mach_env.gradle
new file mode 100644
index 0000000000..560d7dac22
--- /dev/null
+++ b/mobile/android/gradle/mach_env.gradle
@@ -0,0 +1,29 @@
+ext.machEnv = { topsrcdir ->
+ // Allow to specify mozconfig in `local.properties` via
+ // `mozilla-central.mozconfig=/path/to/mozconfig`. This can't be an environment
+ // variable because it's not feasible to specify environment variables under
+ // Android Studio on some platforms including macOS.
+ def localProperties = new Properties()
+ def localPropertiesFile = new File(topsrcdir, 'local.properties')
+ if (localPropertiesFile.canRead()) {
+ localPropertiesFile.withInputStream {
+ localProperties.load(it)
+ logger.lifecycle("settings.gradle> Read local.properties: ${localPropertiesFile}")
+ }
+ }
+
+ def localMozconfig = localProperties.getProperty("mozilla-central.mozconfig")
+
+ def env = System.env.collect { k, v -> "${k}=${v}" }
+ if (localMozconfig) {
+ def envMozconfig = System.env.get('FOUND_MOZCONFIG')
+ if (!envMozconfig || localMozconfig == envMozconfig) {
+ logger.lifecycle("settings.gradle> Setting mozconfig from local.properties: ${localMozconfig}")
+ env << "MOZCONFIG=${localMozconfig}"
+ } else {
+ logger.lifecycle("settings.gradle> Preferring mozconfig set in mach environment to mozconfig set in local.properties: ${envMozconfig}")
+ }
+ }
+
+ return env
+}
diff --git a/mobile/android/gradle/product_flavors.gradle b/mobile/android/gradle/product_flavors.gradle
new file mode 100644
index 0000000000..6278d9ff3f
--- /dev/null
+++ b/mobile/android/gradle/product_flavors.gradle
@@ -0,0 +1,17 @@
+/* -*- Mode: Groovy; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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/. */
+
+ext.configureProductFlavors = {
+ flavorDimensions "geckoBinaries"
+ productFlavors {
+ withGeckoBinaries {
+ dimension "geckoBinaries"
+ }
+
+ withoutGeckoBinaries {
+ dimension "geckoBinaries"
+ }
+ }
+}
diff --git a/mobile/android/gradle/with_gecko_binaries.gradle b/mobile/android/gradle/with_gecko_binaries.gradle
new file mode 100644
index 0000000000..5f0d4eeddd
--- /dev/null
+++ b/mobile/android/gradle/with_gecko_binaries.gradle
@@ -0,0 +1,357 @@
+/* -*- Mode: Groovy; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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/. */
+
+// The JNI wrapper generation tasks depend on the JAR creation task of the :annotations project.
+evaluationDependsOn(':annotations')
+
+import groovy.util.FileNameFinder
+import groovy.transform.Memoized
+
+import java.nio.file.Path
+import java.nio.file.Paths
+import java.security.MessageDigest
+
+// To find the Android NDK directory, there are a few wrinkles. In a compiled
+// build, we can use our own `ANDROID_NDK` configure option. But in an
+// artifact build, that isn't defined, so we fall back to
+// `android.ndkDirectory` -- but that's defined in `local.properties`, which
+// may not define it. In that case, fall back to crawling the filesystem.
+def getNDKDirectory() {
+ if (project.mozconfig.substs.ANDROID_NDK) {
+ return project.mozconfig.substs.ANDROID_NDK
+ }
+ try {
+ if (project.android.ndkDirectory) {
+ return project.android.ndkDirectory
+ }
+ } catch (InvalidUserDataException ex) {
+ // The NDK is not installed, that's ok.
+ }
+ def mozbuild = System.getenv('MOZBUILD_STATE_PATH') ?: "${System.getProperty('user.home')}/.mozbuild"
+ def files = new FileNameFinder().getFileNames(mozbuild, "android-ndk-*/source.properties")
+ if (files) {
+ // It would be nice to sort these by version, but that's too much effort right now.
+ return project.file(files.first()).parentFile.toString()
+ }
+ return null
+}
+
+// Whether to include compiled artifacts: `libs/**/*.so` and `assets/omni.ja`.
+// Multi-locale packaging wants to include compiled artifacts but *not* rebuild
+// them: see also `rootProject.{machStagePackage,geckoBinariesOnlyIf}`.
+def hasCompileArtifacts() {
+ return project.mozconfig.substs.COMPILE_ENVIRONMENT // Full builds.
+ || project.mozconfig.substs.MOZ_ARTIFACT_BUILDS // Artifact builds.
+ || System.getenv("MOZ_CHROME_MULTILOCALE") // Multi-locale packaging.
+}
+
+// Get the LLVM bin folder, either from the currently used toolchain or, if
+// this is an artifact build, from the Android NDK.
+@Memoized
+def getLlvmBinFolder() {
+ // If we have a clang path, just use that
+ if (project.mozconfig.substs.MOZ_CLANG_PATH) {
+ return project.file(project.mozconfig.substs.MOZ_CLANG_PATH)
+ .parentFile
+ }
+
+ def ndkDirectory = getNDKDirectory()
+ if (!ndkDirectory) {
+ // Give up
+ return null;
+ }
+
+ // The `**` in the pattern depends on the host architecture. We could compute them or bake them
+ // in, but this is easy enough. `FileNameFinder` won't return a directory, so we find a file
+ // and return its parent directory.
+ return project
+ .file(new FileNameFinder()
+ .getFileNames(ndkDirectory.toString(), "toolchains/llvm/prebuilt/**/bin/llvm-*")
+ .first())
+ .parentFile
+}
+
+ext.getLlvmBinFolder = {
+ // This is the easiest way to expose the memoized function to consumers.
+ getLlvmBinFolder()
+}
+
+// Bug 1657190: This task works around a bug in the Android-Gradle plugin. When using SNAPSHOT
+// builds (and possibly in other situations) the JNI library invalidation process isn't correct. If
+// there are two JNI libs `a.so` and `b.so` and a new snapshot updates only `a.so`, then the
+// resulting AAR will include both JNI libs but the consuming project's resulting APK will include
+// only the modified `a.so`. For GeckoView, it's usually `libxul.so` that is updated. For a
+// consumer (like Fenix), this leads to a hard startup crash because `libmozglue.so` will be missing
+// when it is read (first, before `libxul.so`)
+//
+// For !MOZILLA_OFFICIAL builds, we work around this issue to ensure that when *any* library is
+// updated then *every* library is updated. We use the ELF build ID baked into each library to
+// determine whether any library is updated. We digest (SHA256, for now) all of the ELF build IDs
+// and use this as our own Mozilla-specific "library generation ID". We then add our own
+// Mozilla-specific ELF section to every library so that they will all be invalidated by the
+// Android-Gradle plugin of a consuming project.
+class SyncLibsAndUpdateGenerationID extends DefaultTask {
+ @InputDirectory
+ File source
+
+ @InputFile
+ File extraSource
+
+ @OutputDirectory
+ File destinationDir
+
+ @InputDirectory
+ File llvmBin = project.ext.getLlvmBinFolder()
+
+ // Sibling to `.note.gnu.build-id`.
+ @Input
+ String newElfSection = ".note.mozilla.build-id"
+
+ @TaskAction
+ void execute() {
+ Path root = Paths.get(source.toString())
+
+ def libs = project.fileTree(source.toString()).files.collect {
+ Path lib = Paths.get(it.toString())
+ root.relativize(lib).toString()
+ }
+
+ def generationId = new ByteArrayOutputStream().withStream { os ->
+ // Start with the hash of any extra source.
+ def digest = MessageDigest.getInstance("SHA-256")
+ extraSource.eachByte(64 * 1024) { byte[] buf, int bytesRead ->
+ digest.update(buf, 0, bytesRead);
+ }
+ def extraSourceHex = new BigInteger(1, digest.digest()).toString(16).padLeft(64, '0')
+ os.write("${extraSource.toString()} ${extraSourceHex}\n".getBytes("utf-8"));
+
+ // Follow with all the ordered build ID section dumps.
+ libs.each { lib ->
+ // This should never fail. If it does, there's a real problem, so an exception is
+ // reasonable.
+ project.exec {
+ commandLine "${llvmBin}/llvm-readobj"
+ args '--hex-dump=.note.gnu.build-id'
+ args "${source}/${lib}"
+ standardOutput = os
+ }
+ }
+
+ def allBuildIDs = os.toString()
+
+ // For detailed debugging.
+ new File("${destinationDir}/${newElfSection}-details").setText(allBuildIDs, 'utf-8')
+
+ allBuildIDs.sha256()
+ }
+
+ logger.info("Mozilla-specific library generation ID is ${generationId} (see ${destinationDir}/${newElfSection}-details)")
+
+ def generationIdIsStale = libs.any { lib ->
+ new ByteArrayOutputStream().withStream { os ->
+ // This can fail, but there's little value letting stderr go to the console in
+ // general, since it's just noise after a clobber build.
+ def execResult = project.exec {
+ ignoreExitValue true
+ commandLine "${llvmBin}/llvm-readobj"
+ args "--hex-dump=${newElfSection}"
+ args "${destinationDir}/${lib}"
+ standardOutput = os
+ errorOutput = new ByteArrayOutputStream()
+ }
+
+ if (execResult.exitValue != 0) {
+ logger.info("Mozilla-specific library generation ID is missing: ${lib}")
+ } else if (!os.toString().contains(generationId)) {
+ logger.info("Mozilla-specific library generation ID is stale: ${lib}")
+ } else {
+ logger.debug("Mozilla-specific library generation ID is fresh: ${lib}")
+ }
+ execResult.exitValue != 0 || !os.toString().contains(generationId)
+ }
+ }
+
+ if (generationIdIsStale) {
+ project.mkdir destinationDir
+ new File("${destinationDir}/${newElfSection}").setText(generationId, 'utf-8')
+
+ libs.each { lib ->
+ project.mkdir project.file("${destinationDir}/${lib}").parent
+
+ new ByteArrayOutputStream().withStream { os ->
+ // This should never fail: if it does, there's a real problem (again).
+ project.exec {
+ commandLine "${llvmBin}/llvm-objcopy"
+ args "--add-section=${newElfSection}=${destinationDir}/${newElfSection}"
+ args "${source}/${lib}"
+ args "${destinationDir}/${lib}"
+ standardOutput = os
+ }
+ }
+ }
+ } else {
+ logger.info("Mozilla-specific library generation ID is fresh!")
+ }
+ }
+}
+
+ext.configureVariantWithGeckoBinaries = { variant ->
+ def omnijarDir = "${topobjdir}/dist/geckoview"
+ def distDir = "${topobjdir}/dist/geckoview"
+
+ def syncOmnijarFromDistDir
+ if (hasCompileArtifacts()) {
+ syncOmnijarFromDistDir = task("syncOmnijarFromDistDirFor${variant.name.capitalize()}", type: Sync) {
+ onlyIf {
+ if (source.empty) {
+ throw new StopExecutionException("Required omnijar not found in ${omnijarDir}/{omni.ja,assets/omni.ja}. Have you built and packaged?")
+ }
+ return true
+ }
+
+ into("${project.buildDir}/moz.build/src/${variant.name}/omnijar")
+ from("${omnijarDir}/omni.ja",
+ "${omnijarDir}/assets/omni.ja") {
+ // Throw an exception if we find multiple, potentially conflicting omni.ja files.
+ duplicatesStrategy 'fail'
+ }
+ }
+ }
+
+ // For !MOZILLA_OFFICIAL builds, work around an Android-Gradle plugin bug that causes startup
+ // crashes with local substitution. But -- we want to allow artifact builds that don't have the
+ // NDK installed. See class comment above.
+ def shouldUpdateGenerationID = {
+ if (mozconfig.substs.MOZILLA_OFFICIAL) {
+ return false
+ } else if (mozconfig.substs.MOZ_ANDROID_FAT_AAR_ARCHITECTURES) {
+ return false
+ } else if (ext.getLlvmBinFolder() == null) {
+ logger.warn("Could not determine LLVM bin directory.")
+ logger.warn("Set `ndk.dir` in `${project.topsrcdir}/local.properties` to avoid startup crashes when using `substitute-local-geckoview.gradle`.")
+ logger.warn("See https://bugzilla.mozilla.org/show_bug.cgi?id=1657190.")
+ return false
+ }
+ return true
+ }()
+
+ def syncLibsFromDistDir = { if (shouldUpdateGenerationID) {
+ def jarTask = tasks["bundleLibRuntimeToJar${variant.name.capitalize()}"]
+ def bundleJar = jarTask.outputs.files.find({ it.name == 'classes.jar' })
+
+ task("syncLibsAndUpdateGenerationIDFromDistDirFor${variant.name.capitalize()}", type: SyncLibsAndUpdateGenerationID) {
+ source file("${distDir}/lib")
+ destinationDir file("${project.buildDir}/moz.build/src/${variant.name}/jniLibs")
+ // Include the hash of classes.jar as well, so that JVM-only changes don't leave every
+ // JNI library unchanged and therefore invalidate all of the JNI libraries in a consumer
+ // doing local substitution.
+ extraSource bundleJar
+ dependsOn jarTask
+ }
+ } else {
+ task("syncLibsFromDistDirFor${variant.name.capitalize()}", type: Sync) {
+ into("${project.buildDir}/moz.build/src/${variant.name}/jniLibs")
+ from("${distDir}/lib")
+ }
+ } }()
+
+ syncLibsFromDistDir.onlyIf { task ->
+ if (!hasCompileArtifacts()) {
+ // We won't have JNI libraries if we're not compiling and we're not downloading
+ // artifacts. Such a configuration is used for running lints, generating docs, etc.
+ return true
+ }
+ if (files(task.source).empty) {
+ throw new StopExecutionException("Required JNI libraries not found in ${task.source}. Have you built and packaged?")
+ }
+ return true
+ }
+
+ def syncAssetsFromDistDir = task("syncAssetsFromDistDirFor${variant.name.capitalize()}", type: Sync) {
+ into("${project.buildDir}/moz.build/src/${variant.name}/assets")
+ from("${distDir}/assets") {
+ exclude 'omni.ja'
+ }
+ }
+
+ if (hasCompileArtifacts()) {
+ // Local (read, not 'official') builds want to reflect developer changes to
+ // the omnijar sources, and (when compiling) to reflect developer changes to
+ // the native binaries. To do this, the Gradle build calls out to the
+ // moz.build system, which can be re-entrant. Official builds are driven by
+ // the moz.build system and should never be re-entrant in this way.
+ if (!mozconfig.substs.MOZILLA_OFFICIAL) {
+ syncOmnijarFromDistDir.dependsOn rootProject.machStagePackage
+ syncLibsFromDistDir.dependsOn rootProject.machStagePackage
+ syncAssetsFromDistDir.dependsOn rootProject.machStagePackage
+ }
+
+ def assetGenTask = tasks.findByName("generate${variant.name.capitalize()}Assets")
+ def jniLibFoldersTask = tasks.findByName("merge${variant.name.capitalize()}JniLibFolders")
+ if ((variant.productFlavors*.name).contains('withGeckoBinaries')) {
+ assetGenTask.dependsOn syncOmnijarFromDistDir
+ assetGenTask.dependsOn syncAssetsFromDistDir
+ jniLibFoldersTask.dependsOn syncLibsFromDistDir
+
+ android.sourceSets."${variant.name}".assets.srcDir syncOmnijarFromDistDir.destinationDir
+ android.sourceSets."${variant.name}".assets.srcDir syncAssetsFromDistDir.destinationDir
+
+ if (!mozconfig.substs.MOZ_ANDROID_FAT_AAR_ARCHITECTURES) {
+ android.sourceSets."${variant.name}".jniLibs.srcDir syncLibsFromDistDir.destinationDir
+ } else {
+ android.sourceSets."${variant.name}".jniLibs.srcDir "${topobjdir}/dist/fat-aar/output/jni"
+ }
+ }
+ }
+}
+
+ext.configureLibraryVariantWithJNIWrappers = { variant, module ->
+ // BundleLibRuntime prepares the library for further processing to be
+ // incorporated in an app. We use this version to create the JNI wrappers.
+ def jarTask = tasks["bundleLibRuntimeToJar${variant.name.capitalize()}"]
+ def bundleJar = jarTask.outputs.files.find({ it.name == 'classes.jar' })
+
+ def annotationProcessorsJarTask = project(':annotations').jar
+
+ def wrapperTask
+ if (System.env.IS_LANGUAGE_REPACK == '1') {
+ // Single-locale l10n repacks set `IS_LANGUAGE_REPACK=1` and don't
+ // really have a build environment.
+ wrapperTask = task("generateJNIWrappersFor${module}${variant.name.capitalize()}")
+ } else {
+ wrapperTask = task("generateJNIWrappersFor${module}${variant.name.capitalize()}", type: JavaExec) {
+ classpath annotationProcessorsJarTask.archivePath
+
+ // Configure the classpath at evaluation-time, not at
+ // configuration-time: see above comment.
+ doFirst {
+ classpath variant.javaCompileProvider.get().classpath
+ }
+
+ mainClass = 'org.mozilla.gecko.annotationProcessors.AnnotationProcessor'
+ args module
+ args bundleJar
+
+ workingDir "${topobjdir}/widget/android"
+
+ inputs.file(bundleJar)
+ inputs.file(annotationProcessorsJarTask.archivePath)
+ inputs.property("module", module)
+
+ outputs.file("${topobjdir}/widget/android/GeneratedJNINatives.h")
+ outputs.file("${topobjdir}/widget/android/GeneratedJNIWrappers.cpp")
+ outputs.file("${topobjdir}/widget/android/GeneratedJNIWrappers.h")
+
+ dependsOn jarTask
+ dependsOn annotationProcessorsJarTask
+ }
+ }
+
+ if (module == 'Generated') {
+ tasks["bundle${variant.name.capitalize()}Aar"].dependsOn wrapperTask
+ } else {
+ tasks["assemble${variant.name.capitalize()}"].dependsOn wrapperTask
+ }
+}