Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduces pythonforandroid/prerequisites.py (Experimental). This allows a more granular check and install process for dependencies on both CI jobs and users installation. #2591

Merged
merged 2 commits into from
May 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on: ['push', 'pull_request']
env:
APK_ARTIFACT_FILENAME: bdist_unit_tests_app-debug-1.1-.apk
AAB_ARTIFACT_FILENAME: bdist_unit_tests_app-release-1.1-.aab
PYTHONFORANDROID_PREREQUISITES_INSTALL_INTERACTIVE: 0

jobs:

Expand Down Expand Up @@ -107,6 +108,16 @@ jobs:
steps:
- name: Checkout python-for-android
uses: actions/checkout@v2
- name: Install python-for-android
run: |
source ci/osx_ci.sh
arm64_set_path_and_python_version 3.9.7
python3 -m pip install -e .
- name: Install prerequisites via pythonforandroid/prerequisites.py (Experimental)
run: |
source ci/osx_ci.sh
arm64_set_path_and_python_version 3.9.7
python3 pythonforandroid/prerequisites.py
- name: Install dependencies
run: |
source ci/osx_ci.sh
Expand Down Expand Up @@ -182,6 +193,16 @@ jobs:
steps:
- name: Checkout python-for-android
uses: actions/checkout@v2
- name: Install python-for-android
run: |
source ci/osx_ci.sh
arm64_set_path_and_python_version 3.9.7
python3 -m pip install -e .
- name: Install prerequisites via pythonforandroid/prerequisites.py (Experimental)
run: |
source ci/osx_ci.sh
arm64_set_path_and_python_version 3.9.7
python3 pythonforandroid/prerequisites.py
- name: Install dependencies
run: |
source ci/osx_ci.sh
Expand Down Expand Up @@ -258,6 +279,16 @@ jobs:
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Install python-for-android
run: |
source ci/osx_ci.sh
arm64_set_path_and_python_version 3.9.7
python3 -m pip install -e .
- name: Install prerequisites via pythonforandroid/prerequisites.py (Experimental)
run: |
source ci/osx_ci.sh
arm64_set_path_and_python_version 3.9.7
python3 pythonforandroid/prerequisites.py
- name: Install dependencies
run: |
source ci/osx_ci.sh
Expand Down
12 changes: 1 addition & 11 deletions ci/makefiles/osx.mk
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,11 @@
# The following variable/s can be override when running the file
ANDROID_HOME ?= $(HOME)/.android

all: install_java upgrade_cython install_android_ndk_sdk install_p4a

install_java:
brew tap adoptopenjdk/openjdk
brew install --cask adoptopenjdk13
/usr/libexec/java_home -V
all: upgrade_cython install_android_ndk_sdk

upgrade_cython:
pip3 install --upgrade Cython

install_android_ndk_sdk:
mkdir -p $(ANDROID_HOME)
make -f ci/makefiles/android.mk JAVA_HOME=`/usr/libexec/java_home -v 13`

install_p4a:
# check python version and install p4a
python3 --version
pip3 install -e .
248 changes: 248 additions & 0 deletions pythonforandroid/prerequisites.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
#!/usr/bin/env python3

import sys
import platform
import os
import subprocess
from pythonforandroid.logger import info, warning, error


class Prerequisite(object):
name = "Default"
mandatory = True
darwin_installer_is_supported = False
linux_installer_is_supported = False

def is_valid(self):
if self.checker():
info(f"Prerequisite {self.name} is met")
return (True, "")
elif not self.mandatory:
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):
if sys.platform == "darwin":
return self.darwin_installer_is_supported
elif sys.platform == "linux":
return self.linux_installer_is_supported

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")


class JDKPrerequisite(Prerequisite):
name = "JDK"
mandatory = True
darwin_installer_is_supported = 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


def check_and_install_default_prerequisites():
DEFAULT_PREREQUISITES = dict(darwin=[JDKPrerequisite()], linux=[], all_platforms=[])

required_prerequisites = (
DEFAULT_PREREQUISITES["all_platforms"] + DEFAULT_PREREQUISITES[sys.platform]
)

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 required_prerequisites:
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()
2 changes: 2 additions & 0 deletions pythonforandroid/toolchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
RECOMMENDED_NDK_API, RECOMMENDED_TARGET_API, print_recommendations)
from pythonforandroid.util import BuildInterruptingException, load_source
from pythonforandroid.entrypoints import main
from pythonforandroid.prerequisites import check_and_install_default_prerequisites


def check_python_dependencies():
Expand Down Expand Up @@ -66,6 +67,7 @@ def check_python_dependencies():
exit(1)


check_and_install_default_prerequisites()
check_python_dependencies()


Expand Down