86 lines
3.1 KiB
Python
86 lines
3.1 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
|
|
from virtualenv.discovery.builtin import Builtin
|
|
from virtualenv.discovery.builtin import check_path
|
|
from virtualenv.discovery.builtin import get_paths
|
|
from virtualenv.discovery.builtin import LazyPathDump
|
|
from virtualenv.discovery.builtin import PathPythonInfo
|
|
from virtualenv.discovery.builtin import possible_specs
|
|
from virtualenv.discovery.py_info import PythonInfo
|
|
from virtualenv.discovery.py_spec import PythonSpec
|
|
|
|
|
|
# Same as Builtin, needed for scope
|
|
class Mock(Builtin):
|
|
def run(self):
|
|
for python_spec in self.python_spec:
|
|
result = get_interpreter(python_spec, self.try_first_with, self.app_data, self._env)
|
|
if result is not None:
|
|
return result
|
|
return None
|
|
|
|
# Same as builtin, needed for scope
|
|
|
|
|
|
def get_interpreter(key, try_first_with, app_data=None, env=None):
|
|
spec = PythonSpec.from_string_spec(key)
|
|
logging.info('find interpreter for spec %r', spec)
|
|
proposed_paths = set()
|
|
env = os.environ if env is None else env
|
|
for interpreter, impl_must_match in propose_interpreters(spec, try_first_with, app_data, env):
|
|
key = interpreter.system_executable, impl_must_match
|
|
if key in proposed_paths:
|
|
continue
|
|
logging.info('proposed %s', interpreter)
|
|
if interpreter.satisfies(spec, impl_must_match):
|
|
logging.debug('accepted %s', interpreter)
|
|
return interpreter
|
|
proposed_paths.add(key)
|
|
|
|
# Current interpreter is removed
|
|
|
|
|
|
def propose_interpreters(spec, try_first_with, app_data, env=None):
|
|
# 0. try with first
|
|
env = os.environ if env is None else env
|
|
for py_exe in try_first_with:
|
|
path = os.path.abspath(py_exe)
|
|
try:
|
|
# Windows Store Python does not work with os.path.exists, but does for os.lstat
|
|
os.lstat(path)
|
|
except OSError:
|
|
pass
|
|
else:
|
|
yield PythonInfo.from_exe(os.path.abspath(path), app_data, env=env), True
|
|
|
|
# 1. if it's a path and exists
|
|
if spec.path is not None:
|
|
try:
|
|
# Windows Store Python does not work with os.path.exists, but does for os.lstat
|
|
os.lstat(spec.path)
|
|
except OSError:
|
|
if spec.is_abs:
|
|
raise
|
|
else:
|
|
yield PythonInfo.from_exe(os.path.abspath(spec.path), app_data, env=env), True
|
|
if spec.is_abs:
|
|
return
|
|
# finally just find on path, the path order matters (as the candidates are less easy to control by end user)
|
|
paths = get_paths(env)
|
|
tested_exes = set()
|
|
for pos, path in enumerate(paths):
|
|
path_str = str(path)
|
|
logging.debug(LazyPathDump(pos, path_str, env))
|
|
for candidate, match in possible_specs(spec):
|
|
found = check_path(candidate, path_str)
|
|
if found is not None:
|
|
exe = os.path.abspath(found)
|
|
if exe not in tested_exes:
|
|
tested_exes.add(exe)
|
|
interpreter = PathPythonInfo.from_exe(exe, app_data, raise_on_error=False, env=env)
|
|
if interpreter is not None:
|
|
yield interpreter, match
|