From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- testing/web-platform/tests/tools/wpt/virtualenv.py | 174 +++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 testing/web-platform/tests/tools/wpt/virtualenv.py (limited to 'testing/web-platform/tests/tools/wpt/virtualenv.py') diff --git a/testing/web-platform/tests/tools/wpt/virtualenv.py b/testing/web-platform/tests/tools/wpt/virtualenv.py new file mode 100644 index 0000000000..f1fad73270 --- /dev/null +++ b/testing/web-platform/tests/tools/wpt/virtualenv.py @@ -0,0 +1,174 @@ +# mypy: allow-untyped-defs + +import logging +import os +import shutil +import site +import sys +import sysconfig +from pathlib import Path +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) + + @property + def bin_path(self): + if sys.platform in ("win32", "cygwin"): + return os.path.join(self.path, "Scripts") + return os.path.join(self.path, "bin") + + @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): + base = self.path + + # this block is literally taken from virtualenv 16.4.3 + IS_PYPY = hasattr(sys, "pypy_version_info") + IS_JYTHON = sys.platform.startswith("java") + if IS_JYTHON: + site_packages = os.path.join(base, "Lib", "site-packages") + elif IS_PYPY: + site_packages = os.path.join(base, "site-packages") + else: + IS_WIN = sys.platform == "win32" + if IS_WIN: + site_packages = os.path.join(base, "Lib", "site-packages") + else: + version = f"{sys.version_info.major}.{sys.version_info.minor}" + site_packages = os.path.join(base, "lib", f"python{version}", "site-packages") + + return site_packages + + @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) + + # Setup the path and site packages as if we'd launched with the virtualenv active + bin_dir = os.path.join(self.path, "bin") + os.environ["PATH"] = os.pathsep.join([bin_dir] + os.environ.get("PATH", "").split(os.pathsep)) + os.environ["VIRTUAL_ENV"] = self.path + + prev_length = len(sys.path) + + schemes = sysconfig.get_scheme_names() + if "venv" in schemes: + scheme = "venv" + else: + scheme = "nt" if os.name == "nt" else "posix_user" + sys_paths = sysconfig.get_paths(scheme) + data_path = sys_paths["data"] + added = set() + # Add the venv library paths as sitedirs. + # This converts system paths like /usr/local/lib/python3.10/site-packages + # to venv-relative paths like {self.path}/lib/python3.10/site-packages and adds + # those paths as site dirs to be used for module import. + for key in ["purelib", "platlib"]: + host_path = Path(sys_paths[key]) + relative_path = host_path.relative_to(data_path) + site_dir = os.path.normpath(os.path.normcase(Path(self.path) / relative_path)) + if site_dir not in added: + site.addsitedir(site_dir) + added.add(site_dir) + sys.path[:] = sys.path[prev_length:] + sys.path[0:prev_length] + + sys.real_prefix = sys.prefix + 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) -- cgit v1.2.3