File: virtualenv.py

package info (click to toggle)
thunderbird 1%3A140.5.0esr-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 4,609,032 kB
  • sloc: cpp: 7,672,739; javascript: 5,901,898; ansic: 3,898,899; python: 1,413,347; xml: 653,997; asm: 462,284; java: 180,927; sh: 113,491; makefile: 20,460; perl: 14,288; objc: 13,059; yacc: 4,583; pascal: 3,352; lex: 1,720; ruby: 1,222; exp: 762; sql: 715; awk: 580; php: 436; lisp: 430; sed: 70; csh: 10
file content (180 lines) | stat: -rw-r--r-- 6,773 bytes parent folder | download | duplicates (11)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# mypy: allow-untyped-defs

import logging
import os
import shutil
import site
import sys
import sysconfig
from shutil import which

# The `pkg_resources` module is provided by `setuptools`, which is itself a
# dependency of `virtualenv`. Tolerate its absence so that this module may be
# evaluated when that module is not available. Because users may not recognize
# the `pkg_resources` module by name, raise a more descriptive error if it is
# referenced during execution.
try:
    import pkg_resources as _pkg_resources
    get_pkg_resources = lambda: _pkg_resources
except ImportError:
    def get_pkg_resources():
        raise ValueError("The Python module `virtualenv` is not installed.")

from tools.wpt.utils import call

logger = logging.getLogger(__name__)

class Virtualenv:
    def __init__(self, path, skip_virtualenv_setup):
        self.path = path
        self.skip_virtualenv_setup = skip_virtualenv_setup
        if not skip_virtualenv_setup:
            self.virtualenv = [sys.executable, "-m", "venv"]
            self._working_set = None

    @property
    def exists(self):
        # We need to check also for lib_path because different python versions
        # create different library paths.
        return os.path.isdir(self.path) and os.path.isdir(self.lib_path)

    @property
    def broken_link(self):
        python_link = os.path.join(self.path, ".Python")
        return os.path.lexists(python_link) and not os.path.exists(python_link)

    def create(self):
        if os.path.exists(self.path):
            shutil.rmtree(self.path, ignore_errors=True)
            self._working_set = None
        call(*self.virtualenv, self.path)

    def get_paths(self):
        """Wrapper around sysconfig.get_paths(), returning the appropriate paths for the env."""
        if "venv" in sysconfig.get_scheme_names():
            # This should always be used on Python 3.11 and above.
            scheme = "venv"
        elif os.name == "nt":
            # This matches nt_venv, unless sysconfig has been modified.
            scheme = "nt"
        elif os.name == "posix":
            # This matches posix_venv, unless sysconfig has been modified.
            scheme = "posix_prefix"
        elif sys.version_info >= (3, 10):
            # Using the default scheme is somewhat fragile, as various Python
            # distributors (e.g., what Debian and Fedora package, and what Xcode
            # includes) change the default scheme away from the upstream
            # defaults, but it's about as good as we can do.
            scheme = sysconfig.get_default_scheme()
        else:
            # This is explicitly documented as having previously existed in the 3.10
            # docs, and has existed since CPython 2.7 and 3.1 (but not 3.0).
            scheme = sysconfig._get_default_scheme()

        vars = {
            "base": self.path,
            "platbase": self.path,
            "installed_base": self.path,
            "installed_platbase": self.path,
        }

        return sysconfig.get_paths(scheme, vars)

    @property
    def bin_path(self):
        return self.get_paths()["scripts"]

    @property
    def pip_path(self):
        path = which("pip3", path=self.bin_path)
        if path is None:
            path = which("pip", path=self.bin_path)
        if path is None:
            raise ValueError("pip3 or pip not found")
        return path

    @property
    def lib_path(self):
        # We always return platlib here, even if it differs to purelib, because we can
        # always install pure-Python code into the platlib safely too. It's also very
        # unlikely to differ for a venv.
        return self.get_paths()["platlib"]

    @property
    def working_set(self):
        if not self.exists:
            raise ValueError("trying to read working_set when venv doesn't exist")

        if self._working_set is None:
            self._working_set = get_pkg_resources().WorkingSet((self.lib_path,))

        return self._working_set

    def activate(self):
        if sys.platform == "darwin":
            # The default Python on macOS sets a __PYVENV_LAUNCHER__ environment
            # variable which affects invocation of python (e.g. via pip) in a
            # virtualenv. Unset it if present to avoid this. More background:
            # https://github.com/web-platform-tests/wpt/issues/27377
            # https://github.com/python/cpython/pull/9516
            os.environ.pop("__PYVENV_LAUNCHER__", None)

        paths = self.get_paths()

        # Setup the path and site packages as if we'd launched with the virtualenv active
        bin_dir = paths["scripts"]
        os.environ["PATH"] = os.pathsep.join([bin_dir] + os.environ.get("PATH", "").split(os.pathsep))

        # While not required (`./venv/bin/python3` won't set it, but
        # `source ./venv/bin/activate && python3` will), we have historically set this.
        os.environ["VIRTUAL_ENV"] = self.path

        prev_length = len(sys.path)

        # Add the venv library paths as sitedirs.
        for key in ["purelib", "platlib"]:
            site.addsitedir(paths[key])

        # Rearrange the path
        sys.path[:] = sys.path[prev_length:] + sys.path[0:prev_length]

        # Change prefixes, similar to what initconfig/site does for venvs.
        sys.exec_prefix = self.path
        sys.prefix = self.path

    def start(self):
        if not self.exists or self.broken_link:
            self.create()
        self.activate()

    def install(self, *requirements):
        try:
            self.working_set.require(*requirements)
        except Exception:
            pass
        else:
            return

        # `--prefer-binary` guards against race conditions when installation
        # occurs while packages are in the process of being published.
        call(self.pip_path, "install", "--prefer-binary", *requirements)

    def install_requirements(self, *requirements_paths):
        install = []
        # Check which requirements are already satisfied, to skip calling pip
        # at all in the case that we've already installed everything, and to
        # minimise the installs in other cases.
        for requirements_path in requirements_paths:
            with open(requirements_path) as f:
                try:
                    self.working_set.require(f.read())
                except Exception:
                    install.append(requirements_path)

        if install:
            # `--prefer-binary` guards against race conditions when installation
            # occurs while packages are in the process of being published.
            cmd = [self.pip_path, "install", "--prefer-binary"]
            for path in install:
                cmd.extend(["-r", path])
            call(*cmd)