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

pybuilder / pybuilder / 23665642884

27 Mar 2026 08:15PM UTC coverage: 82.867%. First build
23665642884

Pull #942

github

web-flow
Merge 64589bacc into ddfe39868
Pull Request #942: Fix lib64 site-packages not added to sys.path on Fedora/RHEL

1385 of 1836 branches covered (75.44%)

Branch coverage included in aggregate %.

2 of 3 new or added lines in 1 file covered. (66.67%)

5512 of 6487 relevant lines covered (84.97%)

19.85 hits per line

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

81.69
/src/main/python/pybuilder/python_env.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 ast
24✔
20
import logging
24✔
21
import os
24✔
22
import subprocess
24✔
23
import sys
24✔
24
from os.path import pathsep
24✔
25

26
from pybuilder.install_utils import install_dependencies
24✔
27
from pybuilder.python_utils import is_windows, which
24✔
28
from pybuilder.utils import assert_can_execute, execute_command, jp, np
24✔
29

30
__all__ = ["PythonEnv", "PythonEnvRegistry"]
24✔
31

32
_PYTHON_INFO_SCRIPT = """import platform, sys, os, sysconfig
24✔
33
_base_executable = getattr(sys, "_base_executable", None)
34
_sys_executable = sys.executable
35
_executable = sys.executable
36
_platform = sys.platform
37
_free_threaded = sysconfig.get_config_var("Py_GIL_DISABLED")
38
if _platform == "linux2":
39
    _platform = "linux"
40
print({
41
"_platform": _platform,
42
"_os_name": os.name,
43
"_base_executable": (_base_executable, ),
44
"_sys_executable": (_sys_executable, ),
45
"_executable": (_executable, ),
46
"_exec_dir": os.path.dirname(_executable),
47
"_name": platform.python_implementation(),
48
"_type": platform.python_implementation().lower(),
49
"_version": tuple(sys.version_info),
50
"_free_threaded": _free_threaded,
51
"_is_pypy": "__pypy__" in sys.builtin_module_names,
52
"_is_64bit": (getattr(sys, "maxsize", None) or getattr(sys, "maxint")) > 2 ** 32,
53
"_versioned_dir_name": "%s-%s%s%s" % (platform.python_implementation().lower(),
54
                                  ".".join(str(f) for f in sys.version_info),
55
                                  "t" if _free_threaded else "",
56
                                  "-debug" if hasattr(sys, "abiflags") and "d" in sys.abiflags else ""),
57
"_environ": dict(os.environ),
58
"_darwin_python_framework": sysconfig.get_config_var("PYTHONFRAMEWORK")
59
})
60
"""
61

62
_FIELDS = {"platform", "executable", "name", "type", "version", "env_dir", "versioned_dir_name", "os_name",
24✔
63
           "site_paths", "is_pypy", "is_64bit", "environ", "exec_dir"}
64

65

66
class PythonEnv(object):
24✔
67
    def __init__(self, env_dir, reactor, platform=None, install_log_name="install.log"):
24✔
68
        self._env_dir = env_dir
24✔
69
        self._reactor = reactor
24✔
70
        self._platform = platform or sys.platform
24✔
71
        self._long_desc = "Unpopulated"
24✔
72
        self._site_paths = None
24✔
73
        self._venv_symlinks = os.name == "posix"
24✔
74
        self._install_log_path = jp(self._env_dir, install_log_name)
24✔
75
        self._populated = False
24✔
76

77
    def _check_populated(self):
24✔
78
        if self._populated:
24!
79
            raise RuntimeError("already populated")
×
80

81
    def _check_not_populated(self):
24✔
82
        if not self._populated:
24!
83
            raise RuntimeError("not yet populated")
×
84

85
    def populate(self):
24✔
86
        """Populates the environment information from the real Python"""
87
        self._check_populated()
24✔
88

89
        python_exec_path = _venv_python_executable(self._env_dir, self._platform)
24✔
90
        result = subprocess.check_output([python_exec_path, "-c", _PYTHON_INFO_SCRIPT], universal_newlines=True)
24✔
91
        python_info = ast.literal_eval(result)
24✔
92

93
        for k, v in python_info.items():
24✔
94
            setattr(self, k, v)
24✔
95

96
        # Python data is all uploaded
97
        self._populated = True
24✔
98

99
        self._recalculate_derived()
24✔
100
        return self
24✔
101

102
    def _recalculate_derived(self):
