예제 #1
0
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)
예제 #2
0
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)
예제 #3
0
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