# 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 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 = "r23c" CMDLINE_TOOLS_VERSION_STRING = "9.0" CMDLINE_TOOLS_VERSION = "9477386" BUNDLETOOL_VERSION = "1.14.1" # 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.7" JAVA_VERSION_PATCH = "7" 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 # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range#directives 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 # /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, # see https://stackoverflow.com/q/10103551. 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 # 'https://dl.google.com/android/repository/android-ndk-$VER-linux.zip base_url = "https://dl.google.com/android/repository/android-ndk" 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(): # e.g. https://github.com/adoptium/temurin17-binaries/releases/ # download/jdk-17.0.7%2B7/OpenJDK17U-jre_x64_linux_hotspot_17.0.7_7.tar.gz java_url = ( "https://github.com/adoptium/temurin{major}-binaries/releases/" "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.7+7 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))