Skip to content

Commit a2dc732

Browse files
committed
Introduces pythonforandroid/prerequisites.py (Experimental). This allows a more granular check and install process for dependencies on both CI jobs and users installation.
1 parent f45408d commit a2dc732

File tree

3 files changed

+235
-6
lines changed

3 files changed

+235
-6
lines changed

.github/workflows/push.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ on: ['push', 'pull_request']
55
env:
66
APK_ARTIFACT_FILENAME: bdist_unit_tests_app-debug-1.1-.apk
77
AAB_ARTIFACT_FILENAME: bdist_unit_tests_app-release-1.1-.aab
8+
PYTHONFORANDROID_PREREQUISITES_INSTALL_INTERACTIVE: 0
89

910
jobs:
1011

@@ -113,6 +114,8 @@ jobs:
113114
arm64_set_path_and_python_version 3.9.7
114115
brew install autoconf automake libtool openssl pkg-config
115116
make --file ci/makefiles/osx.mk
117+
- name: Install prerequisites via pythonforandroid/prerequisites.py (Experimental)
118+
run: python3 pythonforandroid/prerequisites.py
116119
- name: Build multi-arch apk Python 3 (armeabi-v7a, arm64-v8a, x86_64, x86)
117120
run: |
118121
source ci/osx_ci.sh
@@ -188,6 +191,8 @@ jobs:
188191
arm64_set_path_and_python_version 3.9.7
189192
brew install autoconf automake libtool openssl pkg-config
190193
make --file ci/makefiles/osx.mk
194+
- name: Install prerequisites via pythonforandroid/prerequisites.py (Experimental)
195+
run: python3 pythonforandroid/prerequisites.py
191196
- name: Build multi-arch aab Python 3 (armeabi-v7a, arm64-v8a, x86_64, x86)
192197
run: |
193198
source ci/osx_ci.sh
@@ -258,6 +263,8 @@ jobs:
258263
uses: actions/checkout@v2
259264
with:
260265
fetch-depth: 0
266+
- name: Install prerequisites via pythonforandroid/prerequisites.py (Experimental)
267+
run: python3 pythonforandroid/prerequisites.py
261268
- name: Install dependencies
262269
run: |
263270
source ci/osx_ci.sh

ci/makefiles/osx.mk

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,7 @@
33
# The following variable/s can be override when running the file
44
ANDROID_HOME ?= $(HOME)/.android
55

6-
all: install_java upgrade_cython install_android_ndk_sdk install_p4a
7-
8-
install_java:
9-
brew tap adoptopenjdk/openjdk
10-
brew install --cask adoptopenjdk13
11-
/usr/libexec/java_home -V
6+
all: upgrade_cython install_android_ndk_sdk install_p4a
127

138
upgrade_cython:
149
pip3 install --upgrade Cython

