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

pybuilder / pybuilder / 23672534566

27 Mar 2026 11:58PM UTC coverage: 82.55% (-0.4%) from 82.991%
23672534566

push

github

web-flow
Release 0.13.19 [release] (#943)

## Summary

- Update CLAUDE.md with venv infrastructure, logs/reports, release
procedure, `-vX` docs
- Bump actions/checkout v4 to v6

## Changes included in 0.13.19

- #941 Full extras and markers support for dependencies
- #942 Fix lib64 site-packages not added to sys.path on Fedora/RHEL
- #938 Drop Python 3.9 support
- Python 3.14 support

1388 of 1856 branches covered (74.78%)

Branch coverage included in aggregate %.

5533 of 6528 relevant lines covered (84.76%)

8.46 hits per line

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

60.93
/src/main/python/pybuilder/python_utils.py
1
#   -*- coding: utf-8 -*-
2
#
3
#   This file is part of PyBuilder
4
#
5
#   Copyright 2011-2020 PyBuilder Team
6
#
7
#   Licensed under the Apache License, Version 2.0 (the "License");
8
#   you may not use this file except in compliance with the License.
9
#   You may obtain a copy of the License at
10
#
11
#       http://www.apache.org/licenses/LICENSE-2.0
12
#
13
#   Unless required by applicable law or agreed to in writing, software
14
#   distributed under the License is distributed on an "AS IS" BASIS,
15
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
#   See the License for the specific language governing permissions and
17
#   limitations under the License.
18

19
import os
10✔
20
import platform
10✔
21
import sys
10✔
22
import traceback
10✔
23
from collections import OrderedDict
10✔
24
from io import StringIO
10✔
25

26

27
def is_windows(platform=sys.platform, win_platforms={"win32", "cygwin", "msys"}):
10✔
28
    return platform in win_platforms
10✔
29

30

31
StringIO = StringIO
10✔
32
IS_PYPY = '__pypy__' in sys.builtin_module_names
10✔
33
IS_WIN = is_windows()
10✔
34

35

36
def raise_exception(ex, tb):
10✔
37
    raise ex.with_traceback(tb)
10✔
38

39

40
def is_string(val):
10✔
41
    return isinstance(val, str)
10✔
42

43

44
from shutil import which  # noqa: E402
10✔
45

46

47
def save_tb(ex):
10✔
48
    pass
×
49

50

51
is_string = is_string
10✔
52
makedirs = os.makedirs
10✔
53
which = which
10✔
54

55
odict = OrderedDict
10✔
56

57
_mp_get_context = None  # This will be patched at runtime
10✔
58
mp_ForkingPickler = None  # This will be patched at runtime
10✔
59
mp_log_to_stderr = None  # This will be patched at runtime
10✔
60
_mp_billiard_pyb_env = None  # This will be patched at runtime
10✔
61

62
_old_billiard_spawn_passfds = None  # This will be patched at runtime
10✔
63
_installed_tblib = False
10✔
64

65
from multiprocessing import log_to_stderr as mp_log_to_stderr, get_context as _mp_get_context  # noqa: E402
10✔
66
from multiprocessing.reduction import ForkingPickler as mp_ForkingPickler  # noqa: E402
10✔
67

68

69
def patch_mp_pyb_env(pyb_env):
10✔
70
    global _mp_billiard_pyb_env
71

72
    if not _mp_billiard_pyb_env:
10✔
73
        _mp_billiard_pyb_env = pyb_env
10✔
74

75

76
def install_tblib():
10✔
77
    global _installed_tblib
78

79
    if not _installed_tblib:
10!
80
        from pybuilder._vendor.tblib import pickling_support
10✔
81

82
        pickling_support.install()
10✔
83
        _installed_tblib = True
10✔
84

85

86
def patch_mp():
10✔
87
    install_tblib()
10✔
88

89

90
def mp_get_context(context):
10✔
91
    global _mp_get_context
92
    return _mp_get_context(context)
10✔
93

94

95
mp_ForkingPickler = mp_ForkingPickler
10✔
96
mp_log_to_stderr = mp_log_to_stderr
10✔
97
_mp_get_context = _mp_get_context
10✔
98

99

100
def _instrumented_target(q, target, *args, **kwargs):
10✔
101
    patch_mp()
×
102

103
    ex = tb = None
×
104
    try:
×
105
        send_value = (target(*args, **kwargs), None, None)
×
106
    except Exception:
×
107
        _, ex, tb = sys.exc_info()
×
108
        send_value = (None, ex, tb)
×
109

110
    try:
×
111
        q.put(send_value)
×
112
    except Exception:
×
113
        _, send_ex, send_tb = sys.exc_info()
×
114
        e_out = Exception(str(send_ex), send_tb, None if ex is None else str(ex), tb)
×
115
        q.put(e_out)
×
116

117

118
def spawn_process(target=None, args=(), kwargs={}, group=None, name=None):
10✔
119
    """
120
    Forks a child, making sure that all exceptions from the child are safely sent to the parent
121
    If a target raises an exception, the exception is re-raised in the parent process
122
    @return tuple consisting of process exit code and target's return value
123
    """
124
    ctx = mp_get_context("spawn")
×
125

126
    q = ctx.SimpleQueue()
×
127
    p = ctx.Process(group=group, target=_instrumented_target, name=name, args=[q, target] + list(args), kwargs=kwargs)
×
128
    p.start()
×
129
    result = q.get()
×
130
    p.join()
×
131
    if isinstance(result, tuple):
×
132
        if result[1]:
×
133
            raise_exception(result[1], result[2])
×
134
        return p.exitcode, result[0]
×
135
    else:
136
        msg = "Fatal error occurred in the forked process %s: %s" % (p, result.args[0])
×
137
        if result.args[2]:
×
138
            chained_message = "This error masked the send error '%s':\n%s" % (
×
139
                result.args[2], "".join(traceback.format_tb(result.args[3])))
140
            msg += "\n" + chained_message
×
141
        ex = Exception(msg)
×
142
        raise_exception(ex, result.args[1])
×
143

144

145
def prepend_env_to_path(python_env, sys_path):
10✔
146
    """type: (PythonEnv, List(str)) -> None
147
    Prepend venv directories to sys.path-like collection
148
    """
149
    for path in reversed(python_env.site_paths):
10✔
150
        if path not in sys_path:
10✔
151
            sys_path.insert(0, path)
10✔
152

153

154
def add_env_to_path(python_env, sys_path):
10✔
155
    """type: (PythonEnv, List(str)) -> None
156
    Adds venv directories to sys.path-like collection
157
    """
158
    for path in python_env.site_paths:
×
159
        if path not in sys_path:
×
160
            sys_path.append(path)
×
161

162

163
from glob import glob, iglob, escape  # noqa: E402
10✔
164

165
from os import symlink  # noqa: E402
10✔
166

167
symlink = symlink
10✔
168

169
sys_executable_suffix = sys.executable[len(sys.exec_prefix) + 1:]
10✔
170

171
python_specific_dir_name = "%s-%s" % (platform.python_implementation().lower(),
10✔
172
                                      ".".join(str(f) for f in sys.version_info))
173

174
_, _venv_python_exename = os.path.split(os.path.abspath(getattr(sys, "_base_executable", sys.executable)))
10✔
175

176
try:
10✔
177
    from imp import load_source
10✔
178
except ImportError:
6✔
179
    from importlib import machinery as importlib_machinery
6✔
180
    from importlib import util as importlib_util
6✔
181
    from importlib._bootstrap import _exec as importlib_exec
6✔
182
    from importlib._bootstrap import _load as importlib_load
6✔
183

184

185
    class _HackedGetData:
6✔
186

187
        """Compatibility support for 'file' arguments of various load_*()
188
        functions."""
189

190
        def __init__(self, fullname, path, file=None):
6✔
191
            super().__init__(fullname, path)
6✔
192
            self.file = file
6✔
193

194
        def get_data(self, path):
6✔
195
            """Gross hack to contort loader to deal w/ load_*()'s bad API."""
196
            if self.file and path == self.path:
6!
197
                # The contract of get_data() requires us to return bytes. Reopen the
198
                # file in binary mode if needed.
199
                if not self.file.closed:
×
200
                    file = self.file
×
201
                    if 'b' not in file.mode:
×
202
                        file.close()
×
203
                if self.file.closed:
×
204
                    self.file = file = open(self.path, 'rb')
×
205

206
                with file:
×
207
                    return file.read()
×
208
            else:
209
                return super().get_data(path)
6✔
210

211

212
    class _LoadSourceCompatibility(_HackedGetData, importlib_machinery.SourceFileLoader):
6✔
213

214
        """Compatibility support for implementing load_source()."""
215

216

217
    def load_source(name, pathname, file=None):
6✔
218
        loader = _LoadSourceCompatibility(name, pathname, file)
6✔
219
        spec = importlib_util.spec_from_file_location(name, pathname, loader=loader)
6✔
220
        if name in sys.modules:
6✔
221
            module = importlib_exec(spec, sys.modules[name])
6✔
222
        else:
223
            module = importlib_load(spec)
6✔
224
        # To allow reloading to potentially work, use a non-hacked loader which
225
        # won't rely on a now-closed file object.
226
        module.__loader__ = importlib_machinery.SourceFileLoader(name, pathname)
6✔
227
        module.__spec__.loader = module.__loader__
6✔
228
        return module
6✔
229

230
__all__ = ["glob", "iglob", "escape"]
10✔
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

© 2026 Coveralls, Inc