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

kivy / python-for-android / 20391670964

20 Dec 2025 08:18AM UTC coverage: 63.319%. First build
20391670964

Pull #3271

github

web-flow
Merge 3ada98070 into 6494ac165
Pull Request #3271: `toolchain`: auto resolve deps

1781 of 3073 branches covered (57.96%)

Branch coverage included in aggregate %.

19 of 87 new or added lines in 5 files covered. (21.84%)

5217 of 7979 relevant lines covered (65.38%)

5.22 hits per line

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

91.88
/pythonforandroid/recipes/hostpython3/__init__.py
1
import sh
8✔
2
import os
8✔
3

4
from multiprocessing import cpu_count
8✔
5
from pathlib import Path
8✔
6
from os.path import join
8✔
7
from glob import glob
8✔
8

9
from packaging.version import Version
8✔
10
from pythonforandroid.logger import shprint
8✔
11
from pythonforandroid.recipe import Recipe
8✔
12
from pythonforandroid.util import (
8✔
13
    BuildInterruptingException,
14
    current_directory,
15
    ensure_dir,
16
)
17
from pythonforandroid.prerequisites import OpenSSLPrerequisite
8✔
18

19
HOSTPYTHON_VERSION_UNSET_MESSAGE = (
8✔
20
    'The hostpython recipe must have set version'
21
)
22

23
SETUP_DIST_NOT_FIND_MESSAGE = (
8✔
24
    'Could not find Setup.dist or Setup in Python build'
25
)
26

27

28
class HostPython3Recipe(Recipe):
8✔
29
    '''
30
    The hostpython3's recipe.
31

32
    .. versionchanged:: 2019.10.06.post0
33
        Refactored from deleted class ``python.HostPythonRecipe`` into here.
34

35
    .. versionchanged:: 0.6.0
36
        Refactored into  the new class
37
        :class:`~pythonforandroid.python.HostPythonRecipe`
38
    '''
39

40
    version = '3.14.2'
8✔
41

42
    url = 'https://github.com/python/cpython/archive/refs/tags/v{version}.tar.gz'
8✔
43
    '''The default url to download our host python recipe. This url will
6✔
44
    change depending on the python version set in attribute :attr:`version`.'''
45

46
    build_subdir = 'native-build'
8✔
47
    '''Specify the sub build directory for the hostpython3 recipe. Defaults
6✔
48
    to ``native-build``.'''
49

50
    @property
8✔
51
    def _exe_name(self):
8✔
52
        '''
53
        Returns the name of the python executable depending on the version.
54
        '''
55
        if not self.version:
8✔
56
            raise BuildInterruptingException(HOSTPYTHON_VERSION_UNSET_MESSAGE)
8✔
57
        return f'python{self.version.split(".")[0]}'
8✔
58

59
    @property
8✔
60
    def python_exe(self):
8✔
61
        '''Returns the full path of the hostpython executable.'''
62
        return join(self.get_path_to_python(), self._exe_name)
8✔
63

64
    def get_recipe_env(self, arch=None):
8✔
65
        env = os.environ.copy()
8✔
66
        openssl_prereq = OpenSSLPrerequisite()
8✔
67
        if env.get("PKG_CONFIG_PATH", ""):
8!
68
            env["PKG_CONFIG_PATH"] = os.pathsep.join(
8✔
69
                [openssl_prereq.pkg_config_location, env["PKG_CONFIG_PATH"]]
70
            )
71
        else:
72
            env["PKG_CONFIG_PATH"] = openssl_prereq.pkg_config_location
×
73
        return env
8✔
74

75
    def should_build(self, arch):
8✔
76
        if Path(self.python_exe).exists():
8✔
77
            # no need to build, but we must set hostpython for our Context
78
            self.ctx.hostpython = self.python_exe
8✔
79
            return False
8✔
80
        return True
8✔
81

82
    def get_build_container_dir(self, arch=None):
8✔
83
        choices = self.check_recipe_choices()
8✔
84
        dir_name = '-'.join([self.name] + choices)
8✔
85
        return join(self.ctx.build_dir, 'other_builds', dir_name, 'desktop')
8✔
86

87
    def get_build_dir(self, arch=None):
