def _matched_interpreters_iter(interpreters_iter, constraints): candidates = [] failures = [] found = False for interpreter in interpreters_iter: if isinstance(interpreter, PythonInterpreter): candidates.append(interpreter) if any(interpreter.identity.matches(filt) for filt in constraints): TRACER.log( "Constraints on interpreters: %s, Matching Interpreter: %s" % (constraints, interpreter.binary), V=3, ) found = True yield interpreter else: failures.append(interpreter) if not found: raise UnsatisfiableInterpreterConstraintsError(constraints, candidates, failures)
def iter_compatible_interpreters( path=None, # type: Optional[str] valid_basenames=None, # type: Optional[Iterable[str]] interpreter_constraints=None, # type: Optional[Iterable[str]] preferred_interpreter=None, # type: Optional[PythonInterpreter] ): # type: (...) -> Iterator[PythonInterpreter] """Find all compatible interpreters on the system within the supplied constraints. :param path: A PATH-style string with files or directories separated by os.pathsep. :param valid_basenames: Valid basenames for discovered interpreter binaries. If not specified, Then all typical names are accepted (i.e.: python, python3, python2.7, pypy, etc.). :param interpreter_constraints: Interpreter type and version constraint strings as described in `--interpreter-constraint`. :param preferred_interpreter: For testing - an interpreter to prefer amongst all others. Defaults to the current running interpreter. Interpreters are searched for in `path` if specified and $PATH if not. If no interpreters are found and there are no further constraints (neither `valid_basenames` nor `interpreter_constraints` is specified) then the returned iterator will be empty. However, if there are constraints specified, the returned iterator, although emtpy, will raise `UnsatisfiableInterpreterConstraintsError` to provide information about any found interpreters that did not match all the constraints. """ _valid_path = None # type: Optional[PathFilter] if valid_basenames: _valid_basenames = frozenset(cast("Iterable[str]", valid_basenames)) _valid_path = (lambda interpreter_path: os.path.basename( interpreter_path) in _valid_basenames) def _iter_interpreters(): # type: () -> Iterator[InterpreterOrError] seen = set() normalized_paths = (OrderedSet( PythonInterpreter.canonicalize_path(p) for p in path.split(os.pathsep)) if path else None) # Prefer the current interpreter, if valid. current_interpreter = preferred_interpreter or PythonInterpreter.get() if not _valid_path or _valid_path(current_interpreter.binary): if normalized_paths: candidate_paths = frozenset( (current_interpreter.binary, os.path.dirname(current_interpreter.binary))) candidate_paths_in_path = candidate_paths.intersection( normalized_paths) if candidate_paths_in_path: # In case the full path of the current interpreter binary was in the # `normalized_paths` we're searching, remove it to prevent identifying it again # just to then skip it as `seen`. normalized_paths.discard(current_interpreter.binary) seen.add(current_interpreter) yield current_interpreter else: seen.add(current_interpreter) yield current_interpreter for interp in PythonInterpreter.iter_candidates( paths=normalized_paths, path_filter=_valid_path): if interp not in seen: seen.add(interp) yield interp def _valid_interpreter(interp_or_error): # type: (InterpreterOrError) -> bool if not isinstance(interp_or_error, PythonInterpreter): return False if not interpreter_constraints: return True interp = cast(PythonInterpreter, interp_or_error) if any( interp.identity.matches(interpreter_constraint) for interpreter_constraint in interpreter_constraints): TRACER.log( "Constraints on interpreters: {}, Matching Interpreter: {}". format(interpreter_constraints, interp.binary), V=3, ) return True return False candidates = [] # type: List[PythonInterpreter] failures = [] # type: List[InterpreterIdentificationError] found = False for interpreter_or_error in _iter_interpreters(): if isinstance(interpreter_or_error, PythonInterpreter): interpreter = cast(PythonInterpreter, interpreter_or_error) candidates.append(interpreter) if _valid_interpreter(interpreter_or_error): found = True yield interpreter else: error = cast("InterpreterIdentificationError", interpreter_or_error) failures.append(error) if not found and (interpreter_constraints or valid_basenames): constraints = [] # type: List[str] if interpreter_constraints: constraints.append("Version matches {}".format( " or ".join(interpreter_constraints))) if valid_basenames: constraints.append("Basename is {}".format( " or ".join(valid_basenames))) raise UnsatisfiableInterpreterConstraintsError(constraints, candidates, failures)
def find_compatible_interpreter(interpreter_constraints=None): # type: (Optional[Iterable[str]]) -> PythonInterpreter current_interpreter = PythonInterpreter.get() target = current_interpreter # type: Optional[PythonInterpreter] with TRACER.timed("Selecting runtime interpreter", V=3): if ENV.PEX_PYTHON and not ENV.PEX_PYTHON_PATH: # preserve PEX_PYTHON re-exec for backwards compatibility # TODO: Kill this off completely in favor of PEX_PYTHON_PATH # https://github.com/pantsbuild/pex/issues/431 TRACER.log( "Using PEX_PYTHON={} constrained by {}".format( ENV.PEX_PYTHON, interpreter_constraints), V=3, ) try: if os.path.isabs(ENV.PEX_PYTHON): target = _select_path_interpreter( path=ENV.PEX_PYTHON, interpreter_constraints=interpreter_constraints, ) else: target = _select_path_interpreter( valid_basenames=(os.path.basename(ENV.PEX_PYTHON), ), interpreter_constraints=interpreter_constraints, ) except UnsatisfiableInterpreterConstraintsError as e: raise e.with_preamble( "Failed to find a compatible PEX_PYTHON={pex_python}.". format(pex_python=ENV.PEX_PYTHON)) elif ENV.PEX_PYTHON_PATH or interpreter_constraints: TRACER.log( "Using {path} constrained by {constraints}".format( path="PEX_PYTHON_PATH={}".format(ENV.PEX_PYTHON_PATH) if ENV.PEX_PYTHON_PATH else "$PATH", constraints=interpreter_constraints, ), V=3, ) try: target = _select_path_interpreter( path=ENV.PEX_PYTHON_PATH, interpreter_constraints=interpreter_constraints) except UnsatisfiableInterpreterConstraintsError as e: raise e.with_preamble( "Failed to find compatible interpreter on path {path}.". format(path=ENV.PEX_PYTHON_PATH or os.getenv("PATH"))) if target is None: # N.B.: This can only happen when PEX_PYTHON_PATH is set and interpreter_constraints # is empty, but we handle all constraints generally for sanity sake. constraints = [] if ENV.PEX_PYTHON: constraints.append("PEX_PYTHON={}".format(ENV.PEX_PYTHON)) if ENV.PEX_PYTHON_PATH: constraints.append("PEX_PYTHON_PATH={}".format( ENV.PEX_PYTHON_PATH)) if interpreter_constraints: constraints.append("Version matches {}".format( " or ".join(interpreter_constraints))) raise UnsatisfiableInterpreterConstraintsError( constraints=constraints, candidates=[current_interpreter], failures=[], preamble="Could not find a compatible interpreter.", ) return target