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

kivy / python-for-android / 17641397155

11 Sep 2025 10:15AM UTC coverage: 59.214% (+0.04%) from 59.171%
17641397155

Pull #3168

github

web-flow
Merge 42336ff0c into 5330267b5
Pull Request #3168: Fix broadcast receiver

1060 of 2385 branches covered (44.44%)

Branch coverage included in aggregate %.

4965 of 7790 relevant lines covered (63.74%)

2.54 hits per line

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

93.1
/pythonforandroid/util.py
1
import contextlib
4✔
2
from unittest import mock
4✔
3
from fnmatch import fnmatch
4✔
4
import logging
4✔
5
from os.path import exists, join
4✔
6
from os import getcwd, chdir, makedirs, walk
4✔
7
from pathlib import Path
4✔
8
from platform import uname
4✔
9
import shutil
4✔
10
from tempfile import mkdtemp
4✔
11

12
import packaging.version
4✔
13

14
from pythonforandroid.logger import (logger, Err_Fore, error, info)
4✔
15

16
LOGGER = logging.getLogger("p4a.util")
4✔
17

18
build_platform = "{system}-{machine}".format(
4✔
19
    system=uname().system, machine=uname().machine
20
).lower()
21
"""the build platform in the format `system-machine`. We use
2✔
22
this string to define the right build system when compiling some recipes or
23
to get the right path for clang compiler"""
24

25

26
@contextlib.contextmanager
4✔
27
def current_directory(new_dir):
4✔
28
    cur_dir = getcwd()
4✔
29
    logger.info(''.join((Err_Fore.CYAN, '-> directory context ', new_dir,
4✔
30
                         Err_Fore.RESET)))
31
    chdir(new_dir)
4✔
32
    yield
4✔
33
    logger.info(''.join((Err_Fore.CYAN, '<- directory context ', cur_dir,
4✔
34
                         Err_Fore.RESET)))
35
    chdir(cur_dir)
4✔
36

37

38
@contextlib.contextmanager
4✔
39
def temp_directory():
4✔
40
    temp_dir = mkdtemp()
4✔
41
    try:
4✔
42
        logger.debug(''.join((Err_Fore.CYAN, ' + temp directory used ',
4✔
43
                              temp_dir, Err_Fore.RESET)))
44
        yield temp_dir
4✔
45
    finally:
46
        shutil.rmtree(temp_dir)
4✔
47
        logger.debug(''.join((Err_Fore.CYAN, ' - temp directory deleted ',
4✔
48
                              temp_dir, Err_Fore.RESET)))
49

50

51
def walk_valid_filens(base_dir, invalid_dir_names, invalid_file_patterns, excluded_dir_exceptions=None):
4✔
52
    """Recursively walks all the files and directories in ``dirn``,
53
    ignoring directories that match any pattern in ``invalid_dirns``
54
    and files that patch any pattern in ``invalid_filens``.
55

56
    ``invalid_dirns`` and ``invalid_filens`` should both be lists of
57
    strings to match. ``invalid_dir_patterns`` expects a list of
58
    invalid directory names, while ``invalid_file_patterns`` expects a
59
    list of glob patterns compared against the full filepath.
60

61
    File and directory paths are evaluated as full paths relative to ``dirn``.
62

63
    If ``excluded_dir_exceptions`` is given, any directory path that contains
64
    any of those strings will *not* exclude subdirectories matching
65
    ``invalid_dir_names``.
66
    """
67

68
    excluded_dir_exceptions = [] if excluded_dir_exceptions is None else excluded_dir_exceptions
4✔
69

70
    for dirn, subdirs, filens in walk(base_dir):
4✔
71
        allow_invalid_dirs = any(ex in dirn for ex in excluded_dir_exceptions)
4✔
72

73
        # Remove invalid subdirs so that they will not be walked
74
        if not allow_invalid_dirs:
4!
75
            for i in reversed(range(len(subdirs))):
4✔
76
                subdir = subdirs[i]
4✔
77
                if subdir in invalid_dir_names:
4✔
78
                    subdirs.pop(i)
4✔
79