24✔
103
        self._site_paths = tuple(self._get_site_paths())
24✔
104

105
        environ_path = self._environ.get("PATH")
24✔
106
        if environ_path:
24!
107
            self._environ["PATH"] = pathsep.join([self._exec_dir] + environ_path.split(pathsep))
24✔
108

109
        self._long_desc = "%s version %s on %s in %s" % (self.name,
24✔
110
                                                         ".".join(str(v) for v in self.version),
111
                                                         self.platform,
112
                                                         self.executable)
113
        self._short_desc = "%s %s" % (self.name, ".".join(str(v) for v in self.version))
24✔
114

115
    def __str__(self):
24✔
116
        return self._long_desc
24✔
117

118
    @property
24✔
119
    def venv_symlinks(self):
24✔
120
        return self._venv_symlinks
24✔
121

122
    @property
24✔
123
    def reactor(self):
24✔
124
        return self._reactor
×
125

126
    @property
24✔
127
    def project(self):
24✔
128
        return self._reactor.project
24✔
129

130
    @property
24✔
131
    def logger(self):
24✔
132
        return self._reactor.logger
24✔
133

134
    @property
24✔
135
    def install_log_path(self):
24✔
136
        return self._install_log_path
24✔
137

138
    @property
24✔
139
    def platform(self):
24✔
140
        self._check_not_populated()
24✔
141
        return self._platform
24✔
142

143
    @property
24✔
144
    def executable(self):
24✔
145
        self._check_not_populated()
24✔
146
        return list(self._executable)
24✔
147

148
    @property
24✔
149
    def name(self):
24✔
150
        self._check_not_populated()
24✔
151
        return self._name
24✔
152

153
    @property
24✔
154
    def type(self):
24✔
155
        self._check_not_populated()
×
156
        return self._type
×
157

158
    @property
24✔
159
    def version(self):
24✔
160
        self._check_not_populated()
24✔
161
        return self._version
24✔
162

163
    @property
24✔
164
    def free_threaded(self):
24✔
165
        self._check_not_populated()
×
166
        return self._free_threaded
×
167

168
    @property
24✔
169
    def env_dir(self):
24✔
170
        return self._env_dir
24✔
171

172
    @property
24✔
173
    def exec_dir(self):
24✔
174
        return self._exec_dir
×
175

176
    @property
24✔
177
    def versioned_dir_name(self):
24✔
178
        self._check_not_populated()
24✔
179
        return self._versioned_dir_name
24✔
180

181
    @property
24✔
182
    def os_name(self):
24✔
183
        self._check_not_populated()
×
184
        return self._os_name
×
185

186
    @property
24✔
187
    def site_paths(self):
24✔
188
        self._check_not_populated()
24✔
189
        return list(self._site_paths)
24✔
190

191
    @property
24✔
192
    def is_pypy(self):
24✔
193
        self._check_not_populated()
24✔
194
        return self._is_pypy
24✔
195

196
    @property
24✔
197
    def is_64bit(self):
24✔
198
        self._check_not_populated()
×
199
        return self._is_64bit
×
200

201
    @property
24✔
202
    def environ(self):
24✔
203
        self._check_not_populated()
24✔
204
        return dict(self._environ)
24✔
205

206
    def overwrite(self, prop, value):
24✔
207
        if prop not in _FIELDS:
24!
208
            raise KeyError("'%s' is not a property that can be overwritten" % prop)
×
209
        setattr(self, "_%s" % prop, value)
24✔
210

211
        self._recalculate_derived()
24✔
212

213
    def create_venv(self, system_site_packages=False,
24✔
214
                    clear=False,
215
                    symlinks=False,
216
                    upgrade=False,
217
                    with_pip=False,
218
                    prompt=None,
219
                    offline=False,
220
                    ):
221
        """Creates VEnv in the designated location. Must not be yet populated."""
222

223
        self._check_populated()
24✔
224

225
        create_venv(self._env_dir,
24✔
226
                    system_site_packages=system_site_packages,
227
                    clear=clear,
228
                    symlinks=symlinks,
229
                    upgrade=upgrade,
230
                    with_pip=with_pip,
231
                    prompt=prompt,
232
                    offline=offline,
233
                    logger=self.logger)
234

235
        return self.populate()
24✔
236

237
    def recreate_venv(self, system_site_packages=False,
24✔
238
                      clear=False,
239
                      symlinks=False,
240
                      upgrade=False,
241
                      with_pip=False,
242
                      prompt=None,
243
                      offline=False,
244
                      ):
