Skip to content

Commit 54c7a30

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 54c7a30

File tree

3 files changed

+257
-6
lines changed

3 files changed

+257
-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: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
#!/usr/bin/env python3
2+
3+
import sys
4+
import platform
5+
import os
6+
import subprocess
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+
return (
119+
subprocess.run(
120+
["/usr/libexec/java_home", *version_args],
121+
stdout=subprocess.PIPE,
122+
)
123+
.stdout.strip()
124+
.decode()
125+
)
126+
127+
def _darwin_jdk_is_supported(self, jdk_path):
128+
if not jdk_path:
129+
return False
130+
131+
javac_bin = os.path.join(jdk_path, "bin", "javac")
132+
if not os.path.exists(javac_bin):
133+
return False
134+
135+
p = subprocess.Popen(
136+
[javac_bin, "-version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE
137+
)
138+
output, error = p.communicate()
139+
140+
if p.returncode != 0:
141+
error("Failed to run javac to check JDK version")
142+
return False
143+
144+
if not output:
145+
output = error
146+
147+
res = output.strip().decode()
148+
149+
print("javac res: ", res)
150+
151+
print(res.split(" "))
152+
print(res.split(" ")[-1].split("."))
153+
154+
major_version = int(res.split(" ")[-1].split(".")[0])
155+
if major_version >= self.min_supported_version:
156+
info(f"Found a valid JDK at {jdk_path}")
157+
return True
158+
else:
159+
error(f"JDK {self.min_supported_version} or higher is required")
160+
return False
161+
162+
def darwin_helper(self):
163+
info(
164+
"python-for-android requires a JDK 11 or higher to be installed on macOS,"
165+
"but seems like you don't have one installed."
166+
)
167+
info(
168+
"If you think that a valid JDK is already installed, please verify that "
169+
"you have a JDK 11 or higher installed and that `/usr/libexec/java_home` "
170+
"shows the correct path."
171+
)
172+
info(
173+
"If you have multiple JDK installations, please make sure that you have "
174+
"`JAVA_HOME` environment variable set to the correct JDK installation."
175+
)
176+
177+
def darwin_installer(self):
178+
info(
179+
"Looking for a JDK 11 or higher installation which is not the default one ..."
180+
)
181+
jdk_path = self._darwin_get_jdk_path(version="11+")
182+
183+
if not self._darwin_jdk_is_supported(jdk_path):
184+
info("We're unlucky, there's no JDK 11 or higher installation available")
185+
186+
base_url = "https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.2%2B8/"
187+
if platform.machine() == "arm64":
188+
filename = "OpenJDK17U-jdk_aarch64_mac_hotspot_17.0.2_8.tar.gz"
189+
else:
190+
filename = "OpenJDK17U-jdk_x64_mac_hotspot_17.0.2_8.tar.gz"
191+
192+
info(f"Downloading {filename} from {base_url}")
193+
subprocess.check_output(
194+
[
195+
"curl",
196+
"-L",
197+
f"{base_url}{filename}",
198+
"-o",
199+
f"/tmp/{filename}",
200+
]
201+
)
202+
203+
user_library_java_path = os.path.expanduser(
204+
"~/Library/Java/JavaVirtualMachines"
205+
)
206+
info(f"Extracting {filename} to {user_library_java_path}")
207+
subprocess.check_output(
208+
[
209+
"mkdir",
210+
"-p",
211+
user_library_java_path,
212+
],
213+
)
214+
subprocess.check_output(
215+
["tar", "xzf", f"/tmp/{filename}", "-C", user_library_java_path],
216+
)
217+
218+
jdk_path = self._darwin_get_libexec_jdk_path(version="17.0.2+8")
219+
220+
info(f"Setting JAVA_HOME to {jdk_path}")
221+
os.environ["JAVA_HOME"] = jdk_path
222+
223+
224+
if __name__ == "__main__":
225+
DEFAULT_PREREQUISITES = dict(darwin=[JDKPrerequisite()], linux=[], all_platforms=[])
226+
227+
required_prerequisites = (
228+
DEFAULT_PREREQUISITES["all_platforms"] + DEFAULT_PREREQUISITES[sys.platform]
229+
)
230+
231+
prerequisites_not_met = []
232+
233+
warning(
234+
"prerequisites.py is experimental and does not support all prerequisites yet."
235+
)
236+
warning("Please report any issues to the python-for-android issue tracker.")
237+
238+
# Phase 1: Check if all prerequisites are met and add the ones
239+
# which are not to `prerequisites_not_met`
240+
for prerequisite in required_prerequisites:
241+
if not prerequisite.is_valid():
242+
prerequisites_not_met.append(prerequisite)
243+
244+
# Phase 2: Setup/Install all prerequisites that are not met
245+
# (where possible), otherwise show an helper.
246+
for prerequisite in prerequisites_not_met:
247+
prerequisite.show_helper()
248+
if prerequisite.install_is_supported():
249+
prerequisite.install()

0 commit comments

Comments
 (0)