80
        for filen in filens:
4✔
81
            for pattern in invalid_file_patterns:
4✔
82
                if fnmatch(filen, pattern):
4✔
83
                    break
4✔
84
            else:
85
                yield join(dirn, filen)
4✔
86

87

88
def load_source(module, filename):
4✔
89
    # Python 3.5+
90
    import importlib.util
4✔
91
    if hasattr(importlib.util, 'module_from_spec'):
4!
92
        spec = importlib.util.spec_from_file_location(module, filename)
4✔
93
        mod = importlib.util.module_from_spec(spec)
4✔
94
        spec.loader.exec_module(mod)
4✔
95
        return mod
4✔
96
    else:
97
        # Python 3.3 and 3.4:
98
        from importlib.machinery import SourceFileLoader
×
99
        return SourceFileLoader(module, filename).load_module()
×
100

101

102
class BuildInterruptingException(Exception):
4✔
103
    def __init__(self, message, instructions=None):
4✔
104
        super().__init__(message, instructions)
4✔
105
        self.message = message
4✔
106
        self.instructions = instructions
4✔
107

108

109
def handle_build_exception(exception):
4✔
110
    """
111
    Handles a raised BuildInterruptingException by printing its error
112
    message and associated instructions, if any, then exiting.
113
    """
114
    error('Build failed: {}'.format(exception.message))
4✔
115
    if exception.instructions is not None:
4!
116
        info('Instructions: {}'.format(exception.instructions))
4✔
117
    exit(1)
4✔
118

119

120
def rmdir(dn, ignore_errors=False):
4✔
121
    if not exists(dn):
4!
122
        return
×
123
    LOGGER.debug("Remove directory and subdirectory {}".format(dn))
4✔
124
    shutil.rmtree(dn, ignore_errors)
4✔
125

126

127
def ensure_dir(dn):
4✔
128
    if exists(dn):
4✔
129
        return
4✔
130
    LOGGER.debug("Create directory {0}".format(dn))
4✔
131
    makedirs(dn)
4✔
132

133

134
def move(source, destination):
4✔
135
    LOGGER.debug("Moving {} to {}".format(source, destination))
4✔
136
    shutil.move(source, destination)
4✔
137

138

139
def touch(filename):
4✔
140
    Path(filename).touch()
4✔
141

142

143
def build_tools_version_sort_key(
4✔
144
    version_string: str,
145
) -> packaging.version.Version:
146
    """
147
    Returns a packaging.version.Version object for comparison purposes.
148
    It includes canonicalization of the version string to allow for
149
    comparison of versions with spaces in them (historically, RC candidates)
150

151
    If the version string is invalid, it returns a version object with
152
    version 0, which will be sorted at worst position.
153
    """
154

155
    try:
4✔
156
        # Historically, Android build release candidates have had
157
        # spaces in the version number.
158
        return packaging.version.Version(version_string.replace(" ", ""))
4✔
159
    except packaging.version.InvalidVersion:
4✔
160
        # Put badly named versions at worst position.
161
        return packaging.version.Version("0")
4✔
162

163

164
def max_build_tool_version(
4✔
165
    build_tools_versions: list,
166
) -> str:
167
    """
168
    Returns the maximum build tools version from a list of build tools
169
    versions. It uses the :meth:`build_tools_version_sort_key` function to
170
    canonicalize the version strings and then returns the maximum version.
171
    """
172

173
    return max(build_tools_versions, key=build_tools_version_sort_key)
4✔
174

175

176
def patch_wheel_setuptools_logging():
4✔
177
    """
178
    When setuptools is not present and the root logger has no handlers,
179
    Wheels would configure the root logger with DEBUG level, refs:
180
    - https://github.com/pypa/wheel/blob/0.44.0/src/wheel/util.py
181
    - https://github.com/pypa/wheel/blob/0.44.0/src/wheel/_setuptools_logging.py
182

183
    Both of these conditions are met in our CI, leading to very verbose
184
    and unreadable `sh` logs. Patching it prevents that.
185
    """
186
    return mock.patch("wheel._setuptools_logging.configure")
×
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