<commit_before># Copyright 2014 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

import logging
import os
import shutil
from collections import defaultdict
from textwrap import dedent

from pex.interpreter import PythonInterpreter

from pants.backend.python.targets.python_target import PythonTarget
from pants.base.exceptions import TaskError
from pants.process.lock import OwnerPrintingInterProcessFileLock
from pants.python.python_setup import PythonSetup
from pants.subsystem.subsystem import Subsystem
from pants.util.dirutil import safe_concurrent_creation, safe_mkdir
from pants.util.memo import memoized_property

logger = logging.getLogger(__name__)


# TODO: Move under subsystems/ .
class PythonInterpreterCache(Subsystem):
    """Finds Python interpreters on the local system."""

    options_scope = "python-interpreter-cache"

    @classmethod
    def subsystem_dependencies(cls):
        return super().subsystem_dependencies() + (PythonSetup,)

    class UnsatisfiableInterpreterConstraintsError(TaskError):
        """Indicates a Python interpreter matching given constraints could not be located."""

    @staticmethod
    def _matches(interpreter, filters=()):
        return not filters or any(interpreter.identity.matches(filt) for filt in filters)

    @classmethod
    def _matching(cls, interpreters, filters=()):
        for interpreter in interpreters:
            if cls._matches(interpreter, filters=filters):
                yield interpreter

    @property
    def python_setup(self):
        return PythonSetup.global_instance()

    @memoized_property
    def _cache_dir(self):
        cache_dir = self.python_setup.interpreter_cache_dir
        safe_mkdir(cache_dir)
        return cache_dir

    def partition_targets_by_compatibility(self, targets):
        """Partition targets by their compatibility constraints.

        :param targets: a list of `PythonTarget` objects
        :returns: (tgts_by_compatibilities, filters): a dict that maps compatibility constraints
          to a list of matching targets, the aggregate set of compatibility constraints imposed
          by the target set
        :rtype: (dict(str, list), set)
        """
        tgts_by_compatibilities = defaultdict(list)
        filters = set()

        for target in targets:
            if isinstance(target, PythonTarget):
                c = self.python_setup.compatibility_or_constraints(target.compatibility)
                tgts_by_compatibilities[c].append(target)
                filters.update(c)
        return tgts_by_compatibilities, filters

    def select_interpreter_for_targets(self, targets):
        """Pick an interpreter compatible with all the specified targets."""

        tgts_by_compatibilities, total_filter_set = self.partition_targets_by_compatibility(targets)
        allowed_interpreters = set(self.setup(filters=total_filter_set))

        # Constrain allowed_interpreters based on each target's compatibility requirements.
        for compatibility in tgts_by_compatibilities:
            compatible_with_target = set(self._matching(allowed_interpreters, compatibility))
            allowed_interpreters &= compatible_with_target

        if not allowed_interpreters:
            # Create a helpful error message.
            all_interpreter_version_strings = sorted(
                {
                    interpreter.version_string
                    # NB: self.setup() requires filters to be passed, or else it will use the global interpreter
                    # constraints. We allow any interpreter other than CPython 3.0-3.3, which is known to choke
                    # with Pants.
                    for interpreter in self.setup(filters=("CPython<3", "CPython>=3.3", "PyPy"))
                }
            )
            unique_compatibilities = {tuple(c) for c in tgts_by_compatibilities.keys()}
            unique_compatibilities_strs = [",".join(x) for x in unique_compatibilities if x]
            tgts_by_compatibilities_strs = [
                t[0].address.spec for t in tgts_by_compatibilities.values()
            ]
            raise self.UnsatisfiableInterpreterConstraintsError(
                dedent(
                    """\
                    Unable to detect a suitable interpreter for compatibilities: {} (Conflicting targets: {})
            
                    Pants detected these interpreter versions on your system: {}
            
                    Possible ways to fix this:
                    * Modify your Python interpreter constraints by following https://www.pantsbuild.org/python_readme.html#configure-the-python-version.
                    * Ensure the targeted Python version is installed and discoverable.
                    * Modify Pants' interpreter search paths via --pants-setup-interpreter-search-paths.""".format(
                        " && ".join(sorted(unique_compatibilities_strs)),
                        ", ".join(tgts_by_compatibilities_strs),
                        ", ".join(all_interpreter_version_strings),
                    )
                )<commit_msg>Fix typo in V1 Python interpreter selection error message (#9589)<commit_after># Copyright 2014 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

import logging
import os
import shutil
from collections import defaultdict
from textwrap import dedent

from pex.interpreter import PythonInterpreter

from pants.backend.python.targets.python_target import PythonTarget
from pants.base.exceptions import TaskError
from pants.process.lock import OwnerPrintingInterProcessFileLock
from pants.python.python_setup import PythonSetup
from pants.subsystem.subsystem import Subsystem
from pants.util.dirutil import safe_concurrent_creation, safe_mkdir
from pants.util.memo import memoized_property

logger = logging.getLogger(__name__)


# TODO: Move under subsystems/ .
class PythonInterpreterCache(Subsystem):
    """Finds Python interpreters on the local system."""

    options_scope = "python-interpreter-cache"

    @classmethod
    def subsystem_dependencies(cls):
        return super().subsystem_dependencies() + (PythonSetup,)

    class UnsatisfiableInterpreterConstraintsError(TaskError):
        """Indicates a Python interpreter matching given constraints could not be located."""

    @staticmethod
    def _matches(interpreter, filters=()):
        return not filters or any(interpreter.identity.matches(filt) for filt in filters)

    @classmethod
    def _matching(cls, interpreters, filters=()):
        for interpreter in interpreters:
            if cls._matches(interpreter, filters=filters):
                yield interpreter

    @property
    def python_setup(self):
        return PythonSetup.global_instance()

    @memoized_property
    def _cache_dir(self):
        cache_dir = self.python_setup.interpreter_cache_dir
        safe_mkdir(cache_dir)
        return cache_dir

    def partition_targets_by_compatibility(self, targets):
        """Partition targets by their compatibility constraints.

        :param targets: a list of `PythonTarget` objects
        :returns: (tgts_by_compatibilities, filters): a dict that maps compatibility constraints
          to a list of matching targets, the aggregate set of compatibility constraints imposed
          by the target set
        :rtype: (dict(str, list), set)
        """
        tgts_by_compatibilities = defaultdict(list)
        filters = set()

        for target in targets:
            if isinstance(target, PythonTarget):
                c = self.python_setup.compatibility_or_constraints(target.compatibility)
                tgts_by_compatibilities[c].append(target)
                filters.update(c)
        return tgts_by_compatibilities, filters

    def select_interpreter_for_targets(self, targets):
        """Pick an interpreter compatible with all the specified targets."""

        tgts_by_compatibilities, total_filter_set = self.partition_targets_by_compatibility(targets)
        allowed_interpreters = set(self.setup(filters=total_filter_set))

        # Constrain allowed_interpreters based on each target's compatibility requirements.
        for compatibility in tgts_by_compatibilities:
            compatible_with_target = set(self._matching(allowed_interpreters, compatibility))
            allowed_interpreters &= compatible_with_target

        if not allowed_interpreters:
            # Create a helpful error message.
            all_interpreter_version_strings = sorted(
                {
                    interpreter.version_string
                    # NB: self.setup() requires filters to be passed, or else it will use the global interpreter
                    # constraints. We allow any interpreter other than CPython 3.0-3.3, which is known to choke
                    # with Pants.
                    for interpreter in self.setup(filters=("CPython<3", "CPython>=3.3", "PyPy"))
                }
            )
            unique_compatibilities = {tuple(c) for c in tgts_by_compatibilities.keys()}
            unique_compatibilities_strs = [",".join(x) for x in unique_compatibilities if x]
            tgts_by_compatibilities_strs = [
                t[0].address.spec for t in tgts_by_compatibilities.values()
            ]
            raise self.UnsatisfiableInterpreterConstraintsError(
                dedent(
                    """\
                    Unable to detect a suitable interpreter for compatibilities: {} (Conflicting targets: {})
            
                    Pants detected these interpreter versions on your system: {}
            
                    Possible ways to fix this:
                    * Modify your Python interpreter constraints by following https://www.pantsbuild.org/python_readme.html#configure-the-python-version.
                    * Ensure the targeted Python version is installed and discoverable.
                    * Modify Pants' interpreter search paths via --python-setup-interpreter-search-paths.""".format(
                        " && ".join(sorted(unique_compatibilities_strs)),
                        ", ".join(tgts_by_compatibilities_strs),
                        ", ".join(all_interpreter_version_strings),
                    )
                )
{'fraction_non_alphanumeric': 0.06956942428640542, 'fraction_numerical': 0.003193033381712627, 'mean_word_length': 2.63943661971831, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6982314', 'n_tokens_mistral': 2664, 'n_tokens_neox': 2513, 'n_words': 727}