• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kivy / python-for-android / 5976260269

25 Aug 2023 01:05PM UTC coverage: 58.986% (+0.3%) from 58.676%
5976260269

push

github

web-flow
Standardise `ensure_dir` and `rmdir` (#2871)

* Standardise ensure_dir and rmdir

* Standardise ensure_dir and rmdir

* Add libmysqlclient to broken list

* Libtorrent failing to be rebuilt

* Add boost to broken recipes list

940 of 2241 branches covered (0.0%)

Branch coverage included in aggregate %.

73 of 113 new or added lines in 21 files covered. (64.6%)

3 existing lines in 3 files now uncovered.

4715 of 7346 relevant lines covered (64.18%)

2.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

43.07
/pythonforandroid/prerequisites.py
1
#!/usr/bin/env python3
2

3
import os
4✔
4
import platform
4✔
5
import shutil
4✔
6
import subprocess
4✔
7
import sys
4✔
8

9
from pythonforandroid.logger import info, warning, error
4✔
10
from pythonforandroid.util import ensure_dir
4✔
11

12

13
class Prerequisite(object):
4✔
14
    name = "Default"
4✔
15
    homebrew_formula_name = ""
4✔
16
    mandatory = dict(linux=False, darwin=False)
4✔
17
    installer_is_supported = dict(linux=False, darwin=False)
4✔
18

19
    def is_valid(self):
4✔
20
        if self.checker():
×
21
            info(f"Prerequisite {self.name} is met")
×
22
            return (True, "")
×
23
        elif not self.mandatory[sys.platform]:
×
24
            warning(
×
25
                f"Prerequisite {self.name} is not met, but is marked as non-mandatory"
26
            )
27
        else:
28
            error(f"Prerequisite {self.name} is not met")
×
29

30
    def checker(self):
4✔
31
        if sys.platform == "darwin":
×
32
            return self.darwin_checker()
×
33
        elif sys.platform == "linux":
×
34
            return self.linux_checker()
×
35
        else:
36
            raise Exception("Unsupported platform")
×
37

38
    def ask_to_install(self):
4✔
39
        if (
×
40
            os.environ.get("PYTHONFORANDROID_PREREQUISITES_INSTALL_INTERACTIVE", "1")
41
            == "1"
42
        ):
43
            res = input(
×
44
                f"Do you want automatically install prerequisite {self.name}? [y/N] "
45
            )
46
            if res.lower() == "y":
×
47
                return True
×
48
            else:
49
                return False
×
50
        else:
51
            info(
×
52
                "Session is not interactive (usually this happens during a CI run), so let's consider it as a YES"
53
            )
54
            return True
×
55

56
    def install(self):
4✔
57
        info(f"python-for-android can automatically install prerequisite: {self.name}")
×
58
        if self.ask_to_install():
×
59
            if sys.platform == "darwin":
×
60
                self.darwin_installer()
×
61
            elif sys.platform == "linux":
×
62
                self.linux_installer()
×
63
            else:
64
                raise Exception("Unsupported platform")
×
65
        else:
66
            info(
×
67
                f"Skipping installation of prerequisite {self.name} as per user request"
68
            )
69

70
    def show_helper(self):
4✔
71
        if sys.platform == "darwin":
×
72
            self.darwin_helper()
×
73
        elif sys.platform == "linux":
×
74
            self.linux_helper()
×
75
        else:
76
            raise Exception("Unsupported platform")
×
77

78
    def install_is_supported(self):
4✔
79
        return self.installer_is_supported[sys.platform]
×
80

81
    def linux_checker(self):
4✔
82
        raise Exception(f"Unsupported prerequisite check on linux for {self.name}")
×
83

84
    def darwin_checker(self):
4✔
85
        raise Exception(f"Unsupported prerequisite check on macOS for {self.name}")
×
86

87
    def linux_installer(self):
4✔
88
        raise Exception(f"Unsupported prerequisite installer on linux for {self.name}")
×
89

90
    def darwin_installer(self):
4✔
91
        raise Exception(f"Unsupported prerequisite installer on macOS for {self.name}")
×
92

93
    def darwin_helper(self):
4✔
94
        info(f"No helper available for prerequisite: {self.name} on macOS")
×
95

96
    def linux_helper(self):
4✔
97
        info(f"No helper available for prerequisite: {self.name} on linux")
×
98

99
    def _darwin_get_brew_formula_location_prefix(self, formula, installed=False):
4✔
100
        opts = ["--installed"] if installed else []
×
101
        p = subprocess.Popen(
×
102
            ["brew", "--prefix", formula, *opts],
103
            stdout=subprocess.PIPE,
104
            stderr=subprocess.PIPE,
105
        )
106
        _stdout_res, _stderr_res = p.communicate()
×
107

108
        if p.returncode != 0:
×
109
            error(_stderr_res.decode("utf-8").strip())
×
110
            return None
×
111
        else:
112
            return _stdout_res.decode("utf-8").strip()
×
113

114
    def darwin_pkg_config_location(self):
4✔
115
        warning(
4✔
116
            f"pkg-config location is not supported on macOS for prerequisite: {self.name}"
117
        )
118
        return ""
4✔
119

120
    def linux_pkg_config_location(self):
4✔
121
        warning(
4✔
122
            f"pkg-config location is not supported on linux for prerequisite: {self.name}"
123
        )
124
        return ""
4✔
125

126
    @property
4✔
127
    def pkg_config_location(self):
4✔
128
        if sys.platform == "darwin":
4!
129
            return self.darwin_pkg_config_location()
×
130
        elif sys.platform == "linux":
4!
131
            return self.linux_pkg_config_location()
4✔
132

133

134
class HomebrewPrerequisite(Prerequisite):
4✔
135
    name = "homebrew"
4✔
136
    mandatory = dict(linux=False, darwin=True)
4✔
137
    installer_is_supported = dict(linux=False, darwin=False)
4✔
138

139
    def darwin_checker(self):
4✔
140
        return shutil.which("brew") is not None
4✔
141

142
    def darwin_helper(self):
4✔
143
        info(
4✔
144
            "Installer for homebrew is not yet supported on macOS,"
145
            "the nice news is that the installation process is easy!"
146
            "See: https://brew.sh for further instructions."
147
        )
148

149

150
class JDKPrerequisite(Prerequisite):
4✔
151
    name = "JDK"
4✔
152
    mandatory = dict(linux=False, darwin=True)
4✔
153
    installer_is_supported = dict(linux=False, darwin=True)
4✔
154
    min_supported_version = 11
4✔
155

156
    def darwin_checker(self):
4✔
157
        if "JAVA_HOME" in os.environ:
×
158
            info("Found JAVA_HOME environment variable, using it")
×
159
            jdk_path = os.environ["JAVA_HOME"]
×
160
        else:
161
            jdk_path = self._darwin_get_libexec_jdk_path(version=None)
×
162
        return self._darwin_jdk_is_supported(jdk_path)
×
163

164
    def _darwin_get_libexec_jdk_path(self, version=None):
4✔
165
        version_args = []
×
166
        if version is not None:
×
167
            version_args = ["-v", version]
×
168
        return (
×
169
            subprocess.run(
170
                ["/usr/libexec/java_home", *version_args],
171
                stdout=subprocess.PIPE,
172
            )
173
            .stdout.strip()
174
            .decode()
175
        )
176

177
    def _darwin_jdk_is_supported(self, jdk_path):
4✔
178
        if not jdk_path:
×
179
            return False
×
180

181
        javac_bin = os.path.join(jdk_path, "bin", "javac")
×
182
        if not os.path.exists(javac_bin):
×
183
            return False
×
184

185
        p = subprocess.Popen(
×
186
            [javac_bin, "-version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE
187
        )
188
        _stdout_res, _stderr_res = p.communicate()
×
189

190
        if p.returncode != 0:
×
191
            error("Failed to run javac to check JDK version")
×
192
            return False
×
193

194
        if not _stdout_res:
×
195
            _stdout_res = _stderr_res
×
196

197
        res = _stdout_res.strip().decode()
×
198

199
        major_version = int(res.split(" ")[-1].split(".")[0])
×
200
        if major_version >= self.min_supported_version:
×
201
            info(f"Found a valid JDK at {jdk_path}")
×
202
            return True
×
203
        else:
204
            error(f"JDK {self.min_supported_version} or higher is required")
×
205
            return False
×
206

207
    def darwin_helper(self):
4✔
208
        info(
×
209
            "python-for-android requires a JDK 11 or higher to be installed on macOS,"
210
            "but seems like you don't have one installed."
211
        )
212
        info(
×
213
            "If you think that a valid JDK is already installed, please verify that "
214
            "you have a JDK 11 or higher installed and that `/usr/libexec/java_home` "
215
            "shows the correct path."
216
        )
217
        info(
×
218
            "If you have multiple JDK installations, please make sure that you have "
219
            "`JAVA_HOME` environment variable set to the correct JDK installation."
220
        )
221

222
    def darwin_installer(self):
4✔
223
        info(
×
224
            "Looking for a JDK 11 or higher installation which is not the default one ..."
225
        )
226
        jdk_path = self._darwin_get_libexec_jdk_path(version="11+")
×
227

228
        if not self._darwin_jdk_is_supported(jdk_path):
×
229
            info("We're unlucky, there's no JDK 11 or higher installation available")
×
230

231
            base_url = "https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.2%2B8/"
×
232
            if platform.machine() == "arm64":
×
233
                filename = "OpenJDK17U-jdk_aarch64_mac_hotspot_17.0.2_8.tar.gz"
×
234
            else:
235
                filename = "OpenJDK17U-jdk_x64_mac_hotspot_17.0.2_8.tar.gz"
×
236

237
            info(f"Downloading {filename} from {base_url}")
×
238
            subprocess.check_output(
×
239
                [
240
                    "curl",
241
                    "-L",
242
                    f"{base_url}{filename}",
243
                    "-o",
244
                    f"/tmp/{filename}",
245
                ]
246
            )
247

248
            user_library_java_path = os.path.expanduser(
×
249
                "~/Library/Java/JavaVirtualMachines"
250
            )
251
            info(f"Extracting {filename} to {user_library_java_path}")
×
NEW
252
            ensure_dir(user_library_java_path)
×
UNCOV
253
            subprocess.check_output(
×
254
                ["tar", "xzf", f"/tmp/{filename}", "-C", user_library_java_path],
255
            )
256

257
            jdk_path = self._darwin_get_libexec_jdk_path(version="17.0.2+8")
×
258

259
        info(f"Setting JAVA_HOME to {jdk_path}")
×
260
        os.environ["JAVA_HOME"] = jdk_path
×
261

262

263
class OpenSSLPrerequisite(Prerequisite):
4✔
264
    name = "openssl"
4✔
265
    homebrew_formula_name = "openssl@1.1"
4✔
266
    mandatory = dict(linux=False, darwin=True)
4✔
267
    installer_is_supported = dict(linux=False, darwin=True)
4✔
268

269
    def darwin_checker(self):
4✔
270
        return (
4✔
271
            self._darwin_get_brew_formula_location_prefix(
272
                self.homebrew_formula_name, installed=True
273
            )
274
            is not None
275
        )
276

277
    def darwin_pkg_config_location(self):
4✔
278
        return os.path.join(
4✔
279
            self._darwin_get_brew_formula_location_prefix(self.homebrew_formula_name),
280
            "lib/pkgconfig",
281
        )
282

283
    def darwin_installer(self):
4✔
284
        info("Installing OpenSSL ...")
4✔
285
        subprocess.check_output(["brew", "install", self.homebrew_formula_name])
4✔
286

287

288
class AutoconfPrerequisite(Prerequisite):
4✔
289
    name = "autoconf"
4✔
290
    mandatory = dict(linux=False, darwin=True)
4✔
291
    installer_is_supported = dict(linux=False, darwin=True)
4✔
292

293
    def darwin_checker(self):
4✔
294
        return (
4✔
295
            self._darwin_get_brew_formula_location_prefix("autoconf", installed=True)
296
            is not None
297
        )
298

299
    def darwin_installer(self):
4✔
300
        info("Installing Autoconf ...")
4✔
301
        subprocess.check_output(["brew", "install", "autoconf"])
4✔
302

303

304
class AutomakePrerequisite(Prerequisite):
4✔
305
    name = "automake"
4✔
306
    mandatory = dict(linux=False, darwin=True)
4✔
307
    installer_is_supported = dict(linux=False, darwin=True)
4✔
308

309
    def darwin_checker(self):
4✔
310
        return (
4✔
311
            self._darwin_get_brew_formula_location_prefix("automake", installed=True)
312
            is not None
313
        )
314

315
    def darwin_installer(self):
4✔
316
        info("Installing Automake ...")
4✔
317
        subprocess.check_output(["brew", "install", "automake"])
4✔
318

319

320
class LibtoolPrerequisite(Prerequisite):
4✔
321
    name = "libtool"
4✔
322
    mandatory = dict(linux=False, darwin=True)
4✔
323
    installer_is_supported = dict(linux=False, darwin=True)
4✔
324

325
    def darwin_checker(self):
4✔
326
        return (
4✔
327
            self._darwin_get_brew_formula_location_prefix("libtool", installed=True)
328
            is not None
329
        )
330

331
    def darwin_installer(self):
4✔
332
        info("Installing Libtool ...")
4✔
333
        subprocess.check_output(["brew", "install", "libtool"])
4✔
334

335

336
class PkgConfigPrerequisite(Prerequisite):
4✔
337
    name = "pkg-config"
4✔
338
    mandatory = dict(linux=False, darwin=True)
4✔
339
    installer_is_supported = dict(linux=False, darwin=True)
4✔
340

341
    def darwin_checker(self):
4✔
342
        return (
4✔
343
            self._darwin_get_brew_formula_location_prefix("pkg-config", installed=True)
344
            is not None
345
        )
346

347
    def darwin_installer(self):
4✔
348
        info("Installing Pkg-Config ...")
4✔
349
        subprocess.check_output(["brew", "install", "pkg-config"])
4✔
350

351

352
class CmakePrerequisite(Prerequisite):
4✔
353
    name = "cmake"
4✔
354
    mandatory = dict(linux=False, darwin=True)
4✔
355
    installer_is_supported = dict(linux=False, darwin=True)
4✔
356

357
    def darwin_checker(self):
4✔
358
        return (
4✔
359
            self._darwin_get_brew_formula_location_prefix("cmake", installed=True)
360
            is not None
361
        )
362

363
    def darwin_installer(self):
4✔
364
        info("Installing cmake ...")
4✔
365
        subprocess.check_output(["brew", "install", "cmake"])
4✔
366

367

368
def get_required_prerequisites(platform="linux"):
4✔
369
    return [
4✔
370
        prerequisite_cls()
371
        for prerequisite_cls in [
372
            HomebrewPrerequisite,
373
            AutoconfPrerequisite,
374
            AutomakePrerequisite,
375
            LibtoolPrerequisite,
376
            PkgConfigPrerequisite,
377
            CmakePrerequisite,
378
            OpenSSLPrerequisite,
379
            JDKPrerequisite,
380
        ] if prerequisite_cls.mandatory.get(platform, False)
381
    ]
382

383

384
def check_and_install_default_prerequisites():
4✔
385

386
    prerequisites_not_met = []
×
387

388
    warning(
×
389
        "prerequisites.py is experimental and does not support all prerequisites yet."
390
    )
391
    warning("Please report any issues to the python-for-android issue tracker.")
×
392

393
    # Phase 1: Check if all prerequisites are met and add the ones
394
    # which are not to `prerequisites_not_met`
395
    for prerequisite in get_required_prerequisites(sys.platform):
×
396
        if not prerequisite.is_valid():
×
397
            prerequisites_not_met.append(prerequisite)
×
398

399
    # Phase 2: Setup/Install all prerequisites that are not met
400
    # (where possible), otherwise show an helper.
401
    for prerequisite in prerequisites_not_met:
×
402
        prerequisite.show_helper()
×
403
        if prerequisite.install_is_supported():
×
404
            prerequisite.install()
×
405

406

407
if __name__ == "__main__":
408
    check_and_install_default_prerequisites()
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc