416 lines
13 KiB
Python
416 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()
|