245

246
        create_venv(self._env_dir,
×
247
                    system_site_packages=system_site_packages,
248
                    clear=clear,
249
                    symlinks=symlinks,
250
                    upgrade=upgrade,
251
                    with_pip=with_pip,
252
                    prompt=prompt,
253
                    offline=offline,
254
                    logger=self.logger)
255

256
        return self
×
257

258
    def install_dependencies(self, pip_batch,
24✔
259
                             install_log_path=None,
260
                             local_mapping=None,
261
                             constraints_file_name=None,
262
                             log_file_mode="ab",
263
                             package_type="dependency",
264
                             target_dir=None,
265
                             ignore_installed=False,
266
                             ):
267

268
        install_dependencies(self.logger, self.project,
24✔
269
                             pip_batch,
270
                             self,
271
                             install_log_path or self.install_log_path,
272
                             local_mapping=local_mapping,
273
                             constraints_file_name=constraints_file_name,
274
                             log_file_mode=log_file_mode,
275
                             package_type=package_type,
276
                             target_dir=target_dir,
277
                             ignore_installed=ignore_installed)
278

279
    def verify_can_execute(self, command_and_arguments, prerequisite, caller, env=None, no_path_search=False,
24✔
280
                           inherit_env=True):
281
        environ = self.environ if inherit_env else {}
24✔
282
        if env:
24!
283
            environ.update(env)
×
284
        return assert_can_execute(command_and_arguments, prerequisite, caller, env=environ,
24✔
285
                                  no_path_search=no_path_search, logger=self.logger)
286

287
    def execute_command(self, command_and_arguments,
24✔
288
                        outfile_name=None,
289
                        env=None,
290
                        cwd=None,
291
                        error_file_name=None,
292
                        shell=False,
293
                        no_path_search=False,
294
                        inherit_env=True):
295
        environ = self.environ if inherit_env else {}
24✔
296
        if env:
24✔
297
            environ.update(env)
24✔
298

299
        return execute_command(command_and_arguments, outfile_name=outfile_name, env=environ, cwd=cwd,
24✔
300
                               error_file_name=error_file_name, shell=shell, no_path_search=no_path_search,
301
                               logger=self.logger)
302

303
    def run_process_and_wait(self, commands, cwd, stdout, stderr=None, no_path_search=True):
24✔
304
        if is_windows(self.platform) and not no_path_search:
24!
305
            which_cmd = which(commands[0], path=self.environ.get("PATH"))
×
306
            if which_cmd:
×
307
                commands[0] = which_cmd
×
308

309
        with open(os.devnull) as devnull:
24✔
310
            process = subprocess.Popen(commands,
24✔
311
                                       cwd=cwd,
312
                                       stdin=devnull,
313
                                       stdout=stdout,
314
                                       stderr=stderr or stdout,
315
                                       shell=False)
316
            return process.wait()
24✔
317

318
    def _get_site_paths(self):
24✔
319
        prefix = self.env_dir
24✔
320
        if self.is_pypy:
24!
321
            yield os.path.join(prefix, "lib", "pypy%d.%d" % self.version[:2], "site-packages")
×
322
            yield os.path.join(prefix, "site-packages")
×
323
        elif os.sep == "/":
24✔
324
            yield os.path.join(prefix, "lib",
14✔
325
                               "python%d.%d" % self.version[:2],
326
                               "site-packages")
327
            lib64_path = os.path.join(prefix, "lib64",
14✔
328
                                      "python%d.%d" % self.version[:2],
329
                                      "site-packages")
330
            if os.path.isdir(lib64_path):
14!
NEW
331
                yield lib64_path
×
332
        else:
333
            yield prefix
10✔
334
            yield os.path.join(prefix, "lib", "site-packages")
10✔
335

336
        if self.platform == "darwin":
24✔
337
            # for framework builds *only* we add the standard Apple
338
            # locations.
339
            framework = self._darwin_python_framework
4✔
340
            if framework:
4!
341
                yield os.path.join("/Library", framework,
4✔
342
                                   "%d.%d" % self.version[:2], "site-packages")
343

344

345
class PythonEnvRegistry(object):
24✔
346
    def __init__(self, reactor):
24✔
347
        self.reactor = reactor
24✔
348
        self.logger = reactor.logger
24✔
349
        self._registry = {}
24✔
350

351
    def __setitem__(self, key, value):
