Source code
Revision control
Copy as Markdown
Other Tools
# 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,
import errno
import json
import os
import stat
import subprocess
import sys
import time
from pathlib import Path
from typing import Optional, Union
import requests
from tqdm import tqdm
# We need the NDK version in multiple different places, and it's inconvenient
# to pass down the NDK version to all relevant places, so we have this global
# variable.
from mozboot.bootstrap import MOZCONFIG_SUGGESTION_TEMPLATE
NDK_VERSION = "r27c"
CMDLINE_TOOLS_VERSION_STRING = "12.0"
CMDLINE_TOOLS_VERSION = "11076708"
BUNDLETOOL_VERSION = "1.17.0"
# We expect the emulator AVD definitions to be platform agnostic
LINUX_X86_64_ANDROID_AVD = "linux64-android-avd-x86_64-repack"
LINUX_ARM_ANDROID_AVD = "linux64-android-avd-arm-repack"
MACOS_X86_64_ANDROID_AVD = "linux64-android-avd-x86_64-repack"
MACOS_ARM_ANDROID_AVD = "linux64-android-avd-arm-repack"
MACOS_ARM64_ANDROID_AVD = "linux64-android-avd-arm64-repack"
WINDOWS_X86_64_ANDROID_AVD = "linux64-android-avd-x86_64-repack"
WINDOWS_ARM_ANDROID_AVD = "linux64-android-avd-arm-repack"
AVD_MANIFEST_X86_64 = Path(__file__).resolve().parent / "android-avds/x86_64.json"
AVD_MANIFEST_ARM = Path(__file__).resolve().parent / "android-avds/arm.json"
AVD_MANIFEST_ARM64 = Path(__file__).resolve().parent / "android-avds/arm64.json"
JAVA_VERSION_MAJOR = "17"
JAVA_VERSION_MINOR = "0.13"
JAVA_VERSION_PATCH = "11"
ANDROID_NDK_EXISTS = """
Looks like you have the correct version of the Android NDK installed at:
%s
"""
ANDROID_SDK_EXISTS = """
Looks like you have the Android SDK installed at:
%s
We will install all required Android packages.
"""
ANDROID_SDK_TOO_OLD = """
Looks like you have an outdated Android SDK installed at:
%s
I can't update outdated Android SDKs to have the required 'sdkmanager'
tool. Move it out of the way (or remove it entirely) and then run
bootstrap again.
"""
INSTALLING_ANDROID_PACKAGES = """
We are now installing the following Android packages:
%s
You may be prompted to agree to the Android license. You may see some of
output as packages are downloaded and installed.
"""
MOBILE_ANDROID_MOZCONFIG_TEMPLATE = """
# Build GeckoView/Firefox for Android:
ac_add_options --enable-project=mobile/android
# Targeting the following architecture.
# For regular phones, no --target is needed.
# For x86 emulators (and x86 devices, which are uncommon):
# ac_add_options --target=i686
# For newer phones or Apple silicon
# ac_add_options --target=aarch64
# For x86_64 emulators (and x86_64 devices, which are even less common):
# ac_add_options --target=x86_64
{extra_lines}
"""
MOBILE_ANDROID_ARTIFACT_MODE_MOZCONFIG_TEMPLATE = """
# Build GeckoView/Firefox for Android Artifact Mode:
ac_add_options --enable-project=mobile/android
ac_add_options --enable-artifact-builds
{extra_lines}
# Write build artifacts to:
mk_add_options MOZ_OBJDIR=./objdir-frontend
"""
class GetNdkVersionError(Exception):
pass
def install_mobile_android_sdk_or_ndk(url, path: Path):
"""
Fetch an Android SDK or NDK from |url| and unpack it into the given |path|.
We use, and 'requests' respects, https. We could also include SHAs for a
small improvement in the integrity guarantee we give. But this script is
bootstrapped over https anyway, so it's a really minor improvement.
We keep a cache of the downloaded artifacts, writing into |path|/mozboot.
We don't yet clean the cache; it's better to waste some disk space and
not require a long re-download than to wipe the cache prematurely.
"""
download_path = path / "mozboot"
try:
download_path.mkdir(parents=True)
except OSError as e:
if e.errno == errno.EEXIST and download_path.is_dir():
pass
else:
raise
file_name = url.split("/")[-1]
download_file_path = download_path / file_name
download(url, download_file_path)
if file_name.endswith(".tar.gz") or file_name.endswith(".tgz"):
cmd = ["tar", "zxf", str(download_file_path)]
elif file_name.endswith(".tar.bz2"):
cmd = ["tar", "jxf", str(download_file_path)]
elif file_name.endswith(".zip"):
cmd = ["unzip", "-q", str(download_file_path)]
elif file_name.endswith(".bin"):
# Execute the .bin file, which unpacks the content.
mode = os.stat(path).st_mode
download_file_path.chmod(mode | stat.S_IXUSR)
cmd = [str(download_file_path)]
else:
raise NotImplementedError(f"Don't know how to unpack file: {file_name}")
print(f"Unpacking {download_file_path}...")
with open(os.devnull, "w") as stdout:
# These unpack commands produce a ton of output; ignore it. The
# .bin files are 7z archives; there's no command line flag to quiet
# output, so we use this hammer.
subprocess.check_call(cmd, stdout=stdout, cwd=str(path))
print(f"Unpacking {download_file_path}... DONE")
# Now delete the archive
download_file_path.unlink()
def download(
url,
download_file_path: Path,
):
with requests.Session() as session:
request = session.head(url, allow_redirects=True)
request.raise_for_status()
remote_file_size = int(request.headers["content-length"])
if download_file_path.is_file():
local_file_size = download_file_path.stat().st_size
if local_file_size == remote_file_size:
print(
f"{download_file_path.name} already downloaded. Skipping download..."
)
else:
print(f"Partial download detected. Resuming download of {url}...")
download_internal(
download_file_path,
session,
url,
remote_file_size,
local_file_size,
)
else:
print(f"Downloading {url}...")
download_internal(download_file_path, session, url, remote_file_size)
def download_internal(
download_file_path: Path,
session,
url,
remote_file_size,
resume_from_byte_pos: int = None,
):
"""
Handles both a fresh SDK/NDK download, as well as resuming a partial one
"""
# "ab" will behave same as "wb" if file does not exist
with open(download_file_path, "ab") as file:
# 64 KB/s should be fine on even the slowest internet connections
chunk_size = 1024 * 64
resume_header = (
{"Range": f"bytes={resume_from_byte_pos}-"}
if resume_from_byte_pos
else None
)
request = session.get(
url, stream=True, allow_redirects=True, headers=resume_header
)
with tqdm(
total=int(remote_file_size),
unit="B",
unit_scale=True,
unit_divisor=1024,
desc=download_file_path.name,
initial=resume_from_byte_pos if resume_from_byte_pos else 0,
) as progress_bar:
for chunk in request.iter_content(chunk_size):
file.write(chunk)
progress_bar.update(len(chunk))
def get_ndk_version(ndk_path: Union[str, Path]):
"""Given the path to the NDK, return the version as a 3-tuple of (major,
minor, human).
"""
ndk_path = Path(ndk_path)
with open(ndk_path / "source.properties", "r") as f:
revision = [line for line in f if line.startswith("Pkg.Revision")]
if not revision:
raise GetNdkVersionError(
"Cannot determine NDK version from source.properties"
)
if len(revision) != 1:
raise GetNdkVersionError("Too many Pkg.Revision lines in source.properties")
(_, version) = revision[0].split("=")
if not version:
raise GetNdkVersionError(
"Unexpected Pkg.Revision line in source.properties"
)
(major, minor, revision) = version.strip().split(".")
if not major or not minor:
raise GetNdkVersionError("Unexpected NDK version string: " + version)
# source.properties contains a $MAJOR.$MINOR.$PATCH revision number,
# but the more common nomenclature that Google uses is alphanumeric
# version strings like "r20" or "r19c". Convert the source.properties
# notation into an alphanumeric string.
int_minor = int(minor)
alphas = "abcdefghijklmnop"
ascii_minor = alphas[int_minor] if int_minor > 0 else ""
human = "r%s%s" % (major, ascii_minor)
return (major, minor, human)
def get_paths(os_name):
mozbuild_path = Path(
os.environ.get("MOZBUILD_STATE_PATH", Path("~/.mozbuild").expanduser())
)
sdk_path = Path(
os.environ.get("ANDROID_SDK_HOME", mozbuild_path / f"android-sdk-{os_name}"),
)
ndk_path = Path(
os.environ.get(
"ANDROID_NDK_HOME", mozbuild_path / f"android-ndk-{NDK_VERSION}"
),
)
avd_home_path = Path(
os.environ.get("ANDROID_AVD_HOME", mozbuild_path / "android-device" / "avd")
)
return mozbuild_path, sdk_path, ndk_path, avd_home_path
def sdkmanager_tool(sdk_path: Path):
# sys.platform is win32 even if Python/Win64.
sdkmanager = "sdkmanager.bat" if sys.platform.startswith("win") else "sdkmanager"
return (
sdk_path / "cmdline-tools" / CMDLINE_TOOLS_VERSION_STRING / "bin" / sdkmanager
)
def avdmanager_tool(sdk_path: Path):
# sys.platform is win32 even if Python/Win64.
sdkmanager = "avdmanager.bat" if sys.platform.startswith("win") else "avdmanager"
return (
sdk_path / "cmdline-tools" / CMDLINE_TOOLS_VERSION_STRING / "bin" / sdkmanager
)
def adb_tool(sdk_path: Path):
adb = "adb.bat" if sys.platform.startswith("win") else "adb"
return sdk_path / "platform-tools" / adb
def emulator_tool(sdk_path: Path):
emulator = "emulator.bat" if sys.platform.startswith("win") else "emulator"
return sdk_path / "emulator" / emulator
def ensure_android(
os_name,
os_arch,
artifact_mode=False,
ndk_only=False,
system_images_only=False,
emulator_only=False,
avd_manifest_path: Optional[Path] = None,
prewarm_avd=False,
no_interactive=False,
list_packages=False,
):
"""
Ensure the Android SDK (and NDK, if `artifact_mode` is falsy) are
installed. If not, fetch and unpack the SDK and/or NDK from the
given URLs. Ensure the required Android SDK packages are
installed.
`os_name` can be 'linux', 'macosx' or 'windows'.
"""
# The user may have an external Android SDK (in which case we
# save them a lengthy download), or they may have already
# completed the download. We unpack to
# ~/.mozbuild/{android-sdk-$OS_NAME, android-ndk-$VER}.
mozbuild_path, sdk_path, ndk_path, avd_home_path = get_paths(os_name)
if os_name == "macosx":
os_tag = "mac"
elif os_name == "windows":
os_tag = "win"
else:
os_tag = os_name
sdk_url = "https://dl.google.com/android/repository/commandlinetools-{0}-{1}_latest.zip".format( # NOQA: E501
os_tag, CMDLINE_TOOLS_VERSION
)
ndk_url = android_ndk_url(os_name)
bundletool_url = "https://github.com/google/bundletool/releases/download/{v}/bundletool-all-{v}.jar".format( # NOQA: E501
v=BUNDLETOOL_VERSION
)
ensure_android_sdk_and_ndk(
mozbuild_path,
os_name,
sdk_path=sdk_path,
sdk_url=sdk_url,
ndk_path=ndk_path,
ndk_url=ndk_url,
bundletool_url=bundletool_url,
artifact_mode=artifact_mode,
ndk_only=ndk_only,
emulator_only=emulator_only,
)
if ndk_only:
return
avd_manifest = None
if avd_manifest_path is not None:
with open(avd_manifest_path) as f:
avd_manifest = json.load(f)
# Some AVDs cannot be prewarmed in CI because they cannot run on linux64
# (like the arm64 AVD).
if "emulator_prewarm" in avd_manifest:
prewarm_avd = prewarm_avd and avd_manifest["emulator_prewarm"]
# We expect the |sdkmanager| tool to be at
# ~/.mozbuild/android-sdk-$OS_NAME/tools/cmdline-tools/$CMDLINE_TOOLS_VERSION_STRING/bin/sdkmanager. # NOQA: E501
ensure_android_packages(
os_name,
os_arch,
sdkmanager_tool=sdkmanager_tool(sdk_path),
emulator_only=emulator_only,
system_images_only=system_images_only,
avd_manifest=avd_manifest,
no_interactive=no_interactive,
list_packages=list_packages,
)
if emulator_only or system_images_only:
return
ensure_android_avd(
avdmanager_tool=avdmanager_tool(sdk_path),
adb_tool=adb_tool(sdk_path),
emulator_tool=emulator_tool(sdk_path),
avd_home_path=avd_home_path,
sdk_path=sdk_path,
no_interactive=no_interactive,
avd_manifest=avd_manifest,
prewarm_avd=prewarm_avd,
)
def ensure_android_sdk_and_ndk(
mozbuild_path: Path,
os_name,
sdk_path: Path,
sdk_url,
ndk_path: Path,
ndk_url,
bundletool_url,
artifact_mode,
ndk_only,
emulator_only,
):
"""
Ensure the Android SDK and NDK are found at the given paths. If not, fetch
and unpack the SDK and/or NDK from the given URLs into
|mozbuild_path/{android-sdk-$OS_NAME,android-ndk-$VER}|.
"""
# It's not particularly bad to overwrite the NDK toolchain, but it does take
# a while to unpack, so let's avoid the disk activity if possible. The SDK
# may prompt about licensing, so we do this first.
# Check for Android NDK only if we are not in artifact mode.
if not artifact_mode and not emulator_only:
install_ndk = True
if ndk_path.is_dir():
try:
_, _, human = get_ndk_version(ndk_path)
if human == NDK_VERSION:
print(ANDROID_NDK_EXISTS % ndk_path)
install_ndk = False
except GetNdkVersionError:
pass # Just do the install.
if install_ndk:
# The NDK archive unpacks into a top-level android-ndk-$VER directory.
install_mobile_android_sdk_or_ndk(ndk_url, mozbuild_path)
if ndk_only:
return
# We don't want to blindly overwrite, since we use the
# |sdkmanager| tool to install additional parts of the Android
# toolchain. If we overwrite, we lose whatever Android packages
# the user may have already installed.
if sdkmanager_tool(sdk_path).is_file():
print(ANDROID_SDK_EXISTS % sdk_path)
elif sdk_path.is_dir():
raise NotImplementedError(ANDROID_SDK_TOO_OLD % sdk_path)
else:
# The SDK archive used to include a top-level
# android-sdk-$OS_NAME directory; it no longer does so. We
# preserve the old convention to smooth detecting existing SDK
# installations.
cmdline_tools_path = mozbuild_path / f"android-sdk-{os_name}" / "cmdline-tools"
install_mobile_android_sdk_or_ndk(sdk_url, cmdline_tools_path)
# The tools package *really* wants to be in
# <sdk>/cmdline-tools/$CMDLINE_TOOLS_VERSION_STRING
(cmdline_tools_path / "cmdline-tools").rename(
cmdline_tools_path / CMDLINE_TOOLS_VERSION_STRING
)
download(bundletool_url, mozbuild_path / "bundletool.jar")
def get_packages_to_install(packages_file_content, avd_manifest):
packages = []
packages += map(lambda package: package.strip(), packages_file_content)
if avd_manifest is not None:
packages += [avd_manifest["emulator_package"]]
return packages
def ensure_android_avd(
avdmanager_tool: Path,
adb_tool: Path,
emulator_tool: Path,
avd_home_path: Path,
sdk_path: Path,
no_interactive=False,
avd_manifest=None,
prewarm_avd=False,
):
"""
Use the given sdkmanager tool (like 'sdkmanager') to install required
Android packages.
"""
if avd_manifest is None:
return
avd_home_path.mkdir(parents=True, exist_ok=True)
# The AVD needs this folder to boot, so make sure it exists here.
(sdk_path / "platforms").mkdir(parents=True, exist_ok=True)
avd_name = avd_manifest["emulator_avd_name"]
args = [
str(avdmanager_tool),
"--verbose",
"create",
"avd",
"--force",
"--name",
avd_name,
"--package",
avd_manifest["emulator_package"],
]
if not no_interactive:
subprocess.check_call(args)
return
# Flush outputs before running sdkmanager.
sys.stdout.flush()
env = os.environ.copy()
env["ANDROID_AVD_HOME"] = str(avd_home_path)
proc = subprocess.Popen(args, stdin=subprocess.PIPE, env=env)
proc.communicate("no\n".encode("UTF-8"))
retcode = proc.poll()
if retcode:
cmd = args[0]
e = subprocess.CalledProcessError(retcode, cmd)
raise e
avd_path = avd_home_path / (str(avd_name) + ".avd")
config_file_name = avd_path / "config.ini"
print(f"Writing config at {config_file_name}")
if config_file_name.is_file():
with open(config_file_name, "a") as config:
for key, value in avd_manifest["emulator_extra_config"].items():
config.write("%s=%s\n" % (key, value))
else:
raise NotImplementedError(
f"Could not find config file at {config_file_name}, something went wrong"
)
if prewarm_avd:
run_prewarm_avd(adb_tool, emulator_tool, env, avd_name, avd_manifest)
# When running in headless mode, the emulator does not run the cleanup
# step, and thus doesn't delete lock files. On some platforms, left-over
# lock files can cause the emulator to not start, so we remove them here.
for lock_file in ["hardware-qemu.ini.lock", "multiinstance.lock"]:
lock_file_path = avd_path / lock_file
try:
lock_file_path.unlink()
print(f"Removed lock file {lock_file_path}")
except OSError:
# The lock file is not there, nothing to do.
pass
def run_prewarm_avd(
adb_tool: Path,
emulator_tool: Path,
env,
avd_name,
avd_manifest,
):
"""
Ensures the emulator is fully booted to save time on future iterations.
"""
args = [str(emulator_tool), "-avd", avd_name] + avd_manifest["emulator_extra_args"]
# Flush outputs before running emulator.
sys.stdout.flush()
proc = subprocess.Popen(args, env=env)
booted = False
for i in range(100):
boot_completed_cmd = [str(adb_tool), "shell", "getprop", "sys.boot_completed"]
completed_proc = subprocess.Popen(
boot_completed_cmd, env=env, stdout=subprocess.PIPE
)
try:
out, err = completed_proc.communicate(timeout=30)
boot_completed = out.decode("UTF-8").strip()
print("sys.boot_completed = %s" % boot_completed)
time.sleep(30)
if boot_completed == "1":
booted = True
break
except subprocess.TimeoutExpired:
# Sometimes the adb command hangs, that's ok
print("sys.boot_completed = Timeout")
if not booted:
raise NotImplementedError("Could not prewarm emulator")
# Wait until the emulator completely shuts down
subprocess.Popen([str(adb_tool), "emu", "kill"], env=env).wait()
proc.wait()
def ensure_android_packages(
os_name,
os_arch,
sdkmanager_tool: Path,
emulator_only=False,
system_images_only=False,
avd_manifest=None,
no_interactive=False,
list_packages=False,
):
"""
Use the given sdkmanager tool (like 'sdkmanager') to install required
Android packages.
"""
# This tries to install all the required Android packages. The user
# may be prompted to agree to the Android license.
if system_images_only:
packages_file_name = "android-system-images-packages.txt"
elif emulator_only:
packages_file_name = "android-emulator-packages.txt"
else:
packages_file_name = "android-packages.txt"
packages_file_path = (Path(__file__).parent / packages_file_name).resolve()
with open(packages_file_path) as packages_file:
packages_file_content = packages_file.readlines()
packages = get_packages_to_install(packages_file_content, avd_manifest)
print(INSTALLING_ANDROID_PACKAGES % "\n".join(packages))
args = [str(sdkmanager_tool)]
if os_name == "macosx" and os_arch == "arm64":
# Support for Apple Silicon is still in nightly
args.append("--channel=3")
args.extend(packages)
# sdkmanager needs JAVA_HOME
java_bin_path = ensure_java(os_name, os_arch)
env = os.environ.copy()
env["JAVA_HOME"] = str(java_bin_path.parent)
if not no_interactive:
subprocess.check_call(args, env=env)
return
# Flush outputs before running sdkmanager.
sys.stdout.flush()
sys.stderr.flush()
# Emulate yes. For a discussion of passing input to check_output,
yes = "\n".join(["y"] * 100).encode("UTF-8")
proc = subprocess.Popen(args, stdin=subprocess.PIPE, env=env)
proc.communicate(yes)
retcode = proc.poll()
if retcode:
cmd = args[0]
e = subprocess.CalledProcessError(retcode, cmd)
raise e
if list_packages:
subprocess.check_call([str(sdkmanager_tool), "--list"])
def generate_mozconfig(os_name, artifact_mode=False):
moz_state_dir, sdk_path, ndk_path, avd_home_path = get_paths(os_name)
extra_lines = []
if extra_lines:
extra_lines.append("")
if artifact_mode:
template = MOBILE_ANDROID_ARTIFACT_MODE_MOZCONFIG_TEMPLATE
else:
template = MOBILE_ANDROID_MOZCONFIG_TEMPLATE
kwargs = dict(
sdk_path=sdk_path,
ndk_path=ndk_path,
avd_home_path=avd_home_path,
moz_state_dir=moz_state_dir,
extra_lines="\n".join(extra_lines),
)
return template.format(**kwargs).strip()
def android_ndk_url(os_name, ver=NDK_VERSION):
# Produce a URL like
if os_name == "macosx":
# |mach bootstrap| uses 'macosx', but Google uses 'darwin'.
os_name = "darwin"
return "%s-%s-%s.zip" % (base_url, ver, os_name)
def main(argv):
import optparse # No argparse, which is new in Python 2.7.
import platform
parser = optparse.OptionParser()
parser.add_option(
"-a",
"--artifact-mode",
dest="artifact_mode",
action="store_true",
help="If true, install only the Android SDK (and not the Android NDK).",
)
parser.add_option(
"--jdk-only",
dest="jdk_only",
action="store_true",
help="If true, install only the Java JDK.",
)
parser.add_option(
"--ndk-only",
dest="ndk_only",
action="store_true",
help="If true, install only the Android NDK (and not the Android SDK).",
)
parser.add_option(
"--system-images-only",
dest="system_images_only",
action="store_true",
help="If true, install only the system images for the AVDs.",
)
parser.add_option(
"--no-interactive",
dest="no_interactive",
action="store_true",
help="Accept the Android SDK licenses without user interaction.",
)
parser.add_option(
"--emulator-only",
dest="emulator_only",
action="store_true",
help="If true, install only the Android emulator (and not the SDK or NDK).",
)
parser.add_option(
"--avd-manifest",
dest="avd_manifest_path",
help="If present, generate AVD from the manifest pointed by this argument.",
)
parser.add_option(
"--prewarm-avd",
dest="prewarm_avd",
action="store_true",
help="If true, boot the AVD and wait until completed to speed up subsequent boots.",
)
parser.add_option(
"--list-packages",
dest="list_packages",
action="store_true",
help="If true, list installed packages.",
)
options, _ = parser.parse_args(argv)
if options.artifact_mode and options.ndk_only:
raise NotImplementedError("Use no options to install the NDK and the SDK.")
if options.artifact_mode and options.emulator_only:
raise NotImplementedError("Use no options to install the SDK and emulators.")
os_name = None
if platform.system() == "Darwin":
os_name = "macosx"
elif platform.system() == "Linux":
os_name = "linux"
elif platform.system() == "Windows":
os_name = "windows"
else:
raise NotImplementedError(
"We don't support bootstrapping the Android SDK (or Android "
"NDK) on {0} yet!".format(platform.system())
)
os_arch = platform.machine()
if options.jdk_only:
ensure_java(os_name, os_arch)
return 0
avd_manifest_path = (
Path(options.avd_manifest_path) if options.avd_manifest_path else None
)
ensure_android(
os_name,
os_arch,
artifact_mode=options.artifact_mode,
ndk_only=options.ndk_only,
system_images_only=options.system_images_only,
emulator_only=options.emulator_only,
avd_manifest_path=avd_manifest_path,
prewarm_avd=options.prewarm_avd,
no_interactive=options.no_interactive,
list_packages=options.list_packages,
)
mozconfig = generate_mozconfig(os_name, options.artifact_mode)
# |./mach bootstrap| automatically creates a mozconfig file for you if it doesn't
# exist. However, here, we don't know where the "topsrcdir" is, and it's not worth
# pulling in CommandContext (and its dependencies) to find out.
# So, instead, we'll politely ask users to create (or update) the file themselves.
suggestion = MOZCONFIG_SUGGESTION_TEMPLATE % ("$topsrcdir/mozconfig", mozconfig)
print("\n" + suggestion)
return 0
def ensure_java(os_name, os_arch):
mozbuild_path, _, _, _ = get_paths(os_name)
if os_name == "macosx":
os_tag = "mac"
else:
os_tag = os_name
if os_arch == "x86_64":
arch = "x64"
elif os_arch == "arm64":
arch = "aarch64"
else:
arch = os_arch
ext = "zip" if os_name == "windows" else "tar.gz"
java_path = java_bin_path(os_name, mozbuild_path)
if not java_path:
raise NotImplementedError(f"Could not bootstrap java for {os_name}.")
if not java_path.exists():
# download/jdk-17.0.12%2B7/OpenJDK17U-jdk_x64_linux_hotspot_17.0.13_11.tar.gz
java_url = (
"download/jdk-{major}.{minor}%2B{patch}/"
"OpenJDK{major}U-jdk_{arch}_{os}_hotspot_{major}.{minor}_{patch}.{ext}"
).format(
major=JAVA_VERSION_MAJOR,
minor=JAVA_VERSION_MINOR,
patch=JAVA_VERSION_PATCH,
os=os_tag,
arch=arch,
ext=ext,
)
install_mobile_android_sdk_or_ndk(java_url, mozbuild_path / "jdk")
return java_path
def java_bin_path(os_name, toolchain_path: Path):
# Like jdk-17.0.13+11
jdk_folder = "jdk-{major}.{minor}+{patch}".format(
major=JAVA_VERSION_MAJOR, minor=JAVA_VERSION_MINOR, patch=JAVA_VERSION_PATCH
)
java_path = toolchain_path / "jdk" / jdk_folder
if os_name == "macosx":
return java_path / "Contents" / "Home" / "bin"
elif os_name == "linux":
return java_path / "bin"
elif os_name == "windows":
return java_path / "bin"
else:
return None
def locate_java_bin_path(host_kernel, toolchain_path: Union[str, Path]):
if host_kernel == "WINNT":
os_name = "windows"
elif host_kernel == "Darwin":
os_name = "macosx"
elif host_kernel == "Linux":
os_name = "linux"
else:
# Default to Linux
os_name = "linux"
path = java_bin_path(os_name, Path(toolchain_path))
if not path.is_dir():
raise JavaLocationFailedException(
f"Could not locate Java at {path}, please run "
"./mach bootstrap --no-system-changes"
)
return str(path)
class JavaLocationFailedException(Exception):
pass
if __name__ == "__main__":
sys.exit(main(sys.argv))