lbry-android-sdk/p4a/pythonforandroid/prerequisites.py
2022-12-02 15:15:34 -05:00

415 lines
13 KiB
Python

#!/usr/bin/env python3
import sys
import platform
import os
import subprocess
import shutil
from pythonforandroid.logger import info, warning, error
class Prerequisite(object):
name = "Default"
homebrew_formula_name = ""
mandatory = dict(linux=False, darwin=False)
installer_is_supported = dict(linux=False, darwin=False)
def is_valid(self):
if self.checker():
info(f"Prerequisite {self.name} is met")
return (True, "")
elif not self.mandatory[sys.platform]:
warning(
f"Prerequisite {self.name} is not met, but is marked as non-mandatory"
)
else:
error(f"Prerequisite {self.name} is not met")
def checker(self):
if sys.platform == "darwin":
return self.darwin_checker()
elif sys.platform == "linux":
return self.linux_checker()
else:
raise Exception("Unsupported platform")
def ask_to_install(self):
if (
os.environ.get("PYTHONFORANDROID_PREREQUISITES_INSTALL_INTERACTIVE", "1")
== "1"
):
res = input(
f"Do you want automatically install prerequisite {self.name}? [y/N] "
)
if res.lower() == "y":
return True
else:
return False
else:
info(
"Session is not interactive (usually this happens during a CI run), so let's consider it as a YES"
)
return True
def install(self):
info(f"python-for-android can automatically install prerequisite: {self.name}")
if self.ask_to_install():
if sys.platform == "darwin":
self.darwin_installer()
elif sys.platform == "linux":
self.linux_installer()
else:
raise Exception("Unsupported platform")
else:
info(
f"Skipping installation of prerequisite {self.name} as per user request"
)
def show_helper(self):
if sys.platform == "darwin":
self.darwin_helper()
elif sys.platform == "linux":
self.linux_helper()
else:
raise Exception("Unsupported platform")
def install_is_supported(self):
return self.installer_is_supported[sys.platform]
def linux_checker(self):
raise Exception(f"Unsupported prerequisite check on linux for {self.name}")
def darwin_checker(self):
raise Exception(f"Unsupported prerequisite check on macOS for {self.name}")
def linux_installer(self):
raise Exception(f"Unsupported prerequisite installer on linux for {self.name}")
def darwin_installer(self):
raise Exception(f"Unsupported prerequisite installer on macOS for {self.name}")
def darwin_helper(self):
info(f"No helper available for prerequisite: {self.name} on macOS")
def linux_helper(self):
info(f"No helper available for prerequisite: {self.name} on linux")
def _darwin_get_brew_formula_location_prefix(self, formula, installed=False):
opts = ["--installed"] if installed else []
p = subprocess.Popen(
["brew", "--prefix", formula, *opts],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
_stdout_res, _stderr_res = p.communicate()
if p.returncode != 0:
error(_stderr_res.decode("utf-8").strip())
return None
else:
return _stdout_res.decode("utf-8").strip()
def darwin_pkg_config_location(self):
warning(
f"pkg-config location is not supported on macOS for prerequisite: {self.name}"
)
return ""
def linux_pkg_config_location(self):
warning(
f"pkg-config location is not supported on linux for prerequisite: {self.name}"
)
return ""
@property
def pkg_config_location(self):
if sys.platform == "darwin":
return self.darwin_pkg_config_location()
elif sys.platform == "linux":
return self.linux_pkg_config_location()
class HomebrewPrerequisite(Prerequisite):
name = "homebrew"
mandatory = dict(linux=False, darwin=True)
installer_is_supported = dict(linux=False, darwin=False)
def darwin_checker(self):
return shutil.which("brew") is not None
def darwin_helper(self):
info(
"Installer for homebrew is not yet supported on macOS,"
"the nice news is that the installation process is easy!"
"See: https://brew.sh for further instructions."
)
class JDKPrerequisite(Prerequisite):
name = "JDK"
mandatory = dict(linux=False, darwin=True)
installer_is_supported = dict(linux=False, darwin=True)
min_supported_version = 11
def darwin_checker(self):
if "JAVA_HOME" in os.environ:
info("Found JAVA_HOME environment variable, using it")
jdk_path = os.environ["JAVA_HOME"]
else:
jdk_path = self._darwin_get_libexec_jdk_path(version=None)
return self._darwin_jdk_is_supported(jdk_path)
def _darwin_get_libexec_jdk_path(self, version=None):
version_args = []
if version is not None:
version_args = ["-v", version]
return (
subprocess.run(
["/usr/libexec/java_home", *version_args],
stdout=subprocess.PIPE,
)
.stdout.strip()
.decode()
)
def _darwin_jdk_is_supported(self, jdk_path):
if not jdk_path:
return False
javac_bin = os.path.join(jdk_path, "bin", "javac")
if not os.path.exists(javac_bin):
return False
p = subprocess.Popen(
[javac_bin, "-version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
_stdout_res, _stderr_res = p.communicate()
if p.returncode != 0:
error("Failed to run javac to check JDK version")
return False
if not _stdout_res:
_stdout_res = _stderr_res
res = _stdout_res.strip().decode()
major_version = int(res.split(" ")[-1].split(".")[0])
if major_version >= self.min_supported_version:
info(f"Found a valid JDK at {jdk_path}")
return True
else:
error(f"JDK {self.min_supported_version} or higher is required")
return False
def darwin_helper(self):
info(
"python-for-android requires a JDK 11 or higher to be installed on macOS,"
"but seems like you don't have one installed."
)
info(
"If you think that a valid JDK is already installed, please verify that "
"you have a JDK 11 or higher installed and that `/usr/libexec/java_home` "
"shows the correct path."
)
info(
"If you have multiple JDK installations, please make sure that you have "
"`JAVA_HOME` environment variable set to the correct JDK installation."
)
def darwin_installer(self):
info(
"Looking for a JDK 11 or higher installation which is not the default one ..."
)
jdk_path = self._darwin_get_libexec_jdk_path(version="11+")
if not self._darwin_jdk_is_supported(jdk_path):
info("We're unlucky, there's no JDK 11 or higher installation available")
base_url = "https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.2%2B8/"
if platform.machine() == "arm64":
filename = "OpenJDK17U-jdk_aarch64_mac_hotspot_17.0.2_8.tar.gz"
else:
filename = "OpenJDK17U-jdk_x64_mac_hotspot_17.0.2_8.tar.gz"
info(f"Downloading {filename} from {base_url}")
subprocess.check_output(
[
"curl",
"-L",
f"{base_url}{filename}",
"-o",
f"/tmp/{filename}",
]
)
user_library_java_path = os.path.expanduser(
"~/Library/Java/JavaVirtualMachines"
)
info(f"Extracting {filename} to {user_library_java_path}")
subprocess.check_output(
[
"mkdir",
"-p",
user_library_java_path,
],
)
subprocess.check_output(
["tar", "xzf", f"/tmp/{filename}", "-C", user_library_java_path],
)
jdk_path = self._darwin_get_libexec_jdk_path(version="17.0.2+8")
info(f"Setting JAVA_HOME to {jdk_path}")
os.environ["JAVA_HOME"] = jdk_path
class OpenSSLPrerequisite(Prerequisite):
name = "openssl"
homebrew_formula_name = "openssl@1.1"
mandatory = dict(linux=False, darwin=True)
installer_is_supported = dict(linux=False, darwin=True)
def darwin_checker(self):
return (
self._darwin_get_brew_formula_location_prefix(
self.homebrew_formula_name, installed=True
)
is not None
)
def darwin_pkg_config_location(self):
return os.path.join(
self._darwin_get_brew_formula_location_prefix(self.homebrew_formula_name),
"lib/pkgconfig",
)
def darwin_installer(self):
info("Installing OpenSSL ...")
subprocess.check_output(["brew", "install", self.homebrew_formula_name])
class AutoconfPrerequisite(Prerequisite):
name = "autoconf"
mandatory = dict(linux=False, darwin=True)
installer_is_supported = dict(linux=False, darwin=True)
def darwin_checker(self):
return (
self._darwin_get_brew_formula_location_prefix("autoconf", installed=True)
is not None
)
def darwin_installer(self):
info("Installing Autoconf ...")
subprocess.check_output(["brew", "install", "autoconf"])
class AutomakePrerequisite(Prerequisite):
name = "automake"
mandatory = dict(linux=False, darwin=True)
installer_is_supported = dict(linux=False, darwin=True)
def darwin_checker(self):
return (
self._darwin_get_brew_formula_location_prefix("automake", installed=True)
is not None
)
def darwin_installer(self):
info("Installing Automake ...")
subprocess.check_output(["brew", "install", "automake"])
class LibtoolPrerequisite(Prerequisite):
name = "libtool"
mandatory = dict(linux=False, darwin=True)
installer_is_supported = dict(linux=False, darwin=True)
def darwin_checker(self):
return (
self._darwin_get_brew_formula_location_prefix("libtool", installed=True)
is not None
)
def darwin_installer(self):
info("Installing Libtool ...")
subprocess.check_output(["brew", "install", "libtool"])
class PkgConfigPrerequisite(Prerequisite):
name = "pkg-config"
mandatory = dict(linux=False, darwin=True)
installer_is_supported = dict(linux=False, darwin=True)
def darwin_checker(self):
return (
self._darwin_get_brew_formula_location_prefix("pkg-config", installed=True)
is not None
)
def darwin_installer(self):
info("Installing Pkg-Config ...")
subprocess.check_output(["brew", "install", "pkg-config"])
class CmakePrerequisite(Prerequisite):
name = "cmake"
mandatory = dict(linux=False, darwin=True)
installer_is_supported = dict(linux=False, darwin=True)
def darwin_checker(self):
return (
self._darwin_get_brew_formula_location_prefix("cmake", installed=True)
is not None
)
def darwin_installer(self):
info("Installing cmake ...")
subprocess.check_output(["brew", "install", "cmake"])
def get_required_prerequisites(platform="linux"):
DEFAULT_PREREQUISITES = dict(
darwin=[
HomebrewPrerequisite(),
AutoconfPrerequisite(),
AutomakePrerequisite(),
LibtoolPrerequisite(),
PkgConfigPrerequisite(),
CmakePrerequisite(),
OpenSSLPrerequisite(),
JDKPrerequisite(),
],
linux=[],
all_platforms=[],
)
return DEFAULT_PREREQUISITES["all_platforms"] + DEFAULT_PREREQUISITES[platform]
def check_and_install_default_prerequisites():
prerequisites_not_met = []
warning(
"prerequisites.py is experimental and does not support all prerequisites yet."
)
warning("Please report any issues to the python-for-android issue tracker.")
# Phase 1: Check if all prerequisites are met and add the ones
# which are not to `prerequisites_not_met`
for prerequisite in get_required_prerequisites(sys.platform):
if not prerequisite.is_valid():
prerequisites_not_met.append(prerequisite)
# Phase 2: Setup/Install all prerequisites that are not met
# (where possible), otherwise show an helper.
for prerequisite in prerequisites_not_met:
prerequisite.show_helper()
if prerequisite.install_is_supported():
prerequisite.install()
if __name__ == "__main__":
check_and_install_default_prerequisites()