pythonforandroid/prerequisites.py

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
#!/usr/bin/env python3
2+
3+
import sys
4+
import platform
5+
import os
6+
from subprocess import CalledProcessError, check_output
7+
from pythonforandroid.logger import info, warning, error
8+
9+
10+
class Prerequisite(object):
11+
name = "Default"
12+
mandatory = True
13+
darwin_installer_is_supported = False
14+
linux_installer_is_supported = False
15+
16+
def is_valid(self):
17+
if self.checker():
18+
info(f"Prerequisite {self.name} is met")
19+
return (True, "")
20+
elif not self.mandatory:
21+
warning(
22+
f"Prerequisite {self.name} is not met, but is marked as non-mandatory"
23+
)
24+
else:
25+
error(f"Prerequisite {self.name} is not met")
26+
27+
def checker(self):
28+
if sys.platform == "darwin":
29+
return self.darwin_checker()
30+
elif sys.platform == "linux":
31+
return self.linux_checker()
32+
else:
33+
raise Exception("Unsupported platform")
34+
35+
def ask_to_install(self):
36+
if (
37+
os.environ.get("PYTHONFORANDROID_PREREQUISITES_INSTALL_INTERACTIVE", "1")
38+
== "1"
39+
):
40+
res = input(
41+
f"Do you want automatically install prerequisite {self.name}? [y/N] "
42+
)
43+
if res.lower() == "y":
44+
return True
45+
else:
46+
return False
47+
else:
48+
info(
49+
"Session is not interactive (usually this happens during a CI run), so let's consider it as a YES"
50+
)
51+
return True
52+
53+
def install(self):
54+
info(f"python-for-android can automatically install prerequisite: {self.name}")
55+
if self.ask_to_install():
56+
if sys.platform == "darwin":
57+
self.darwin_installer()
58+
elif sys.platform == "linux":
59+
self.linux_installer()
60+
else:
61+
raise Exception("Unsupported platform")
62+
else:
63+
info(
64+
f"Skipping installation of prerequisite {self.name} as per user request"
65+
)
66+
67+
def show_helper(self):
68+
if sys.platform == "darwin":
69+
self.darwin_helper()
70+
elif sys.platform == "linux":
71+
self.linux_helper()
72+
else:
73+
raise Exception("Unsupported platform")
74+
75+
def install_is_supported(self):
76+
if sys.platform == "darwin":
77+
return self.darwin_installer_is_supported
78+
elif sys.platform == "linux":
79+
return self.linux_installer_is_supported
80+
81+
def linux_checker(self):
82+
raise Exception(f"Unsupported prerequisite check on linux for {self.name}")
83+
84+
def darwin_checker(self):
85+
raise Exception(f"Unsupported prerequisite check on macOS for {self.name}")
86+
87+
def linux_installer(self):
88+
raise Exception(f"Unsupported prerequisite installer on linux for {self.name}")
89+
90+
def darwin_installer(self):
91+
raise Exception(f"Unsupported prerequisite installer on macOS for {self.name}")
92+
93+
def darwin_helper(self):
94+
info(f"No helper available for prerequisite: {self.name} on macOS")
95+
96+
def linux_helper(self):
97+
info(f"No helper available for prerequisite: {self.name} on linux")
98+
99+
100+
class JDKPrerequisite(Prerequisite):
101+
name = "JDK"
102+
mandatory = True
103+
darwin_installer_is_supported = True
104+
min_supported_version = 11
105+
106+
def darwin_checker(self):
107+
if "JAVA_HOME" in os.environ:
108+
info("Found JAVA_HOME environment variable, using it")
109+
jdk_path = os.environ["JAVA_HOME"]
110+
else:
111+
jdk_path = self._darwin_get_libexec_jdk_path(version=None)
112+
return self._darwin_jdk_is_supported(jdk_path)
113+
114+
def _darwin_get_libexec_jdk_path(self, version=None):
115+
version_args = []
116+
if version is not None:
117+
version_args = ["-v", version]
118+
try:
119+
return check_output(["/usr/libexec/java_home", *version_args]).splitlines()[0].decode()
120+
except CalledProcessError as e:
121+
info("Can't find a valid JDK via /usr/libexec/java_home")
122+
return ""
123+
124+
def _darwin_jdk_is_supported(self, jdk_path):
125+
if not jdk_path:
126+
return False
127+
128+
javac_bin = os.path.join(jdk_path, "bin", "javac")
129+
if not os.path.exists(javac_bin):
130+
return False
131+
132+
res = check_output([javac_bin, "-version"]).decode()
133+
134+
major_version = int(res.split(" ")[-1].split(".")[0])
135+
if major_version >= self.min_supported_version:
136+
info(f"Found a valid JDK at {jdk_path}")
137+
return True
138+
else:
139+
error(f"JDK {self.min_supported_version} or higher is required")
140+
return False
141+
142+
def darwin_helper(self):
143+
info(
144+
"python-for-android requires a JDK 11 or higher to be installed on macOS,"
145+
"but seems like you don't have one installed."
146+
)
147+
info(
148+
"If you think that a valid JDK is already installed, please verify that "
149+
"you have a JDK 11 or higher installed and that `/usr/libexec/java_home` "
150+
"shows the correct path."
151+
)
152+
info(
153+
"If you have multiple JDK installations, please make sure that you have "
154+
"`JAVA_HOME` environment variable set to the correct JDK installation."
155+
)
156+
157+
def darwin_installer(self):
158+
info(
159+
"Looking for a JDK 11 or higher installation which is not the default one ..."
160+
)
161+
jdk_path = self._darwin_get_jdk_path(version="11+")
162+
163+
if not self._darwin_jdk_is_supported(jdk_path):
164+
info("We're unlucky, there's no JDK 11 or higher installation available")
165+
166+
base_url = "https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.2%2B8/"
167+
if platform.machine() == "arm64":
168+
filename = "OpenJDK17U-jdk_aarch64_mac_hotspot_17.0.2_8.tar.gz"
169+
else:
170+
filename = "OpenJDK17U-jdk_x64_mac_hotspot_17.0.2_8.tar.gz"
171+
172+
info(f"Downloading {filename} from {base_url}")
173+
check_output(
174+
[
175+
"curl",
176+
"-L",
177+
f"{base_url}{filename}",
178+
"-o",
179+
f"/tmp/{filename}",
180+
]
181+
)
182+
183+
user_library_java_path = os.path.expanduser(
184+
"~/Library/Java/JavaVirtualMachines"
185+
)
186+
info(f"Extracting {filename} to {user_library_java_path}")
187+
check_output(
188+
[
189+
"mkdir",
190+
"-p",
191+
user_library_java_path,
192+
]
193+
)
194+
check_output(
195+
["tar", "xzf", f"/tmp/{filename}", "-C", user_library_java_path]
196+
)
197+
198+
jdk_path = self._darwin_get_libexec_jdk_path(version="17.0.2+8")
199+
200+
info(f"Setting JAVA_HOME to {jdk_path}")
201+
os.environ["JAVA_HOME"] = jdk_path
202+
203+
204+
if __name__ == "__main__":
205+
DEFAULT_PREREQUISITES = dict(darwin=[JDKPrerequisite()], linux=[], all_platforms=[])
206+
207+
required_prerequisites = (
208+
DEFAULT_PREREQUISITES["all_platforms"] + DEFAULT_PREREQUISITES[sys.platform]
209+
)
210+
211+
prerequisites_not_met = []
212+
213+
warning("prerequisites.py is experimental and does not support all prerequisites yet.")
214+
warning("Please report any issues to the python-for-android issue tracker.")
215+
216+
# Phase 1: Check if all prerequisites are met and add the ones
217+
# which are not to `prerequisites_not_met`
218+
for prerequisite in required_prerequisites:
219+
if not prerequisite.is_valid():
220+
prerequisites_not_met.append(prerequisite)
221+
222+
# Phase 2: Setup/Install all prerequisites that are not met
223+
# (where possible), otherwise show an helper.
224+
for prerequisite in prerequisites_not_met:
225+
prerequisite.show_helper()
226+
if prerequisite.install_is_supported():
227+
prerequisite.install()

0 commit comments

Comments
 (0)