Mercurial > repos > guerler > springsuite
comparison planemo/lib/python3.7/site-packages/virtualenv/discovery/py_info.py @ 1:56ad4e20f292 draft
"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
| author | guerler |
|---|---|
| date | Fri, 31 Jul 2020 00:32:28 -0400 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 0:d30785e31577 | 1:56ad4e20f292 |
|---|---|
| 1 """ | |
| 2 The PythonInfo contains information about a concrete instance of a Python interpreter | |
| 3 | |
| 4 Note: this file is also used to query target interpreters, so can only use standard library methods | |
| 5 """ | |
| 6 from __future__ import absolute_import, print_function | |
| 7 | |
| 8 import json | |
| 9 import logging | |
| 10 import os | |
| 11 import platform | |
| 12 import re | |
| 13 import sys | |
| 14 import sysconfig | |
| 15 from collections import OrderedDict, namedtuple | |
| 16 from distutils import dist | |
| 17 from distutils.command.install import SCHEME_KEYS | |
| 18 from string import digits | |
| 19 | |
| 20 VersionInfo = namedtuple("VersionInfo", ["major", "minor", "micro", "releaselevel", "serial"]) | |
| 21 | |
| 22 | |
| 23 def _get_path_extensions(): | |
| 24 return list(OrderedDict.fromkeys([""] + os.environ.get("PATHEXT", "").lower().split(os.pathsep))) | |
| 25 | |
| 26 | |
| 27 EXTENSIONS = _get_path_extensions() | |
| 28 _CONF_VAR_RE = re.compile(r"\{\w+\}") | |
| 29 | |
| 30 | |
| 31 class PythonInfo(object): | |
| 32 """Contains information for a Python interpreter""" | |
| 33 | |
| 34 def __init__(self): | |
| 35 def u(v): | |
| 36 return v.decode("utf-8") if isinstance(v, bytes) else v | |
| 37 | |
| 38 def abs_path(v): | |
| 39 return None if v is None else os.path.abspath(v) # unroll relative elements from path (e.g. ..) | |
| 40 | |
| 41 # qualifies the python | |
| 42 self.platform = u(sys.platform) | |
| 43 self.implementation = u(platform.python_implementation()) | |
| 44 if self.implementation == "PyPy": | |
| 45 self.pypy_version_info = tuple(u(i) for i in sys.pypy_version_info) | |
| 46 | |
| 47 # this is a tuple in earlier, struct later, unify to our own named tuple | |
| 48 self.version_info = VersionInfo(*list(u(i) for i in sys.version_info)) | |
| 49 self.architecture = 64 if sys.maxsize > 2 ** 32 else 32 | |
| 50 | |
| 51 self.version = u(sys.version) | |
| 52 self.os = u(os.name) | |
| 53 | |
| 54 # information about the prefix - determines python home | |
| 55 self.prefix = u(abs_path(getattr(sys, "prefix", None))) # prefix we think | |
| 56 self.base_prefix = u(abs_path(getattr(sys, "base_prefix", None))) # venv | |
| 57 self.real_prefix = u(abs_path(getattr(sys, "real_prefix", None))) # old virtualenv | |
| 58 | |
| 59 # information about the exec prefix - dynamic stdlib modules | |
| 60 self.base_exec_prefix = u(abs_path(getattr(sys, "base_exec_prefix", None))) | |
| 61 self.exec_prefix = u(abs_path(getattr(sys, "exec_prefix", None))) | |
| 62 | |
| 63 self.executable = u(abs_path(sys.executable)) # the executable we were invoked via | |
| 64 self.original_executable = u(abs_path(self.executable)) # the executable as known by the interpreter | |
| 65 self.system_executable = self._fast_get_system_executable() # the executable we are based of (if available) | |
| 66 | |
| 67 try: | |
| 68 __import__("venv") | |
| 69 has = True | |
| 70 except ImportError: | |
| 71 has = False | |
| 72 self.has_venv = has | |
| 73 self.path = [u(i) for i in sys.path] | |
| 74 self.file_system_encoding = u(sys.getfilesystemencoding()) | |
| 75 self.stdout_encoding = u(getattr(sys.stdout, "encoding", None)) | |
| 76 | |
| 77 self.sysconfig_paths = {u(i): u(sysconfig.get_path(i, expand=False)) for i in sysconfig.get_path_names()} | |
| 78 # https://bugs.python.org/issue22199 | |
| 79 makefile = getattr(sysconfig, "get_makefile_filename", getattr(sysconfig, "_get_makefile_filename", None)) | |
| 80 self.sysconfig = { | |
| 81 u(k): u(v) | |
| 82 for k, v in [ | |
| 83 # a list of content to store from sysconfig | |
| 84 ("makefile_filename", makefile()), | |
| 85 ] | |
| 86 if k is not None | |
| 87 } | |
| 88 | |
| 89 config_var_keys = set() | |
| 90 for element in self.sysconfig_paths.values(): | |
| 91 for k in _CONF_VAR_RE.findall(element): | |
| 92 config_var_keys.add(u(k[1:-1])) | |
| 93 config_var_keys.add("PYTHONFRAMEWORK") | |
| 94 | |
| 95 self.sysconfig_vars = {u(i): u(sysconfig.get_config_var(i) or "") for i in config_var_keys} | |
| 96 if self.implementation == "PyPy" and sys.version_info.major == 2: | |
| 97 self.sysconfig_vars[u"implementation_lower"] = u"python" | |
| 98 | |
| 99 self.distutils_install = {u(k): u(v) for k, v in self._distutils_install().items()} | |
| 100 confs = {k: (self.system_prefix if v.startswith(self.prefix) else v) for k, v in self.sysconfig_vars.items()} | |
| 101 self.system_stdlib = self.sysconfig_path("stdlib", confs) | |
| 102 self.system_stdlib_platform = self.sysconfig_path("platstdlib", confs) | |
| 103 self.max_size = getattr(sys, "maxsize", getattr(sys, "maxint", None)) | |
| 104 self._creators = None | |
| 105 | |
| 106 def _fast_get_system_executable(self): | |
| 107 """Try to get the system executable by just looking at properties""" | |
| 108 if self.real_prefix or ( | |
| 109 self.base_prefix is not None and self.base_prefix != self.prefix | |
| 110 ): # if this is a virtual environment | |
| 111 if self.real_prefix is None: | |
| 112 base_executable = getattr(sys, "_base_executable", None) # some platforms may set this to help us | |
| 113 if base_executable is not None: # use the saved system executable if present | |
| 114 if sys.executable != base_executable: # we know we're in a virtual environment, cannot be us | |
| 115 return base_executable | |
| 116 return None # in this case we just can't tell easily without poking around FS and calling them, bail | |
| 117 # if we're not in a virtual environment, this is already a system python, so return the original executable | |
| 118 # note we must choose the original and not the pure executable as shim scripts might throw us off | |
| 119 return self.original_executable | |
| 120 | |
| 121 @staticmethod | |
| 122 def _distutils_install(): | |
| 123 # follow https://github.com/pypa/pip/blob/master/src/pip/_internal/locations.py#L95 | |
| 124 # note here we don't import Distribution directly to allow setuptools to patch it | |
| 125 d = dist.Distribution({"script_args": "--no-user-cfg"}) # conf files not parsed so they do not hijack paths | |
| 126 if hasattr(sys, "_framework"): | |
| 127 sys._framework = None # disable macOS static paths for framework | |
| 128 i = d.get_command_obj("install", create=True) | |
| 129 i.prefix = os.sep # paths generated are relative to prefix that contains the path sep, this makes it relative | |
| 130 i.finalize_options() | |
| 131 result = {key: (getattr(i, "install_{}".format(key))[1:]).lstrip(os.sep) for key in SCHEME_KEYS} | |
| 132 return result | |
| 133 | |
| 134 @property | |
| 135 def version_str(self): | |
| 136 return ".".join(str(i) for i in self.version_info[0:3]) | |
| 137 | |
| 138 @property | |
| 139 def version_release_str(self): | |
| 140 return ".".join(str(i) for i in self.version_info[0:2]) | |
| 141 | |
| 142 @property | |
| 143 def python_name(self): | |
| 144 version_info = self.version_info | |
| 145 return "python{}.{}".format(version_info.major, version_info.minor) | |
| 146 | |
| 147 @property | |
| 148 def is_old_virtualenv(self): | |
| 149 return self.real_prefix is not None | |
| 150 | |
| 151 @property | |
| 152 def is_venv(self): | |
| 153 return self.base_prefix is not None and self.version_info.major == 3 | |
| 154 | |
| 155 def sysconfig_path(self, key, config_var=None, sep=os.sep): | |
| 156 pattern = self.sysconfig_paths[key] | |
| 157 if config_var is None: | |
| 158 config_var = self.sysconfig_vars | |
| 159 else: | |
| 160 base = {k: v for k, v in self.sysconfig_vars.items()} | |
| 161 base.update(config_var) | |
| 162 config_var = base | |
| 163 return pattern.format(**config_var).replace(u"/", sep) | |
| 164 | |
| 165 def creators(self, refresh=False): | |
| 166 if self._creators is None or refresh is True: | |
| 167 from virtualenv.run.plugin.creators import CreatorSelector | |
| 168 | |
| 169 self._creators = CreatorSelector.for_interpreter(self) | |
| 170 return self._creators | |
| 171 | |
| 172 @property | |
| 173 def system_include(self): | |
| 174 path = self.sysconfig_path( | |
| 175 "include", | |
| 176 {k: (self.system_prefix if v.startswith(self.prefix) else v) for k, v in self.sysconfig_vars.items()}, | |
| 177 ) | |
| 178 if not os.path.exists(path): # some broken packaging don't respect the sysconfig, fallback to distutils path | |
| 179 # the pattern include the distribution name too at the end, remove that via the parent call | |
| 180 fallback = os.path.join(self.prefix, os.path.dirname(self.distutils_install["headers"])) | |
| 181 if os.path.exists(fallback): | |
| 182 path = fallback | |
| 183 return path | |
| 184 | |
| 185 @property | |
| 186 def system_prefix(self): | |
| 187 return self.real_prefix or self.base_prefix or self.prefix | |
| 188 | |
| 189 @property | |
| 190 def system_exec_prefix(self): | |
| 191 return self.real_prefix or self.base_exec_prefix or self.exec_prefix | |
| 192 | |
| 193 def __unicode__(self): | |
| 194 content = repr(self) | |
| 195 if sys.version_info == 2: | |
| 196 content = content.decode("utf-8") | |
| 197 return content | |
| 198 | |
| 199 def __repr__(self): | |
| 200 return "{}({!r})".format( | |
| 201 self.__class__.__name__, {k: v for k, v in self.__dict__.items() if not k.startswith("_")}, | |
| 202 ) | |
| 203 | |
| 204 def __str__(self): | |
| 205 content = "{}({})".format( | |
| 206 self.__class__.__name__, | |
| 207 ", ".join( | |
| 208 "{}={}".format(k, v) | |
| 209 for k, v in ( | |
| 210 ("spec", self.spec), | |
| 211 ( | |
| 212 "system" | |
| 213 if self.system_executable is not None and self.system_executable != self.executable | |
| 214 else None, | |
| 215 self.system_executable, | |
| 216 ), | |
| 217 ( | |
| 218 "original" | |
| 219 if ( | |
| 220 self.original_executable != self.system_executable | |
| 221 and self.original_executable != self.executable | |
| 222 ) | |
| 223 else None, | |
| 224 self.original_executable, | |
| 225 ), | |
| 226 ("exe", self.executable), | |
| 227 ("platform", self.platform), | |
| 228 ("version", repr(self.version)), | |
| 229 ("encoding_fs_io", "{}-{}".format(self.file_system_encoding, self.stdout_encoding)), | |
| 230 ) | |
| 231 if k is not None | |
| 232 ), | |
| 233 ) | |
| 234 return content | |
| 235 | |
| 236 @property | |
| 237 def spec(self): | |
| 238 return "{}{}-{}".format(self.implementation, ".".join(str(i) for i in self.version_info), self.architecture) | |
| 239 | |
| 240 @classmethod | |
| 241 def clear_cache(cls, app_data): | |
| 242 # this method is not used by itself, so here and called functions can import stuff locally | |
| 243 from virtualenv.discovery.cached_py_info import clear | |
| 244 | |
| 245 clear(app_data) | |
| 246 cls._cache_exe_discovery.clear() | |
| 247 | |
| 248 def satisfies(self, spec, impl_must_match): | |
| 249 """check if a given specification can be satisfied by the this python interpreter instance""" | |
| 250 if spec.path: | |
| 251 if self.executable == os.path.abspath(spec.path): | |
| 252 return True # if the path is a our own executable path we're done | |
| 253 if not spec.is_abs: | |
| 254 # if path set, and is not our original executable name, this does not match | |
| 255 basename = os.path.basename(self.original_executable) | |
| 256 spec_path = spec.path | |
| 257 if sys.platform == "win32": | |
| 258 basename, suffix = os.path.splitext(basename) | |
| 259 if spec_path.endswith(suffix): | |
| 260 spec_path = spec_path[: -len(suffix)] | |
| 261 if basename != spec_path: | |
| 262 return False | |
| 263 | |
| 264 if impl_must_match: | |
| 265 if spec.implementation is not None and spec.implementation.lower() != self.implementation.lower(): | |
| 266 return False | |
| 267 | |
| 268 if spec.architecture is not None and spec.architecture != self.architecture: | |
| 269 return False | |
| 270 | |
| 271 for our, req in zip(self.version_info[0:3], (spec.major, spec.minor, spec.micro)): | |
| 272 if req is not None and our is not None and our != req: | |
| 273 return False | |
| 274 return True | |
| 275 | |
| 276 _current_system = None | |
| 277 _current = None | |
| 278 | |
| 279 @classmethod | |
| 280 def current(cls, app_data=None): | |
| 281 """ | |
| 282 This locates the current host interpreter information. This might be different than what we run into in case | |
| 283 the host python has been upgraded from underneath us. | |
| 284 """ | |
| 285 if cls._current is None: | |
| 286 cls._current = cls.from_exe(sys.executable, app_data, raise_on_error=True, resolve_to_host=False) | |
| 287 return cls._current | |
| 288 | |
| 289 @classmethod | |
| 290 def current_system(cls, app_data=None): | |
| 291 """ | |
| 292 This locates the current host interpreter information. This might be different than what we run into in case | |
| 293 the host python has been upgraded from underneath us. | |
| 294 """ | |
| 295 if cls._current_system is None: | |
| 296 cls._current_system = cls.from_exe(sys.executable, app_data, raise_on_error=True, resolve_to_host=True) | |
| 297 return cls._current_system | |
| 298 | |
| 299 def _to_json(self): | |
| 300 # don't save calculated paths, as these are non primitive types | |
| 301 return json.dumps(self._to_dict(), indent=2) | |
| 302 | |
| 303 def _to_dict(self): | |
| 304 data = {var: (getattr(self, var) if var not in ("_creators",) else None) for var in vars(self)} | |
| 305 # noinspection PyProtectedMember | |
| 306 data["version_info"] = data["version_info"]._asdict() # namedtuple to dictionary | |
| 307 return data | |
| 308 | |
| 309 @classmethod | |
| 310 def from_exe(cls, exe, app_data=None, raise_on_error=True, ignore_cache=False, resolve_to_host=True): | |
| 311 """Given a path to an executable get the python information""" | |
| 312 # this method is not used by itself, so here and called functions can import stuff locally | |
| 313 from virtualenv.discovery.cached_py_info import from_exe | |
| 314 | |
| 315 proposed = from_exe(cls, app_data, exe, raise_on_error=raise_on_error, ignore_cache=ignore_cache) | |
| 316 # noinspection PyProtectedMember | |
| 317 if isinstance(proposed, PythonInfo) and resolve_to_host: | |
| 318 try: | |
| 319 proposed = proposed._resolve_to_system(app_data, proposed) | |
| 320 except Exception as exception: | |
| 321 if raise_on_error: | |
| 322 raise exception | |
| 323 logging.info("ignore %s due cannot resolve system due to %r", proposed.original_executable, exception) | |
| 324 proposed = None | |
| 325 return proposed | |
| 326 | |
| 327 @classmethod | |
| 328 def _from_json(cls, payload): | |
| 329 # the dictionary unroll here is to protect against pypy bug of interpreter crashing | |
| 330 raw = json.loads(payload) | |
| 331 return cls._from_dict({k: v for k, v in raw.items()}) | |
| 332 | |
| 333 @classmethod | |
| 334 def _from_dict(cls, data): | |
| 335 data["version_info"] = VersionInfo(**data["version_info"]) # restore this to a named tuple structure | |
| 336 result = cls() | |
| 337 result.__dict__ = {k: v for k, v in data.items()} | |
| 338 return result | |
| 339 | |
| 340 @classmethod | |
| 341 def _resolve_to_system(cls, app_data, target): | |
| 342 start_executable = target.executable | |
| 343 prefixes = OrderedDict() | |
| 344 while target.system_executable is None: | |
| 345 prefix = target.real_prefix or target.base_prefix or target.prefix | |
| 346 if prefix in prefixes: | |
| 347 if len(prefixes) == 1: | |
| 348 # if we're linking back to ourselves accept ourselves with a WARNING | |
| 349 logging.info("%r links back to itself via prefixes", target) | |
| 350 target.system_executable = target.executable | |
| 351 break | |
| 352 for at, (p, t) in enumerate(prefixes.items(), start=1): | |
| 353 logging.error("%d: prefix=%s, info=%r", at, p, t) | |
| 354 logging.error("%d: prefix=%s, info=%r", len(prefixes) + 1, prefix, target) | |
| 355 raise RuntimeError("prefixes are causing a circle {}".format("|".join(prefixes.keys()))) | |
| 356 prefixes[prefix] = target | |
| 357 target = target.discover_exe(app_data, prefix=prefix, exact=False) | |
| 358 if target.executable != target.system_executable: | |
| 359 target = cls.from_exe(target.system_executable, app_data) | |
| 360 target.executable = start_executable | |
| 361 return target | |
| 362 | |
| 363 _cache_exe_discovery = {} | |
| 364 | |
| 365 def discover_exe(self, app_data, prefix, exact=True): | |
| 366 key = prefix, exact | |
| 367 if key in self._cache_exe_discovery and prefix: | |
| 368 logging.debug("discover exe from cache %s - exact %s: %r", prefix, exact, self._cache_exe_discovery[key]) | |
| 369 return self._cache_exe_discovery[key] | |
| 370 logging.debug("discover exe for %s in %s", self, prefix) | |
| 371 # we don't know explicitly here, do some guess work - our executable name should tell | |
| 372 possible_names = self._find_possible_exe_names() | |
| 373 possible_folders = self._find_possible_folders(prefix) | |
| 374 discovered = [] | |
| 375 for folder in possible_folders: | |
| 376 for name in possible_names: | |
| 377 info = self._check_exe(app_data, folder, name, exact, discovered) | |
| 378 if info is not None: | |
| 379 self._cache_exe_discovery[key] = info | |
| 380 return info | |
| 381 if exact is False and discovered: | |
| 382 info = self._select_most_likely(discovered, self) | |
| 383 folders = os.pathsep.join(possible_folders) | |
| 384 self._cache_exe_discovery[key] = info | |
| 385 logging.debug("no exact match found, chosen most similar of %s within base folders %s", info, folders) | |
| 386 return info | |
| 387 msg = "failed to detect {} in {}".format("|".join(possible_names), os.pathsep.join(possible_folders)) | |
| 388 raise RuntimeError(msg) | |
| 389 | |
| 390 def _check_exe(self, app_data, folder, name, exact, discovered): | |
| 391 exe_path = os.path.join(folder, name) | |
| 392 if not os.path.exists(exe_path): | |
| 393 return None | |
| 394 info = self.from_exe(exe_path, app_data, resolve_to_host=False, raise_on_error=False) | |
| 395 if info is None: # ignore if for some reason we can't query | |
| 396 return None | |
| 397 for item in ["implementation", "architecture", "version_info"]: | |
| 398 found = getattr(info, item) | |
| 399 searched = getattr(self, item) | |
| 400 if found != searched: | |
| 401 if item == "version_info": | |
| 402 found, searched = ".".join(str(i) for i in found), ".".join(str(i) for i in searched) | |
| 403 executable = info.executable | |
| 404 logging.debug("refused interpreter %s because %s differs %s != %s", executable, item, found, searched) | |
| 405 if exact is False: | |
| 406 discovered.append(info) | |
| 407 break | |
| 408 else: | |
| 409 return info | |
| 410 return None | |
| 411 | |
| 412 @staticmethod | |
| 413 def _select_most_likely(discovered, target): | |
| 414 # no exact match found, start relaxing our requirements then to facilitate system package upgrades that | |
| 415 # could cause this (when using copy strategy of the host python) | |
| 416 def sort_by(info): | |
| 417 # we need to setup some priority of traits, this is as follows: | |
| 418 # implementation, major, minor, micro, architecture, tag, serial | |
| 419 matches = [ | |
| 420 info.implementation == target.implementation, | |
| 421 info.version_info.major == target.version_info.major, | |
| 422 info.version_info.minor == target.version_info.minor, | |
| 423 info.architecture == target.architecture, | |
| 424 info.version_info.micro == target.version_info.micro, | |
| 425 info.version_info.releaselevel == target.version_info.releaselevel, | |
| 426 info.version_info.serial == target.version_info.serial, | |
| 427 ] | |
| 428 priority = sum((1 << pos if match else 0) for pos, match in enumerate(reversed(matches))) | |
| 429 return priority | |
| 430 | |
| 431 sorted_discovered = sorted(discovered, key=sort_by, reverse=True) # sort by priority in decreasing order | |
| 432 most_likely = sorted_discovered[0] | |
| 433 return most_likely | |
| 434 | |
| 435 def _find_possible_folders(self, inside_folder): | |
| 436 candidate_folder = OrderedDict() | |
| 437 executables = OrderedDict() | |
| 438 executables[os.path.realpath(self.executable)] = None | |
| 439 executables[self.executable] = None | |
| 440 executables[os.path.realpath(self.original_executable)] = None | |
| 441 executables[self.original_executable] = None | |
| 442 for exe in executables.keys(): | |
| 443 base = os.path.dirname(exe) | |
| 444 # following path pattern of the current | |
| 445 if base.startswith(self.prefix): | |
| 446 relative = base[len(self.prefix) :] | |
| 447 candidate_folder["{}{}".format(inside_folder, relative)] = None | |
| 448 | |
| 449 # or at root level | |
| 450 candidate_folder[inside_folder] = None | |
| 451 return list(i for i in candidate_folder.keys() if os.path.exists(i)) | |
| 452 | |
| 453 def _find_possible_exe_names(self): | |
| 454 name_candidate = OrderedDict() | |
| 455 for name in self._possible_base(): | |
| 456 for at in (3, 2, 1, 0): | |
| 457 version = ".".join(str(i) for i in self.version_info[:at]) | |
| 458 for arch in ["-{}".format(self.architecture), ""]: | |
| 459 for ext in EXTENSIONS: | |
| 460 candidate = "{}{}{}{}".format(name, version, arch, ext) | |
| 461 name_candidate[candidate] = None | |
| 462 return list(name_candidate.keys()) | |
| 463 | |
| 464 def _possible_base(self): | |
| 465 possible_base = OrderedDict() | |
| 466 basename = os.path.splitext(os.path.basename(self.executable))[0].rstrip(digits) | |
| 467 possible_base[basename] = None | |
| 468 possible_base[self.implementation] = None | |
| 469 # python is always the final option as in practice is used by multiple implementation as exe name | |
| 470 if "python" in possible_base: | |
| 471 del possible_base["python"] | |
| 472 possible_base["python"] = None | |
| 473 for base in possible_base: | |
| 474 lower = base.lower() | |
| 475 yield lower | |
| 476 from virtualenv.info import fs_is_case_sensitive | |
| 477 | |
| 478 if fs_is_case_sensitive(): | |
| 479 if base != lower: | |
| 480 yield base | |
| 481 upper = base.upper() | |
| 482 if upper != base: | |
| 483 yield upper | |
| 484 | |
| 485 | |
| 486 if __name__ == "__main__": | |
| 487 # dump a JSON representation of the current python | |
| 488 # noinspection PyProtectedMember | |
| 489 print(PythonInfo()._to_json()) |