24✔
352
        """type: (str, PythonEnv) -> None"""
353
        registry = self._registry
24✔
354
        existing_env = registry.get(key)
24✔
355
        if existing_env:
24!
356
            raise KeyError("environment '%s' is already registered: %s", key, existing_env[-1])
×
357

358
        self.logger.debug("Registered Python environment '%s': %s", key, value)
24✔
359
        existing_env = [value]
24✔
360
        registry[key] = existing_env
24✔
361

362
    def __delitem__(self, key):
24✔
363
        """type: (str) -> None"""
364
        return self._registry.__delitem__(key)
×
365

366
    def __getitem__(self, item):
24✔
367
        """type: (str) -> PythonEnv"""
368
        registry = self._registry
24✔
369
        existing_env = registry.get(item)
24✔
370
        if not existing_env:
24✔
371
            raise KeyError("no environment '%s' registered" % item)
24✔
372
        return self._registry[item][-1]
24✔
373

374
    def push_override(self, key, value):
24✔
375
        registry = self._registry
24✔
376
        existing_env = registry.get(key)
24✔
377
        if not existing_env:
24!
378
            raise KeyError("no environment '%s' registered" % key)
×
379

380
        existing_env.append(value)
24✔
381

382
    def pop_override(self, key):
24✔
383
        registry = self._registry
24✔
384
        existing_env = registry.get(key)
24✔
385

386
        if not existing_env:
24!
387
            raise KeyError("no environment '%s' registered" % key)
×
388

389
        if len(existing_env) == 1:
24!
390
            raise RuntimeError("environment '%s' is not overridden" % key)
×
391

392
        del existing_env[-1]
24✔
393

394

395
def create_venv(home_dir,
24✔
396
                system_site_packages=False,
397
                clear=False,
398
                symlinks=False,
399
                upgrade=False,
400
                with_pip=False,
401
                prompt=None,
402
                offline=False,
403
                logger=None):
404
    import virtualenv
24✔
405

406
    args = [home_dir, "--no-periodic-update", "-p", sys.executable]
24✔
407

408
    if upgrade and (not offline):
24✔
409
        pass
24✔
410
    #        args_upgrade = list(args)
411
    #        args_upgrade.append("--download")
412
    #        args_upgrade.append("--upgrade-embed-wheels")
413
    #        try:
414
    #            virtualenv.cli_run(args_upgrade, setup_logging=False)
415
    #        except SystemExit as e:
416
    #            if e.code:
417
    #                raise RuntimeError("VirtualEnv upgrade has not completed successfully", e)
418

419
    if clear:
24!
420
        args.append("--clear")
×
421

422
    # if logger.level < logger.WARNING:
423
    #    args += ["-v"]
424

425
    if symlinks:
24✔
426
        args.append("--symlinks")
14✔
427
    else:
428
        args.append("--copies")
10✔
429
    if not with_pip:
24!
430
        args.append("--no-pip")
×
431
    if system_site_packages:
24!
432
        args.append("--system-site-packages")
×
433
    if prompt:
24!
434
        args += ["--prompt", prompt]
×
435

436
    logging.getLogger("filelock").setLevel(logging.INFO)
24✔
437
    virtualenv.cli_run(args, setup_logging=False)
24✔
438

439

440
_, _venv_python_exename = os.path.split(os.path.abspath(getattr(sys, "_base_executable", sys.executable)))
24✔
441
venv_symlinks = os.name == "posix"
24✔
442

443
# On Windows python.exe could be in PythonXY/ or venv/Scripts/
444
# python[3[.x]].exe may also not be available, only python.exe
445
_windows_exec_candidates = (lambda env_dir: jp(env_dir, "Scripts", _venv_python_exename),
24✔
446
                            lambda env_dir: jp(env_dir, _venv_python_exename),
447
                            lambda env_dir: jp(env_dir, "Scripts", "python.exe"),
448
                            lambda env_dir: jp(env_dir, "python.exe"),
449
                            )
450

451

452
def _venv_python_executable(env_dir, platform):
24✔
453
    """Binary Python executable for a specific virtual environment"""
454
    if is_windows(platform):
24✔
455
        for candidate_func in _windows_exec_candidates:
10!
456
            candidate = candidate_func(env_dir)
10✔
457
            if os.path.exists(candidate):
10!
458
                break
10✔
459
    else:
460
        candidate = jp(env_dir, "bin", _venv_python_exename)
14✔
461

462
    return np(candidate)
24✔
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