8✔
88
        '''
89
        .. note:: Unlike other recipes, the hostpython build dir doesn't
90
            depend on the target arch
91
        '''
92
        return join(self.get_build_container_dir(), self.name)
8✔
93

94
    def get_path_to_python(self):
8✔
95
        return join(self.get_build_dir(), self.build_subdir)
8✔
96

97
    @property
8✔
98
    def site_root(self):
8✔
99
        return join(self.get_path_to_python(), "root")
8✔
100

101
    @property
8✔
102
    def site_bin(self):
8✔
103
        return join(self.site_root, self.site_dir, "bin")
8✔
104

105
    @property
8✔
106
    def local_bin(self):
8✔
107
        return join(self.site_root, "usr/local/bin/")
8✔
108

109
    @property
8✔
110
    def site_dir(self):
8✔
111
        p_version = Version(self.version)
8✔
112
        return join(
8✔
113
            self.site_root,
114
            f"usr/local/lib/python{p_version.major}.{p_version.minor}/site-packages/"
115
        )
116

117
    @property
8✔
118
    def _pip(self):
8✔
119

NEW
120
        _pip = join(self.local_bin, "pip*")
×
NEW
121
        try:
×
NEW
122
            _pip = glob(_pip)[0]
×
NEW
123
        except Exception as e:
×
NEW
124
            print(os.listdir(self.local_bin))
×
NEW
125
            print("fail!", e, _pip)
×
NEW
126
            exit(1)
×
127

NEW
128
        return _pip
×
129

130
    @property
8✔
131
    def pip(self):
8✔
NEW
132
        return sh.Command(self._pip)
×
133

134
    def build_arch(self, arch):
8✔
135
        env = self.get_recipe_env(arch)
8✔
136

137
        recipe_build_dir = self.get_build_dir(arch.arch)
8✔
138

139
        # Create a subdirectory to actually perform the build
140
        build_dir = join(recipe_build_dir, self.build_subdir)
8✔
141
        ensure_dir(build_dir)
8✔
142

143
        # Configure the build
144
        build_configured = False
8✔
145
        with current_directory(build_dir):
8✔
146
            if not Path('config.status').exists():
8✔
147
                shprint(sh.Command(join(recipe_build_dir, 'configure')), _env=env)
8✔
148
                build_configured = True
8✔
149

150
        with current_directory(recipe_build_dir):
8✔
151
            # Create the Setup file. This copying from Setup.dist is
152
            # the normal and expected procedure before Python 3.8, but
153
            # after this the file with default options is already named "Setup"
154
            setup_dist_location = join('Modules', 'Setup.dist')
8✔
155
            if Path(setup_dist_location).exists():
8✔
156
                shprint(sh.cp, setup_dist_location,
8✔
157
                        join(build_dir, 'Modules', 'Setup'))
158
            else:
159
                # Check the expected file does exist
160
                setup_location = join('Modules', 'Setup')
8✔
161
                if not Path(setup_location).exists():
8✔
162
                    raise BuildInterruptingException(
8✔
163
                        SETUP_DIST_NOT_FIND_MESSAGE
164
                    )
165

166
            shprint(sh.make, '-j', str(cpu_count()), '-C', build_dir, _env=env)
8✔
167

168
            # make a copy of the python executable giving it the name we want,
169
            # because we got different python's executable names depending on
170
            # the fs being case-insensitive (Mac OS X, Cygwin...) or
171
            # case-sensitive (linux)...so this way we will have an unique name
172
            # for our hostpython, regarding the used fs
173
            for exe_name in ['python.exe', 'python']:
8!
174
                exe = join(self.get_path_to_python(), exe_name)
8✔
175
                if Path(exe).is_file():
8!
176
                    shprint(sh.cp, exe, self.python_exe)
8✔
177
                    break
8✔
178

179
        ensure_dir(self.site_root)
8✔
180
        self.ctx.hostpython = self.python_exe
8✔
181

182
        if build_configured:
8✔
183
            # , "PATH": self.local_bin
184
            shprint(
8✔
185
                sh.Command(self.python_exe), "-m", "ensurepip", "--root", self.site_root, "-U",
186
                _env={"HOME": "/tmp"}
187
            )
188

189

190
recipe = HostPython3Recipe()
8✔
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