Skip to content

Commit 0de9354

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 0de9354

File tree

3 files changed

+276
-11
lines changed

3 files changed

+276
-11
lines changed

.github/workflows/push.yml

Lines changed: 31 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

@@ -107,6 +108,16 @@ jobs:
107108
steps:
108109
- name: Checkout python-for-android
109110
uses: actions/checkout@v2
111+
- name: Install python-for-android
112+
run: |
113+
source ci/osx_ci.sh
114+
arm64_set_path_and_python_version 3.9.7
115+
python3 -m pip install -e .
116+
- name: Install prerequisites via pythonforandroid/prerequisites.py (Experimental)
117+
run: |
118+
source ci/osx_ci.sh
119+
arm64_set_path_and_python_version 3.9.7
120+
python3 pythonforandroid/prerequisites.py
110121
- name: Install dependencies
111122
run: |
112123
source ci/osx_ci.sh
@@ -182,6 +193,16 @@ jobs:
182193
steps:
183194
- name: Checkout python-for-android
184195
uses: actions/checkout@v2
196+
- name: Install python-for-android
197+
run: |
198+
source ci/osx_ci.sh
199+
arm64_set_path_and_python_version 3.9.7
200+
python3 -m pip install -e .
201+
- name: Install prerequisites via pythonforandroid/prerequisites.py (Experimental)
202+
run: |
203+
source ci/osx_ci.sh
204+
arm64_set_path_and_python_version 3.9.7
205+
python3 pythonforandroid/prerequisites.py
185206
- name: Install dependencies
186207
run: |
187208
source ci/osx_ci.sh
@@ -258,6 +279,16 @@ jobs:
258279
uses: actions/checkout@v2
259280
with:
260281
fetch-depth: 0
282+
- name: Install python-for-android
283+
run: |
284+
source ci/osx_ci.sh
285+
arm64_set_path_and_python_version 3.9.7
286+
python3 -m pip install -e .
287+
- name: Install prerequisites via pythonforandroid/prerequisites.py (Experimental)
288+
run: |
289+
source ci/osx_ci.sh
290+
arm64_set_path_and_python_version 3.9.7
291+
python3 pythonforandroid/prerequisites.py
261292
- name: Install dependencies
262293
run: |
263294
source ci/osx_ci.sh

ci/makefiles/osx.mk

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,11 @@
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
127

138
upgrade_cython:
149
pip3 install --upgrade Cython
1510

1611
install_android_ndk_sdk:
1712
mkdir -p $(ANDROID_HOME)
1813
make -f ci/makefiles/android.mk JAVA_HOME=`/usr/libexec/java_home -v 13`
19-
20-
install_p4a:
21-
# check python version and install p4a
22-
python3 --version
23-
pip3 install -e .

pythonforandroid/prerequisites.py

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

0 commit comments

Comments
 (0)