# -*- coding=utf-8 -*-
"""
Backports and helper functionality to support using new functionality.
"""
from __future__ import absolute_import, print_function

import atexit
import contextlib
import functools
import inspect
import os
import re
import sys
import types

import six
from packaging import specifiers

from .environment import MYPY_RUNNING
from .utils import (
    call_function_with_correct_args,
    filter_allowed_args,
    get_allowed_args,
    get_method_args,
    nullcontext,
    suppress_setattr,
)

if sys.version_info[:2] < (3, 5):
    from backports.tempfile import TemporaryDirectory
else:
    from tempfile import TemporaryDirectory

if six.PY3:
    from contextlib import ExitStack
else:
    from contextlib2 import ExitStack


if MYPY_RUNNING:
    from optparse import Values
    from requests import Session
    from typing import (
        Any,
        Callable,
        Dict,
        Generator,
        Generic,
        Iterable,
        Iterator,
        List,
        Optional,
        Tuple,
        Type,
        TypeVar,
        Union,
    )
    from .utils import TShimmedPath, TShim, TShimmedFunc

    TFinder = TypeVar("TFinder")
    TResolver = TypeVar("TResolver")
    TReqTracker = TypeVar("TReqTracker")
    TReqSet = TypeVar("TReqSet")
    TLink = TypeVar("TLink")
    TSession = TypeVar("TSession", bound=Session)
    TCommand = TypeVar("TCommand", covariant=True)
    TCommandInstance = TypeVar("TCommandInstance")
    TCmdDict = Dict[str, Union[Tuple[str, str, str], TCommandInstance]]
    TInstallRequirement = TypeVar("TInstallRequirement")
    TFormatControl = TypeVar("TFormatControl")
    TShimmedCmdDict = Union[TShim, TCmdDict]
    TWheelCache = TypeVar("TWheelCache")
    TPreparer = TypeVar("TPreparer")


class SearchScope(object):
    def __init__(self, find_links=None, index_urls=None):
        self.index_urls = index_urls if index_urls else []
        self.find_links = find_links

    @classmethod
    def create(cls, find_links=None, index_urls=None):
        if not index_urls:
            index_urls = ["https://pypi.org/simple"]
        return cls(find_links=find_links, index_urls=index_urls)


class SelectionPreferences(object):
    def __init__(
        self,
        allow_yanked=True,
        allow_all_prereleases=False,
        format_control=None,
        prefer_binary=False,
        ignore_requires_python=False,
    ):
        self.allow_yanked = allow_yanked
        self.allow_all_prereleases = allow_all_prereleases
        self.format_control = format_control
        self.prefer_binary = prefer_binary
        self.ignore_requires_python = ignore_requires_python


class TargetPython(object):
    fallback_get_tags = None  # type: Optional[TShimmedFunc]

    def __init__(
        self,
        platform=None,  # type: Optional[str]
        py_version_info=None,  # type: Optional[Tuple[int, ...]]
        abi=None,  # type: Optional[str]
        implementation=None,  # type: Optional[str]
    ):
        # type: (...) -> None
        self._given_py_version_info = py_version_info
        if py_version_info is None:
            py_version_info = sys.version_info[:3]
        elif len(py_version_info) < 3:
            py_version_info += (3 - len(py_version_info)) * (0,)
        else:
            py_version_info = py_version_info[:3]
        py_version = ".".join(map(str, py_version_info[:2]))
        self.abi = abi
        self.implementation = implementation
        self.platform = platform
        self.py_version = py_version
        self.py_version_info = py_version_info
        self._valid_tags = None

    def get_tags(self):
        if self._valid_tags is None and self.fallback_get_tags:
            fallback_func = resolve_possible_shim(self.fallback_get_tags)
            versions = None
            if self._given_py_version_info:
                versions = ["".join(map(str, self._given_py_version_info[:2]))]
            self._valid_tags = fallback_func(
                versions=versions,
                platform=self.platform,
                abi=self.abi,
                impl=self.implementation,
            )
        return self._valid_tags


class CandidatePreferences(object):
    def __init__(self, prefer_binary=False, allow_all_prereleases=False):
        self.prefer_binary = prefer_binary
        self.allow_all_prereleases = allow_all_prereleases


class LinkCollector(object):
    def __init__(self, session=None, search_scope=None):
        self.session = session
        self.search_scope = search_scope


class CandidateEvaluator(object):
    @classmethod
    def create(
        cls,
        project_name,  # type: str
        target_python=None,  # type: Optional[TargetPython]
        prefer_binary=False,  # type: bool
        allow_all_prereleases=False,  # type: bool
        specifier=None,  # type: Optional[specifiers.BaseSpecifier]
        hashes=None,  # type: Optional[Any]
    ):
        if target_python is None:
            target_python = TargetPython()
        if specifier is None:
            specifier = specifiers.SpecifierSet()

        supported_tags = target_python.get_tags()

        return cls(
            project_name=project_name,
            supported_tags=supported_tags,
            specifier=specifier,
            prefer_binary=prefer_binary,
            allow_all_prereleases=allow_all_prereleases,
            hashes=hashes,
        )

    def __init__(
        self,
        project_name,  # type: str
        supported_tags,  # type: List[Any]
        specifier,  # type: specifiers.BaseSpecifier
        prefer_binary=False,  # type: bool
        allow_all_prereleases=False,  # type: bool
        hashes=None,  # type: Optional[Any]
    ):
        self._allow_all_prereleases = allow_all_prereleases
        self._hashes = hashes
        self._prefer_binary = prefer_binary
        self._project_name = project_name
        self._specifier = specifier
        self._supported_tags = supported_tags


class LinkEvaluator(object):
    def __init__(
        self,
        allow_yanked,
        project_name,
        canonical_name,
        formats,
        target_python,
        ignore_requires_python=False,
        ignore_compatibility=True,
    ):
        self._allow_yanked = allow_yanked
        self._canonical_name = canonical_name
        self._ignore_requires_python = ignore_requires_python
        self._formats = formats
        self._target_python = target_python
        self._ignore_compatibility = ignore_compatibility

        self.project_name = project_name


class InvalidWheelFilename(Exception):
    """Wheel Filename is Invalid"""


class Wheel(object):
    wheel_file_re = re.compile(
        r"""^(?P<namever>(?P<name>.+?)-(?P<ver>.*?))
        ((-(?P<build>\d[^-]*?))?-(?P<pyver>.+?)-(?P<abi>.+?)-(?P<plat>.+?)
        \.whl|\.dist-info)$""",
        re.VERBOSE,
    )

    def __init__(self, filename):
        # type: (str) -> None
        wheel_info = self.wheel_file_re.match(filename)
        if not wheel_info:
            raise InvalidWheelFilename("%s is not a valid wheel filename." % filename)
        self.filename = filename
        self.name = wheel_info.group("name").replace("_", "-")
        # we'll assume "_" means "-" due to wheel naming scheme
        # (https://github.com/pypa/pip/issues/1150)
        self.version = wheel_info.group("ver").replace("_", "-")
        self.build_tag = wheel_info.group("build")
        self.pyversions = wheel_info.group("pyver").split(".")
        self.abis = wheel_info.group("abi").split(".")
        self.plats = wheel_info.group("plat").split(".")

        # All the tag combinations from this file
        self.file_tags = {
            (x, y, z) for x in self.pyversions for y in self.abis for z in self.plats
        }

    def get_formatted_file_tags(self):
        # type: () -> List[str]
        """
        Return the wheel's tags as a sorted list of strings.
        """
        return sorted("-".join(tag) for tag in self.file_tags)

    def support_index_min(self, tags):
        # type: (List[Any]) -> int
        """
        Return the lowest index that one of the wheel's file_tag combinations
        achieves in the given list of supported tags.

        For example, if there are 8 supported tags and one of the file tags
        is first in the list, then return 0.

        :param tags: the PEP 425 tags to check the wheel against, in order
            with most preferred first.
        :raises ValueError: If none of the wheel's file tags match one of
            the supported tags.
        """
        return min(tags.index(tag) for tag in self.file_tags if tag in tags)

    def supported(self, tags):
        # type: (List[Any]) -> bool
        """
        Return whether the wheel is compatible with one of the given tags.

        :param tags: the PEP 425 tags to check the wheel against.
        """
        return not self.file_tags.isdisjoint(tags)


def resolve_possible_shim(target):
    # type: (TShimmedFunc) -> Optional[Union[Type, Callable]]
    if target is None:
        return target
    if getattr(target, "shim", None) and isinstance(
        target.shim, (types.MethodType, types.FunctionType)
    ):
        return target.shim()
    return target


@contextlib.contextmanager
def temp_environ():
    """Allow the ability to set os.environ temporarily"""
    environ = dict(os.environ)
    try:
        yield
    finally:
        os.environ.clear()
        os.environ.update(environ)


@contextlib.contextmanager
def get_requirement_tracker(req_tracker_creator=None):
    # type: (Optional[Callable]) -> Generator[Optional[TReqTracker], None, None]
    root = os.environ.get("PIP_REQ_TRACKER")
    if not req_tracker_creator:
        yield None
    else:
        req_tracker_args = []
        _, required_args = get_method_args(req_tracker_creator.__init__)  # type: ignore
        with ExitStack() as ctx:
            if root is None:
                root = ctx.enter_context(TemporaryDirectory(prefix="req-tracker"))
                if root:
                    root = str(root)
                    ctx.enter_context(temp_environ())
                    os.environ["PIP_REQ_TRACKER"] = root
            if required_args is not None and "root" in required_args:
                req_tracker_args.append(root)
            with req_tracker_creator(*req_tracker_args) as tracker:
                yield tracker


@contextlib.contextmanager
def ensure_resolution_dirs(**kwargs):
    # type: (Any) -> Iterator[Dict[str, Any]]
    """
    Ensures that the proper directories are scaffolded and present in the provided kwargs
    for performing dependency resolution via pip.

    :return: A new kwargs dictionary with scaffolded directories for **build_dir**, **src_dir**,
        **download_dir**, and **wheel_download_dir** added to the key value pairs.
    :rtype: Dict[str, Any]
    """
    keys = ("build_dir", "src_dir", "download_dir", "wheel_download_dir")
    if not any(kwargs.get(key) is None for key in keys):
        yield kwargs
    else:
        with TemporaryDirectory(prefix="pip-shims-") as base_dir:
            for key in keys:
                if kwargs.get(key) is not None:
                    continue
                target = os.path.join(base_dir, key)
                os.makedirs(target)
                kwargs[key] = target
            yield kwargs


@contextlib.contextmanager
def wheel_cache(
    cache_dir=None,  # type: str
    format_control=None,  # type: Any
    wheel_cache_provider=None,  # type: TShimmedFunc
    format_control_provider=None,  # type: Optional[TShimmedFunc]
    tempdir_manager_provider=None,  # type: TShimmedFunc
):
    tempdir_manager_provider = resolve_possible_shim(tempdir_manager_provider)
    wheel_cache_provider = resolve_possible_shim(wheel_cache_provider)
    format_control_provider = resolve_possible_shim(format_control_provider)
    if not format_control and not format_control_provider:
        raise TypeError("Format control or provider needed for wheel cache!")
    if not format_control:
        format_control = format_control_provider(None, None)
    with ExitStack() as ctx:
        ctx.enter_context(tempdir_manager_provider())
        wheel_cache = wheel_cache_provider(cache_dir, format_control)
        yield wheel_cache


def partial_command(shimmed_path, cmd_mapping=None):
    # type: (Type, Optional[TShimmedCmdDict]) -> Union[Type[TCommandInstance], functools.partial]
    """
    Maps a default set of arguments across all members of a
    :class:`~pip_shims.models.ShimmedPath` instance, specifically for
    :class:`~pip._internal.command.Command` instances which need
    `summary` and `name` arguments.

    :param :class:`~pip_shims.models.ShimmedPath` shimmed_path:  A
        :class:`~pip_shims.models.ShimmedCollection` instance
    :param Any cmd_mapping: A reference to use for mapping against, e.g. an
        import that depends on pip also
    :return: A dictionary mapping new arguments to their default values
    :rtype: Dict[str, str]
    """
    basecls = shimmed_path.shim()
    resolved_cmd_mapping = None  # type: Optional[Dict[str, Any]]
    cmd_mapping = resolve_possible_shim(cmd_mapping)
    if cmd_mapping is not None and isinstance(cmd_mapping, dict):
        resolved_cmd_mapping = cmd_mapping.copy()
    base_args = []  # type: List[str]
    for root_cls in basecls.mro():
        if root_cls.__name__ == "Command":
            _, root_init_args = get_method_args(root_cls.__init__)
            if root_init_args is not None:
                base_args = root_init_args.args
    needs_name_and_summary = any(arg in base_args for arg in ("name", "summary"))
    if not needs_name_and_summary:
        basecls.name = shimmed_path.name
        return basecls
    elif (
        not resolved_cmd_mapping
        and needs_name_and_summary
        and getattr(functools, "partialmethod", None)
    ):
        new_init = functools.partial(
            basecls.__init__, name=shimmed_path.name, summary="Summary"
        )
        basecls.__init__ = new_init
    result = basecls
    assert resolved_cmd_mapping is not None
    for command_name, command_info in resolved_cmd_mapping.items():
        if getattr(command_info, "class_name", None) == shimmed_path.name:
            summary = getattr(command_info, "summary", "Command summary")
            result = functools.partial(basecls, command_name, summary)
            break
    return result


def get_session(
    install_cmd_provider=None,  # type: Optional[TShimmedFunc]
    install_cmd=None,  # type: TCommandInstance
    options=None,  # type: Optional[Values]
):
    # type: (...) -> TSession
    session = None  # type: Optional[TSession]
    if install_cmd is None:
        assert install_cmd_provider is not None
        install_cmd_provider = resolve_possible_shim(install_cmd_provider)
        assert isinstance(install_cmd_provider, (type, functools.partial))
        install_cmd = install_cmd_provider()
    if options is None:
        options, _ = install_cmd.parser.parse_args([])  # type: ignore
    session = install_cmd._build_session(options)  # type: ignore
    assert session is not None
    atexit.register(session.close)
    return session


def populate_options(
    install_command=None,  # type: TCommandInstance
    options=None,  # type: Optional[Values]
    **kwargs  # type: Any
):
    # (...) -> Tuple[Dict[str, Any], Values]
    results = {}
    if install_command is None and options is None:
        raise TypeError("Must pass either options or InstallCommand to populate options")
    if options is None and install_command is not None:
        options, _ = install_command.parser.parse_args([])  # type: ignore
    options_dict = options.__dict__
    for provided_key, provided_value in kwargs.items():
        if provided_key == "isolated":
            options_key = "isolated_mode"
        elif provided_key == "source_dir":
            options_key = "src_dir"
        else:
            options_key = provided_key
        if provided_key in options_dict and provided_value is not None:
            setattr(options, options_key, provided_value)
            results[provided_key] = provided_value
        elif getattr(options, options_key, None) is not None:
            results[provided_key] = getattr(options, options_key)
        else:
            results[provided_key] = provided_value
    return results, options


def get_requirement_set(
    install_command=None,  # type: Optional[TCommandInstance]
    req_set_provider=None,  # type: Optional[TShimmedFunc]
    build_dir=None,  # type: Optional[str]
    src_dir=None,  # type: Optional[str]
    download_dir=None,  # type: Optional[str]
    wheel_download_dir=None,  # type: Optional[str]
    session=None,  # type: Optional[TSession]
    wheel_cache=None,  # type: Optional[TWheelCache]
    upgrade=False,  # type: bool
    upgrade_strategy=None,  # type: Optional[str]
    ignore_installed=False,  # type: bool
    ignore_dependencies=False,  # type: bool
    force_reinstall=False,  # type: bool
    use_user_site=False,  # type: bool
    isolated=False,  # type: bool
    ignore_requires_python=False,  # type: bool
    require_hashes=None,  # type: bool
    cache_dir=None,  # type: Optional[str]
    options=None,  # type: Optional[Values]
    install_cmd_provider=None,  # type: Optional[TShimmedFunc]
    wheel_cache_provider=None,  # type: Optional[TShimmedFunc]
):
    # (...) -> TRequirementSet
    """
    Creates a requirement set from the supplied parameters.

    Not all parameters are passed through for all pip versions, but any
    invalid parameters will be ignored if they are not needed to generate a
    requirement set on the current pip version.

    :param :class:`~pip_shims.models.ShimmedPathCollection` wheel_cache_provider: A
        context manager provider which resolves to a `WheelCache` instance
    :param install_command: A :class:`~pip._internal.commands.install.InstallCommand`
        instance which is used to generate the finder.
    :param :class:`~pip_shims.models.ShimmedPathCollection` req_set_provider: A provider
        to build requirement set instances.
    :param str build_dir: The directory to build requirements in. Removed in pip 10,
        defeaults to None
    :param str source_dir: The directory to use for source requirements. Removed in
        pip 10, defaults to None
    :param str download_dir: The directory to download requirement artifacts to. Removed
        in pip 10, defaults to None
    :param str wheel_download_dir: The directory to download wheels to. Removed in pip
        10, defaults ot None
    :param :class:`~requests.Session` session: The pip session to use. Removed in pip 10,
        defaults to None
    :param WheelCache wheel_cache: The pip WheelCache instance to use for caching wheels.
        Removed in pip 10, defaults to None
    :param bool upgrade: Whether to try to upgrade existing requirements. Removed in pip
        10, defaults to False.
    :param str upgrade_strategy: The upgrade strategy to use, e.g. "only-if-needed".
        Removed in pip 10, defaults to None.
    :param bool ignore_installed: Whether to ignore installed packages when resolving.
        Removed in pip 10, defaults to False.
    :param bool ignore_dependencies: Whether to ignore dependencies of requirements
        when resolving. Removed in pip 10, defaults to False.
    :param bool force_reinstall: Whether to force reinstall of packages when resolving.
        Removed in pip 10, defaults to False.
    :param bool use_user_site: Whether to use user site packages when resolving. Removed
        in pip 10, defaults to False.
    :param bool isolated: Whether to resolve in isolation. Removed in pip 10, defaults
        to False.
    :param bool ignore_requires_python: Removed in pip 10, defaults to False.
    :param bool require_hashes: Whether to require hashes when resolving. Defaults to
        False.
    :param Values options: An :class:`~optparse.Values` instance from an install cmd
    :param install_cmd_provider: A shim for providing new install command instances.
    :type install_cmd_provider: :class:`~pip_shims.models.ShimmedPathCollection`
    :return: A new requirement set instance
    :rtype: :class:`~pip._internal.req.req_set.RequirementSet`
    """
    wheel_cache_provider = resolve_possible_shim(wheel_cache_provider)
    req_set_provider = resolve_possible_shim(req_set_provider)
    if install_command is None:
        install_cmd_provider = resolve_possible_shim(install_cmd_provider)
        assert isinstance(install_cmd_provider, (type, functools.partial))
        install_command = install_cmd_provider()
    required_args = inspect.getargs(
        req_set_provider.__init__.__code__
    ).args  # type: ignore
    results, options = populate_options(
        install_command,
        options,
        build_dir=build_dir,
        src_dir=src_dir,
        download_dir=download_dir,
        upgrade=upgrade,
        upgrade_strategy=upgrade_strategy,
        ignore_installed=ignore_installed,
        ignore_dependencies=ignore_dependencies,
        force_reinstall=force_reinstall,
        use_user_site=use_user_site,
        isolated=isolated,
        ignore_requires_python=ignore_requires_python,
        require_hashes=require_hashes,
        cache_dir=cache_dir,
    )
    if session is None and "session" in required_args:
        session = get_session(install_cmd=install_command, options=options)
    with ExitStack() as stack:
        if wheel_cache is None:
            wheel_cache = stack.enter_context(wheel_cache_provider(cache_dir=cache_dir))
        results["wheel_cache"] = wheel_cache
        results["session"] = session
        results["wheel_download_dir"] = wheel_download_dir
        return call_function_with_correct_args(req_set_provider, **results)


def get_package_finder(
    install_cmd=None,  # type: Optional[TCommand]
    options=None,  # type: Optional[Values]
    session=None,  # type: Optional[TSession]
    platform=None,  # type: Optional[str]
    python_versions=None,  # type: Optional[Tuple[str, ...]]
    abi=None,  # type: Optional[str]
    implementation=None,  # type: Optional[str]
    target_python=None,  # type: Optional[Any]
    ignore_requires_python=None,  # type: Optional[bool]
    target_python_builder=None,  # type: Optional[TShimmedFunc]
    install_cmd_provider=None,  # type: Optional[TShimmedFunc]
):
    # type: (...) -> TFinder
    """Shim for compatibility to generate package finders.

    Build and return a :class:`~pip._internal.index.package_finder.PackageFinder`
    instance using the :class:`~pip._internal.commands.install.InstallCommand` helper
    method to construct the finder, shimmed with backports as needed for compatibility.

    :param install_cmd_provider: A shim for providing new install command instances.
    :type install_cmd_provider: :class:`~pip_shims.models.ShimmedPathCollection`
    :param install_cmd: A :class:`~pip._internal.commands.install.InstallCommand`
        instance which is used to generate the finder.
    :param optparse.Values options: An optional :class:`optparse.Values` instance
        generated by calling `install_cmd.parser.parse_args()` typically.
    :param session: An optional session instance, can be created by the `install_cmd`.
    :param Optional[str] platform: An optional platform string, e.g. linux_x86_64
    :param Optional[Tuple[str, ...]] python_versions: A tuple of 2-digit strings
        representing python versions, e.g. ("27", "35", "36", "37"...)
    :param Optional[str] abi: The target abi to support, e.g. "cp38"
    :param Optional[str] implementation: An optional implementation string for limiting
        searches to a specific implementation, e.g. "cp" or "py"
    :param target_python: A :class:`~pip._internal.models.target_python.TargetPython`
        instance (will be translated to alternate arguments if necessary on incompatible
        pip versions).
    :param Optional[bool] ignore_requires_python: Whether to ignore `requires_python`
        on resulting candidates, only valid after pip version 19.3.1
    :param target_python_builder: A 'TargetPython' builder (e.g. the class itself,
        uninstantiated)
    :return: A :class:`pip._internal.index.package_finder.PackageFinder` instance
    :rtype: :class:`pip._internal.index.package_finder.PackageFinder`

    :Example:

    >>> from pip_shims.shims import InstallCommand, get_package_finder
    >>> install_cmd = InstallCommand()
    >>> finder = get_package_finder(
    ...     install_cmd, python_versions=("27", "35", "36", "37", "38"), implementation="
    cp"
    ... )
    >>> candidates = finder.find_all_candidates("requests")
    >>> requests_222 = next(iter(c for c in candidates if c.version.public == "2.22.0"))
    >>> requests_222
    <InstallationCandidate('requests', <Version('2.22.0')>, <Link https://files.pythonhos
    ted.org/packages/51/bd/23c926cd341ea6b7dd0b2a00aba99ae0f828be89d72b2190f27c11d4b7fb/r
    equests-2.22.0-py2.py3-none-any.whl#sha256=9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9
    a590f48c010551dc6c4b31 (from https://pypi.org/simple/requests/) (requires-python:>=2.
    7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*)>)>
    """
    if install_cmd is None:
        install_cmd_provider = resolve_possible_shim(install_cmd_provider)
        assert isinstance(install_cmd_provider, (type, functools.partial))
        install_cmd = install_cmd_provider()
    if options is None:
        options, _ = install_cmd.parser.parse_args([])  # type: ignore
    if session is None:
        session = get_session(install_cmd=install_cmd, options=options)  # type: ignore
    builder_args = inspect.getargs(
        install_cmd._build_package_finder.__code__
    )  # type: ignore
    build_kwargs = {"options": options, "session": session}
    expects_targetpython = "target_python" in builder_args.args
    received_python = any(arg for arg in [platform, python_versions, abi, implementation])
    if expects_targetpython and received_python and not target_python:
        if target_python_builder is None:
            target_python_builder = TargetPython
        py_version_info = None
        if python_versions:
            py_version_info_python = max(python_versions)
            py_version_info = tuple([int(part) for part in py_version_info_python])
        target_python = target_python_builder(
            platform=platform,
            abi=abi,
            implementation=implementation,
            py_version_info=py_version_info,
        )
        build_kwargs["target_python"] = target_python
    elif any(
        arg in builder_args.args
        for arg in ["platform", "python_versions", "abi", "implementation"]
    ):
        if target_python and not received_python:
            tags = target_python.get_tags()
            version_impl = {t[0] for t in tags}
            # impls = set([v[:2] for v in version_impl])
            # impls.remove("py")
            # impl = next(iter(impls), "py") if not target_python
            versions = {v[2:] for v in version_impl}
            build_kwargs.update(
                {
                    "platform": target_python.platform,
                    "python_versions": versions,
                    "abi": target_python.abi,
                    "implementation": target_python.implementation,
                }
            )
    if (
        ignore_requires_python is not None
        and "ignore_requires_python" in builder_args.args
    ):
        build_kwargs["ignore_requires_python"] = ignore_requires_python
    return install_cmd._build_package_finder(**build_kwargs)  # type: ignore


def shim_unpack(
    unpack_fn,  # type: TShimmedFunc
    download_dir,  # type str
    tempdir_manager_provider,  # type: TShimmedFunc
    ireq=None,  # type: Optional[Any]
    link=None,  # type: Optional[Any]
    location=None,  # type Optional[str],
    hashes=None,  # type: Optional[Any]
    progress_bar="off",  # type: str
    only_download=None,  # type: Optional[bool]
    downloader_provider=None,  # type: Optional[TShimmedFunc]
    session=None,  # type: Optional[Any]
):
    # (...) -> None
    """
    Accepts all parameters that have been valid to pass
    to :func:`pip._internal.download.unpack_url` and selects or
    drops parameters as needed before invoking the provided
    callable.

    :param unpack_fn: A callable or shim referring to the pip implementation
    :type unpack_fn: Callable
    :param str download_dir: The directory to download the file to
    :param TShimmedFunc tempdir_manager_provider: A callable or shim referring to
        `global_tempdir_manager` function from pip or a shimmed no-op context manager
    :param Optional[:class:`~pip._internal.req.req_install.InstallRequirement`] ireq:
        an Install Requirement instance, defaults to None
    :param Optional[:class:`~pip._internal.models.link.Link`] link: A Link instance,
        defaults to None.
    :param Optional[str] location: A location or source directory if the target is
        a VCS url, defaults to None.
    :param Optional[Any] hashes: A Hashes instance, defaults to None
    :param str progress_bar: Indicates progress par usage during download, defatuls to
        off.
    :param Optional[bool] only_download: Whether to skip install, defaults to None.
    :param Optional[ShimmedPathCollection] downloader_provider: A downloader class
        to instantiate, if applicable.
    :param Optional[`~requests.Session`] session: A PipSession instance, defaults to
        None.
    :return: The result of unpacking the url.
    :rtype: None
    """
    unpack_fn = resolve_possible_shim(unpack_fn)
    downloader_provider = resolve_possible_shim(downloader_provider)
    tempdir_manager_provider = resolve_possible_shim(tempdir_manager_provider)
    required_args = inspect.getargs(unpack_fn.__code__).args  # type: ignore
    unpack_kwargs = {"download_dir": download_dir}
    with tempdir_manager_provider():
        if ireq:
            if not link and ireq.link:
                link = ireq.link
            if only_download is None:
                only_download = ireq.is_wheel
            if hashes is None:
                hashes = ireq.hashes(True)
            if location is None and getattr(ireq, "source_dir", None):
                location = ireq.source_dir
        unpack_kwargs.update({"link": link, "location": location})
        if hashes is not None and "hashes" in required_args:
            unpack_kwargs["hashes"] = hashes
        if "progress_bar" in required_args:
            unpack_kwargs["progress_bar"] = progress_bar
        if only_download is not None and "only_download" in required_args:
            unpack_kwargs["only_download"] = only_download
        if session is not None and "session" in required_args:
            unpack_kwargs["session"] = session
        if "downloader" in required_args and downloader_provider is not None:
            assert session is not None
            assert progress_bar is not None
            unpack_kwargs["downloader"] = downloader_provider(session, progress_bar)
        return unpack_fn(**unpack_kwargs)  # type: ignore


def _ensure_finder(
    finder=None,  # type: Optional[TFinder]
    finder_provider=None,  # type: Optional[Callable]
    install_cmd=None,  # type: Optional[TCommandInstance]
    options=None,  # type: Optional[Values]
    session=None,  # type: Optional[TSession]
):
    if not any([finder, finder_provider, install_cmd]):
        raise TypeError(
            "RequirementPreparer requires a packagefinder but no InstallCommand"
            " was provided to build one and none was passed in."
        )
    if finder is not None:
        return finder
    else:
        if session is None:
            session = get_session(install_cmd=install_cmd, options=options)
        if finder_provider is not None and options is not None:
            finder_provider(options=options, session=session)
        else:
            finder = get_package_finder(install_cmd, options=options, session=session)
        return finder


@contextlib.contextmanager
def make_preparer(
    preparer_fn,  # type: TShimmedFunc
    req_tracker_fn=None,  # type: Optional[TShimmedFunc]
    build_dir=None,  # type: Optional[str]
    src_dir=None,  # type: Optional[str]
    download_dir=None,  # type: Optional[str]
    wheel_download_dir=None,  # type: Optional[str]
    progress_bar="off",  # type: str
    build_isolation=False,  # type: bool
    session=None,  # type: Optional[TSession]
    finder=None,  # type: Optional[TFinder]
    options=None,  # type: Optional[Values]
    require_hashes=None,  # type: Optional[bool]
    use_user_site=None,  # type: Optional[bool]
    req_tracker=None,  # type: Optional[Union[TReqTracker, TShimmedFunc]]
    install_cmd_provider=None,  # type: Optional[TShimmedFunc]
    downloader_provider=None,  # type: Optional[TShimmedFunc]
    install_cmd=None,  # type: Optional[TCommandInstance]
    finder_provider=None,  # type: Optional[TShimmedFunc]
):
    # (...) -> ContextManager
    """
    Creates a requirement preparer for preparing pip requirements.

    Provides a compatibilty shim that accepts all previously valid arguments and
    discards any that are no longer used.

    :raises TypeError: No requirement tracker provided and one cannot be generated
    :raises TypeError: No valid sessions provided and one cannot be generated
    :raises TypeError: No valid finders provided and one cannot be generated
    :param TShimmedFunc preparer_fn: Callable or shim for generating preparers.
    :param Optional[TShimmedFunc] req_tracker_fn: Callable or shim for generating
        requirement trackers, defualts to None
    :param Optional[str] build_dir: Directory for building packages and wheels,
        defaults to None
    :param Optional[str] src_dir: Directory to find or extract source files, defaults
        to None
    :param Optional[str] download_dir: Target directory to download files, defaults to
        None
    :param Optional[str] wheel_download_dir: Target directoryto download wheels, defaults
        to None
    :param str progress_bar: Whether to display a progress bar, defaults to off
    :param bool build_isolation: Whether to build requirements in isolation, defaults
        to False
    :param Optional[TSession] session: Existing session to use for getting requirements,
        defaults to None
    :param Optional[TFinder] finder: The package finder to use during resolution,
        defaults to None
    :param Optional[Values] options: Pip options to use if needed, defaults to None
    :param Optional[bool] require_hashes: Whether to require hashes for preparation
    :param Optional[bool] use_user_site: Whether to use the user site directory for
        preparing requirements
    :param Optional[Union[TReqTracker, TShimmedFunc]] req_tracker: The requirement
        tracker to use for building packages, defaults to None
    :param Optional[TShimmedFunc] downloader_provider: A downloader provider
    :param Optional[TCommandInstance] install_cmd: The install command used to create
        the finder, session, and options if needed, defaults to None
    :param Optional[TShimmedFunc] finder_provider: A package finder provider
    :yield: A new requirement preparer instance
    :rtype: ContextManager[:class:`~pip._internal.operations.prepare.RequirementPreparer`]

    :Example:

    >>> from pip_shims.shims import (
    ...     InstallCommand, get_package_finder, make_preparer, get_requirement_tracker
    ... )
    >>> install_cmd = InstallCommand()
    >>> pip_options, _ = install_cmd.parser.parse_args([])
    >>> session = install_cmd._build_session(pip_options)
    >>> finder = get_package_finder(
    ...     install_cmd, session=session, options=pip_options
    ... )
    >>> with make_preparer(
    ...     options=pip_options, finder=finder, session=session, install_cmd=ic
    ... ) as preparer:
    ...     print(preparer)
    <pip._internal.operations.prepare.RequirementPreparer object at 0x7f8a2734be80>
    """
    preparer_fn = resolve_possible_shim(preparer_fn)
    downloader_provider = resolve_possible_shim(downloader_provider)
    finder_provider = resolve_possible_shim(finder_provider)
    required_args, required_kwargs = get_allowed_args(preparer_fn)  # type: ignore
    if not req_tracker and not req_tracker_fn and "req_tracker" in required_args:
        raise TypeError("No requirement tracker and no req tracker generator found!")
    if "downloader" in required_args and not downloader_provider:
        raise TypeError("no downloader provided, but one is required to continue!")
    req_tracker_fn = resolve_possible_shim(req_tracker_fn)
    pip_options_created = options is None
    session_is_required = "session" in required_args
    finder_is_required = "finder" in required_args
    downloader_is_required = "downloader" in required_args
    options_map = {
        "src_dir": src_dir,
        "download_dir": download_dir,
        "wheel_download_dir": wheel_download_dir,
        "build_dir": build_dir,
        "progress_bar": progress_bar,
        "build_isolation": build_isolation,
        "require_hashes": require_hashes,
        "use_user_site": use_user_site,
    }
    if install_cmd is None:
        assert install_cmd_provider is not None
        install_cmd_provider = resolve_possible_shim(install_cmd_provider)
        assert isinstance(install_cmd_provider, (type, functools.partial))
        install_cmd = install_cmd_provider()
    preparer_args, options = populate_options(install_cmd, options, **options_map)
    if options is not None and pip_options_created:
        for k, v in options_map.items():
            suppress_setattr(options, k, v, filter_none=True)
    if session_is_required:
        if session is None:
            session = get_session(install_cmd=install_cmd, options=options)
        preparer_args["session"] = session
    if finder_is_required:
        finder = _ensure_finder(
            finder=finder,
            finder_provider=finder_provider,
            install_cmd=install_cmd,
            options=options,
            session=session,
        )
        preparer_args["finder"] = finder
    if downloader_is_required:
        preparer_args["downloader"] = downloader_provider(session, progress_bar)
    req_tracker_fn = nullcontext if not req_tracker_fn else req_tracker_fn
    with req_tracker_fn() as tracker_ctx:
        if "req_tracker" in required_args:
            req_tracker = tracker_ctx if req_tracker is None else req_tracker
            preparer_args["req_tracker"] = req_tracker
        preparer_args["lazy_wheel"] = True
        result = call_function_with_correct_args(preparer_fn, **preparer_args)
        yield result


@contextlib.contextmanager
def _ensure_wheel_cache(
    wheel_cache=None,  # type: Optional[Type[TWheelCache]]
    wheel_cache_provider=None,  # type: Optional[Callable]
    format_control=None,  # type: Optional[TFormatControl]
    format_control_provider=None,  # type: Optional[Type[TShimmedFunc]]
    options=None,  # type: Optional[Values]
    cache_dir=None,  # type: Optional[str]
):
    if wheel_cache is not None:
        yield wheel_cache
    elif wheel_cache_provider is not None:
        with ExitStack() as stack:
            cache_dir = getattr(options, "cache_dir", cache_dir)
            format_control = getattr(
                options,
                "format_control",
                format_control_provider(None, None),  # TFormatControl
            )
            wheel_cache = stack.enter_context(
                wheel_cache_provider(cache_dir, format_control)
            )
            yield wheel_cache


def get_ireq_output_path(wheel_cache, ireq):
    if getattr(wheel_cache, "get_path_for_link", None):
        return wheel_cache.get_path_for_link(ireq.link)
    elif getattr(wheel_cache, "cached_wheel", None):
        return wheel_cache.cached_wheel(ireq.link, ireq.name).url_without_fragment


def get_resolver(
    resolver_fn,  # type: TShimmedFunc
    install_req_provider=None,  # type: Optional[TShimmedFunc]
    format_control_provider=None,  # type: Optional[TShimmedFunc]
    wheel_cache_provider=None,  # type: Optional[TShimmedFunc]
    finder=None,  # type: Optional[TFinder]
    upgrade_strategy="to-satisfy-only",  # type: str
    force_reinstall=None,  # type: Optional[bool]
    ignore_dependencies=None,  # type: Optional[bool]
    ignore_requires_python=None,  # type: Optional[bool]
    ignore_installed=True,  # type: bool
    use_user_site=False,  # type: bool
    isolated=None,  # type: Optional[bool]
    wheel_cache=None,  # type: Optional[TWheelCache]
    preparer=None,  # type: Optional[TPreparer]
    session=None,  # type: Optional[TSession]
    options=None,  # type: Optional[Values]
    make_install_req=None,  # type: Optional[Callable]
    install_cmd_provider=None,  # type: Optional[TShimmedFunc]
    install_cmd=None,  # type: Optional[TCommandInstance]
    use_pep517=True,  # type: bool
):
    # (...) -> TResolver
    """
    A resolver creation compatibility shim for generating a resolver.

    Consumes any argument that was previously used to instantiate a
    resolver, discards anything that is no longer valid.

    .. note:: This is only valid for **pip >= 10.0.0**

    :raises ValueError: A session is required but not provided and one cannot be created
    :raises ValueError: A finder is required but not provided and one cannot be created
    :raises ValueError: An install requirement provider is required and has not been
        provided
    :param TShimmedFunc resolver_fn: The resolver function used to create new resolver
        instances.
    :param TShimmedFunc install_req_provider: The provider function to use to generate
        install requirements if needed.
    :param TShimmedFunc format_control_provider: The provider function to use to generate
        a format_control instance if needed.
    :param TShimmedFunc wheel_cache_provider: The provider function to use to generate
        a wheel cache if needed.
    :param Optional[TFinder] finder: The package finder to use during resolution,
        defaults to None.
    :param str upgrade_strategy: Upgrade strategy to use, defaults to ``only-if-needed``.
    :param Optional[bool] force_reinstall: Whether to simulate or assume package
        reinstallation during resolution, defaults to None
    :param Optional[bool] ignore_dependencies: Whether to ignore package dependencies,
        defaults to None
    :param Optional[bool] ignore_requires_python: Whether to ignore indicated
        required_python versions on packages, defaults to None
    :param bool ignore_installed: Whether to ignore installed packages during resolution,
        defaults to True
    :param bool use_user_site: Whether to use the user site location during resolution,
        defaults to False
    :param Optional[bool] isolated: Whether to isolate the resolution process, defaults
        to None
    :param Optional[TWheelCache] wheel_cache: The wheel cache to use, defaults to None
    :param Optional[TPreparer] preparer: The requirement preparer to use, defaults to
        None
    :param Optional[TSession] session: Existing session to use for getting requirements,
        defaults to None
    :param Optional[Values] options: Pip options to use if needed, defaults to None
    :param Optional[functools.partial] make_install_req: The partial function to pass in
        to the resolver for actually generating install requirements, if necessary
    :param Optional[TCommandInstance] install_cmd: The install command used to create
        the finder, session, and options if needed, defaults to None.
    :param bool use_pep517: Whether to use the pep517 build process.
    :return: A new resolver instance.
    :rtype: :class:`~pip._internal.legacy_resolve.Resolver`

    :Example:

    >>> import os
    >>> from tempdir import TemporaryDirectory
    >>> from pip_shims.shims import (
    ...     InstallCommand, get_package_finder, make_preparer, get_requirement_tracker,
    ...     get_resolver, InstallRequirement, RequirementSet
    ... )
    >>> install_cmd = InstallCommand()
    >>> pip_options, _ = install_cmd.parser.parse_args([])
    >>> session = install_cmd._build_session(pip_options)
    >>> finder = get_package_finder(
    ...     install_cmd, session=session, options=pip_options
    ... )
    >>> wheel_cache = WheelCache(USER_CACHE_DIR, FormatControl(None, None))
    >>> with TemporaryDirectory() as temp_base:
    ...     reqset = RequirementSet()
    ...     ireq = InstallRequirement.from_line("requests")
    ...     ireq.is_direct = True
    ...     build_dir = os.path.join(temp_base, "build")
    ...     src_dir = os.path.join(temp_base, "src")
    ...     ireq.build_location(build_dir)
    ...     with make_preparer(
    ...         options=pip_options, finder=finder, session=session,
    ...         build_dir=build_dir, install_cmd=install_cmd,
    ...     ) as preparer:
    ...         resolver = get_resolver(
    ...             finder=finder, ignore_dependencies=False, ignore_requires_python=True,
    ...             preparer=preparer, session=session, options=pip_options,
    ...             install_cmd=install_cmd, wheel_cache=wheel_cache,
    ...         )
    ...         resolver.require_hashes = False
    ...         reqset.add_requirement(ireq)
    ...         results = resolver.resolve(reqset)
    ...         #reqset.cleanup_files()
    ...         for result_req in reqset.requirements:
    ...             print(result_req)
    requests
    chardet
    certifi
    urllib3
    idna
    """
    resolver_fn = resolve_possible_shim(resolver_fn)
    install_req_provider = resolve_possible_shim(install_req_provider)
    format_control_provider = resolve_possible_shim(format_control_provider)
    wheel_cache_provider = resolve_possible_shim(wheel_cache_provider)
    install_cmd_provider = resolve_possible_shim(install_cmd_provider)
    required_args = inspect.getargs(resolver_fn.__init__.__code__).args  # type: ignore
    install_cmd_dependency_map = {"session": session, "finder": finder}
    resolver_kwargs = {}  # type: Dict[str, Any]
    if install_cmd is None:
        assert isinstance(install_cmd_provider, (type, functools.partial))
        install_cmd = install_cmd_provider()
    if options is None and install_cmd is not None:
        options, _ = install_cmd.parser.parse_args([])  # type: ignore
    for arg, val in install_cmd_dependency_map.items():
        if arg not in required_args:
            continue
        elif val is None and install_cmd is None:
            raise TypeError(
                "Preparer requires a {} but did not receive one "
                "and cannot generate one".format(arg)
            )
        elif arg == "session" and val is None:
            val = get_session(install_cmd=install_cmd, options=options)
        elif arg == "finder" and val is None:
            val = get_package_finder(install_cmd, options=options, session=session)
        resolver_kwargs[arg] = val
    if "make_install_req" in required_args:
        if make_install_req is None and install_req_provider is not None:
            make_install_req_kwargs = {
                "isolated": isolated,
                "wheel_cache": wheel_cache,
                "use_pep517": use_pep517,
            }
            factory_args, factory_kwargs = filter_allowed_args(
                install_req_provider, **make_install_req_kwargs
            )
            make_install_req = functools.partial(
                install_req_provider, *factory_args, **factory_kwargs
            )
        assert make_install_req is not None
        resolver_kwargs["make_install_req"] = make_install_req
    if "isolated" in required_args:
        resolver_kwargs["isolated"] = isolated
    resolver_kwargs.update(
        {
            "upgrade_strategy": upgrade_strategy,
            "force_reinstall": force_reinstall,
            "ignore_dependencies": ignore_dependencies,
            "ignore_requires_python": ignore_requires_python,
            "ignore_installed": ignore_installed,
            "use_user_site": use_user_site,
            "preparer": preparer,
        }
    )
    if "wheel_cache" in required_args:
        with _ensure_wheel_cache(
            wheel_cache=wheel_cache,
            wheel_cache_provider=wheel_cache_provider,
            format_control_provider=format_control_provider,
            options=options,
        ) as wheel_cache:
            resolver_kwargs["wheel_cache"] = wheel_cache
            return resolver_fn(**resolver_kwargs)  # type: ignore
    return resolver_fn(**resolver_kwargs)  # type: ignore


def resolve(  # noqa:C901
    ireq,  # type: TInstallRequirement
    reqset_provider=None,  # type: Optional[TShimmedFunc]
    req_tracker_provider=None,  # type: Optional[TShimmedFunc]
    install_cmd_provider=None,  # type: Optional[TShimmedFunc]
    install_command=None,  # type: Optional[TCommand]
    finder_provider=None,  # type: Optional[TShimmedFunc]
    resolver_provider=None,  # type: Optional[TShimmedFunc]
    wheel_cache_provider=None,  # type: Optional[TShimmedFunc]
    format_control_provider=None,  # type: Optional[TShimmedFunc]
    make_preparer_provider=None,  # type: Optional[TShimmedFunc]
    tempdir_manager_provider=None,  # type: Optional[TShimmedFunc]
    options=None,  # type: Optional[Values]
    session=None,  # type: Optional[TSession]
    resolver=None,  # type: Optional[TResolver]
    finder=None,  # type: Optional[TFinder]
    upgrade_strategy="to-satisfy-only",  # type: str
    force_reinstall=None,  # type: Optional[bool]
    ignore_dependencies=None,  # type: Optional[bool]
    ignore_requires_python=None,  # type: Optional[bool]
    ignore_installed=True,  # type: bool
    use_user_site=False,  # type: bool
    isolated=None,  # type: Optional[bool]
    build_dir=None,  # type: Optional[str]
    source_dir=None,  # type: Optional[str]
    download_dir=None,  # type: Optional[str]
    cache_dir=None,  # type: Optional[str]
    wheel_download_dir=None,  # type: Optional[str]
    wheel_cache=None,  # type: Optional[TWheelCache]
    require_hashes=None,  # type: bool
    check_supported_wheels=True,  # type: bool
):
    # (...) -> Set[TInstallRequirement]
    """
    Resolves the provided **InstallRequirement**, returning a dictionary.

    Maps a dictionary of names to corresponding ``InstallRequirement`` values.

    :param :class:`~pip._internal.req.req_install.InstallRequirement` ireq: An
        InstallRequirement to initiate the resolution process
    :param :class:`~pip_shims.models.ShimmedPathCollection` reqset_provider: A provider
        to build requirement set instances.
    :param :class:`~pip_shims.models.ShimmedPathCollection` req_tracker_provider: A
        provider to build requirement tracker instances
    :param install_cmd_provider: A shim for providing new install command instances.
    :type install_cmd_provider: :class:`~pip_shims.models.ShimmedPathCollection`
    :param Optional[TCommandInstance] install_command:  The install command used to
        create the finder, session, and options if needed, defaults to None.
    :param :class:`~pip_shims.models.ShimmedPathCollection` finder_provider: A provider
        to package finder instances.
    :param :class:`~pip_shims.models.ShimmedPathCollection` resolver_provider: A provider
        to build resolver instances
    :param TShimmedFunc wheel_cache_provider: The provider function to use to generate a
        wheel cache if needed.
    :param TShimmedFunc format_control_provider: The provider function to use to generate
        a format_control instance if needed.
    :param TShimmedFunc make_preparer_provider: Callable or shim for generating preparers.
    :param Optional[TShimmedFunc] tempdir_manager_provider: Shim for generating tempdir
        manager for pip temporary directories
    :param Optional[Values] options: Pip options to use if needed, defaults to None
    :param Optional[TSession] session: Existing session to use for getting requirements,
        defaults to None
    :param :class:`~pip._internal.legacy_resolve.Resolver` resolver: A pre-existing
        resolver instance to use for resolution
    :param Optional[TFinder] finder: The package finder to use during resolution,
        defaults to None.
    :param str upgrade_strategy: Upgrade strategy to use, defaults to ``only-if-needed``.
    :param Optional[bool] force_reinstall: Whether to simulate or assume package
        reinstallation during resolution, defaults to None
    :param Optional[bool] ignore_dependencies: Whether to ignore package dependencies,
        defaults to None
    :param Optional[bool] ignore_requires_python: Whether to ignore indicated
        required_python versions on packages, defaults to None
    :param bool ignore_installed: Whether to ignore installed packages during
        resolution, defaults to True
    :param bool use_user_site: Whether to use the user site location during resolution,
        defaults to False
    :param Optional[bool] isolated: Whether to isolate the resolution process, defaults
        to None
    :param Optional[str] build_dir: Directory for building packages and wheels, defaults
        to None
    :param str source_dir: The directory to use for source requirements. Removed in pip
        10, defaults to None
    :param Optional[str] download_dir: Target directory to download files, defaults to
        None
    :param str cache_dir: The cache directory to use for caching artifacts during
        resolution
    :param Optional[str] wheel_download_dir: Target directoryto download wheels, defaults
        to None
    :param Optional[TWheelCache] wheel_cache: The wheel cache to use, defaults to None
    :param bool require_hashes: Whether to require hashes when resolving. Defaults to
        False.
    :param bool check_supported_wheels: Whether to check support of wheels before including
        them in resolution.
    :return: A dictionary mapping requirements to corresponding
        :class:`~pip._internal.req.req_install.InstallRequirement`s
    :rtype: :class:`~pip._internal.req.req_install.InstallRequirement`

    :Example:

    >>> from pip_shims.shims import resolve, InstallRequirement
    >>> ireq = InstallRequirement.from_line("requests>=2.20")
    >>> results = resolve(ireq)
    >>> for k, v in results.items():
    ...    print("{0}: {1!r}".format(k, v))
    requests: <InstallRequirement object: requests>=2.20 from https://files.pythonhosted.
    org/packages/51/bd/23c926cd341ea6b7dd0b2a00aba99ae0f828be89d72b2190f27c11d4b7fb/reque
    sts-2.22.0-py2.py3-none-any.whl#sha256=9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590
    f48c010551dc6c4b31 editable=False>
    idna: <InstallRequirement object: idna<2.9,>=2.5 from https://files.pythonhosted.org/
    packages/14/2c/cd551d81dbe15200be1cf41cd03869a46fe7226e7450af7a6545bfc474c9/idna-2.8-
    py2.py3-none-any.whl#sha256=ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432
    f7e4a3c (from requests>=2.20) editable=False>
    urllib3: <InstallRequirement object: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 from htt
    ps://files.pythonhosted.org/packages/b4/40/a9837291310ee1ccc242ceb6ebfd9eb21539649f19
    3a7c8c86ba15b98539/urllib3-1.25.7-py2.py3-none-any.whl#sha256=a8a318824cc77d1fd4b2bec
    2ded92646630d7fe8619497b142c84a9e6f5a7293 (from requests>=2.20) editable=False>
    chardet: <InstallRequirement object: chardet<3.1.0,>=3.0.2 from https://files.pythonh
    osted.org/packages/bc/a9/01ffebfb562e4274b6487b4bb1ddec7ca55ec7510b22e4c51f14098443b8
    /chardet-3.0.4-py2.py3-none-any.whl#sha256=fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed
    4531e3e15460124c106691 (from requests>=2.20) editable=False>
    certifi: <InstallRequirement object: certifi>=2017.4.17 from https://files.pythonhost
    ed.org/packages/18/b0/8146a4f8dd402f60744fa380bc73ca47303cccf8b9190fd16a827281eac2/ce
    rtifi-2019.9.11-py2.py3-none-any.whl#sha256=fd7c7c74727ddcf00e9acd26bba8da604ffec95bf
    1c2144e67aff7a8b50e6cef (from requests>=2.20) editable=False>
    """
    reqset_provider = resolve_possible_shim(reqset_provider)
    finder_provider = resolve_possible_shim(finder_provider)
    resolver_provider = resolve_possible_shim(resolver_provider)
    wheel_cache_provider = resolve_possible_shim(wheel_cache_provider)
    format_control_provider = resolve_possible_shim(format_control_provider)
    make_preparer_provider = resolve_possible_shim(make_preparer_provider)
    req_tracker_provider = resolve_possible_shim(req_tracker_provider)
    install_cmd_provider = resolve_possible_shim(install_cmd_provider)
    tempdir_manager_provider = resolve_possible_shim(tempdir_manager_provider)
    if install_command is None:
        assert isinstance(install_cmd_provider, (type, functools.partial))
        install_command = install_cmd_provider()
    kwarg_map = {
        "upgrade_strategy": upgrade_strategy,
        "force_reinstall": force_reinstall,
        "ignore_dependencies": ignore_dependencies,
        "ignore_requires_python": ignore_requires_python,
        "ignore_installed": ignore_installed,
        "use_user_site": use_user_site,
        "isolated": isolated,
        "build_dir": build_dir,
        "src_dir": source_dir,
        "download_dir": download_dir,
        "require_hashes": require_hashes,
        "cache_dir": cache_dir,
    }
    kwargs, options = populate_options(install_command, options, **kwarg_map)
    with ExitStack() as ctx:
        ctx.enter_context(tempdir_manager_provider())
        kwargs = ctx.enter_context(
            ensure_resolution_dirs(wheel_download_dir=wheel_download_dir, **kwargs)
        )
        wheel_download_dir = kwargs.pop("wheel_download_dir")
        if session is None:
            session = get_session(install_cmd=install_command, options=options)
        if finder is None:
            finder = finder_provider(
                install_command, options=options, session=session
            )  # type: ignore
        format_control = getattr(options, "format_control", None)
        if not format_control:
            format_control = format_control_provider(None, None)  # type: ignore
        wheel_cache = ctx.enter_context(
            wheel_cache_provider(kwargs["cache_dir"], format_control)
        )  # type: ignore
        ireq.is_direct = True  # type: ignore
        build_location_kwargs = {
            "build_dir": kwargs["build_dir"],
            "autodelete": True,
            "parallel_builds": False
        }
        call_function_with_correct_args(ireq.build_location, **build_location_kwargs)
        if reqset_provider is None:
            raise TypeError(
                "cannot resolve without a requirement set provider... failed!"
            )
        reqset = reqset_provider(
            install_command,
            options=options,
            session=session,
            wheel_download_dir=wheel_download_dir,
            **kwargs
        )  # type: ignore
        if getattr(reqset, "prepare_files", None):
            reqset.add_requirement(ireq)
            results = reqset.prepare_files(finder)
            result = reqset.requirements
            reqset.cleanup_files()
            return result
        if make_preparer_provider is None:
            raise TypeError("Cannot create requirement preparer, cannot resolve!")

        preparer_args = {
            "build_dir": kwargs["build_dir"],
            "src_dir": kwargs["src_dir"],
            "download_dir": kwargs["download_dir"],
            "wheel_download_dir": wheel_download_dir,
            "build_isolation": kwargs["isolated"],
            "install_cmd": install_command,
            "options": options,
            "finder": finder,
            "session": session,
            "use_user_site": use_user_site,
            "require_hashes": require_hashes,
        }
        if isinstance(req_tracker_provider, (types.FunctionType, functools.partial)):
            preparer_args["req_tracker"] = ctx.enter_context(req_tracker_provider())
        resolver_keys = [
            "upgrade_strategy",
            "force_reinstall",
            "ignore_dependencies",
            "ignore_installed",
            "use_user_site",
            "isolated",
            "use_user_site",
        ]
        resolver_args = {key: kwargs[key] for key in resolver_keys if key in kwargs}
        if resolver_provider is None:
            raise TypeError("Cannot resolve without a resolver provider... failed!")
        preparer = ctx.enter_context(make_preparer_provider(**preparer_args))
        resolver = resolver_provider(
            finder=finder,
            preparer=preparer,
            session=session,
            options=options,
            install_cmd=install_command,
            wheel_cache=wheel_cache,
            **resolver_args
        )  # type: ignore
        resolver.require_hashes = kwargs.get("require_hashes", False)  # type: ignore
        _, required_resolver_args = get_method_args(resolver.resolve)
        resolver_args = []
        if "requirement_set" in required_resolver_args.args:
            reqset.add_requirement(ireq)
            resolver_args.append(reqset)
        elif "root_reqs" in required_resolver_args.args:
            resolver_args.append([ireq])
        if "check_supported_wheels" in required_resolver_args.args:
            resolver_args.append(check_supported_wheels)
        result_reqset = resolver.resolve(*resolver_args)  # type: ignore
        if result_reqset is None:
            result_reqset = reqset
        results = result_reqset.requirements
        cleanup_fn = getattr(reqset, "cleanup_files", None)
        if cleanup_fn is not None:
            cleanup_fn()
        return results


def build_wheel(  # noqa:C901
    req=None,  # type: Optional[TInstallRequirement]
    reqset=None,  # type: Optional[Union[TReqSet, Iterable[TInstallRequirement]]]
    output_dir=None,  # type: Optional[str]
    preparer=None,  # type: Optional[TPreparer]
    wheel_cache=None,  # type: Optional[TWheelCache]
    build_options=None,  # type: Optional[List[str]]
    global_options=None,  # type: Optional[List[str]]
    check_binary_allowed=None,  # type: Optional[Callable[TInstallRequirement, bool]]
    no_clean=False,  # type: bool
    session=None,  # type: Optional[TSession]
    finder=None,  # type: Optional[TFinder]
    install_command=None,  # type: Optional[TCommand]
    req_tracker=None,  # type: Optional[TReqTracker]
    build_dir=None,  # type: Optional[str]
    src_dir=None,  # type: Optional[str]
    download_dir=None,  # type: Optional[str]
    wheel_download_dir=None,  # type: Optional[str]
    cache_dir=None,  # type: Optional[str]
    use_user_site=False,  # type: bool
    use_pep517=None,  # type: Optional[bool]
    format_control_provider=None,  # type: Optional[TShimmedFunc]
    wheel_cache_provider=None,  # type: Optional[TShimmedFunc]
    preparer_provider=None,  # type: Optional[TShimmedFunc]
    wheel_builder_provider=None,  # type: Optional[TShimmedFunc]
    build_one_provider=None,  # type: Optional[TShimmedFunc]
    build_one_inside_env_provider=None,  # type: Optional[TShimmedFunc]
    build_many_provider=None,  # type: Optional[TShimmedFunc]
    install_command_provider=None,  # type: Optional[TShimmedFunc]
    finder_provider=None,  # type: Optional[TShimmedFunc]
    reqset_provider=None,  # type: Optional[TShimmedFunc]
):
    # type: (...) -> Generator[Union[str, Tuple[List[TInstallRequirement], ...]], None, None]
    """
    Build a wheel or a set of wheels

    :raises TypeError: Raised when no requirements are provided
    :param Optional[TInstallRequirement] req:  An `InstallRequirement` to build
    :param Optional[TReqSet] reqset: A `RequirementSet` instance (`pip<10`) or an
        iterable of `InstallRequirement` instances (`pip>=10`) to build
    :param Optional[str] output_dir: Target output directory, only useful when building
        one wheel using pip>=20.0
    :param Optional[TPreparer] preparer: A preparer instance, defaults to None
    :param Optional[TWheelCache] wheel_cache: A wheel cache instance, defaults to None
    :param Optional[List[str]] build_options: A list of build options to pass in
    :param Optional[List[str]] global_options: A list of global options to pass in
    :param Optional[Callable[TInstallRequirement, bool]] check_binary_allowed: A callable
        to check whether we are allowed to build and cache wheels for an ireq
    :param bool no_clean: Whether to avoid cleaning up wheels
    :param Optional[TSession] session: A `PipSession` instance to pass to create a
        `finder` if necessary
    :param Optional[TFinder] finder: A `PackageFinder` instance to use for generating a
        `WheelBuilder` instance on `pip<20`
    :param Optional[TCommandInstance] install_command:  The install command used to
        create the finder, session, and options if needed, defaults to None.
    :param Optional[TReqTracker] req_tracker: An optional requirement tracker instance,
        if one already exists
    :param Optional[str] build_dir: Passthrough parameter for building preparer
    :param Optional[str] src_dir: Passthrough parameter for building preparer
    :param Optional[str] download_dir: Passthrough parameter for building preparer
    :param Optional[str] wheel_download_dir: Passthrough parameter for building preparer
    :param Optional[str] cache_dir: Passthrough cache directory for wheel cache options
    :param bool use_user_site: Whether to use the user site directory when preparing
        install requirements on `pip<20`
    :param Optional[bool] use_pep517: When set to *True* or *False*, prefers building
        with or without pep517 as specified, otherwise uses requirement preference.
        Only works for single requirements.
    :param Optional[TShimmedFunc] format_control_provider: A provider for the
        `FormatControl` class
    :param Optional[TShimmedFunc] wheel_cache_provider: A provider for the `WheelCache`
        class
    :param Optional[TShimmedFunc] preparer_provider: A provider for the
        `RequirementPreparer` class
    :param Optional[TShimmedFunc] wheel_builder_provider: A provider for the
        `WheelBuilder` class, if it exists
    :param Optional[TShimmedFunc] build_one_provider: A provider for the `_build_one`
        function, if it exists
    :param Optional[TShimmedFunc] build_one_inside_env_provider: A provider for the
        `_build_one_inside_env` function, if it exists
    :param Optional[TShimmedFunc] build_many_provider: A provider for the `build`
        function, if it exists
    :param Optional[TShimmedFunc] install_command_provider: A shim for providing new
        install command instances
    :param TShimmedFunc finder_provider: A provider to package finder instances
    :param TShimmedFunc reqset_provider: A provider for requirement set generation
    :return: A tuple of successful and failed install requirements or else a path to
        a wheel
    :rtype: Optional[Union[str, Tuple[List[TInstallRequirement], List[TInstallRequirement]]]]
    """
    wheel_cache_provider = resolve_possible_shim(wheel_cache_provider)
    preparer_provider = resolve_possible_shim(preparer_provider)
    wheel_builder_provider = resolve_possible_shim(wheel_builder_provider)
    build_one_provider = resolve_possible_shim(build_one_provider)
    build_one_inside_env_provider = resolve_possible_shim(build_one_inside_env_provider)
    build_many_provider = resolve_possible_shim(build_many_provider)
    install_cmd_provider = resolve_possible_shim(install_command_provider)
    format_control_provider = resolve_possible_shim(format_control_provider)
    finder_provider = resolve_possible_shim(finder_provider) or get_package_finder
    reqset_provider = resolve_possible_shim(reqset_provider)
    global_options = [] if global_options is None else global_options
    build_options = [] if build_options is None else build_options
    options = None
    kwarg_map = {
        "cache_dir": cache_dir,
        "src_dir": src_dir,
        "download_dir": download_dir,
        "wheel_download_dir": wheel_download_dir,
        "build_dir": build_dir,
        "use_user_site": use_user_site,
    }
    if not req and not reqset:
        raise TypeError("Must provide either a requirement or requirement set to build")
    with ExitStack() as ctx:
        kwargs = kwarg_map.copy()
        if wheel_cache is None and (reqset is not None or output_dir is None):
            if install_command is None:
                assert isinstance(install_cmd_provider, (type, functools.partial))
                install_command = install_cmd_provider()
            kwargs, options = populate_options(install_command, options, **kwarg_map)
            format_control = getattr(options, "format_control", None)
            if not format_control:
                format_control = format_control_provider(None, None)  # type: ignore
            wheel_cache = ctx.enter_context(
                wheel_cache_provider(options.cache_dir, format_control)
            )
        if req and not reqset and not output_dir:
            output_dir = get_ireq_output_path(wheel_cache, req)
        if not reqset and build_one_provider:
            yield build_one_provider(req, output_dir, build_options, global_options)
        elif build_many_provider:
            yield build_many_provider(
                reqset, wheel_cache, build_options, global_options, check_binary_allowed
            )
        else:
            builder_args, builder_kwargs = get_allowed_args(wheel_builder_provider)
            if "requirement_set" in builder_args and not reqset:
                reqset = reqset_provider()
            if session is None and finder is None:
                session = get_session(install_cmd=install_command, options=options)
                finder = finder_provider(
                    install_command, options=options, session=session
                )
            if preparer is None:
                preparer_kwargs = {
                    "build_dir": kwargs["build_dir"],
                    "src_dir": kwargs["src_dir"],
                    "download_dir": kwargs["download_dir"],
                    "wheel_download_dir": kwargs["wheel_download_dir"],
                    "finder": finder,
                    "session": session
                    if session
                    else get_session(install_cmd=install_command, options=options),
                    "install_cmd": install_command,
                    "options": options,
                    "use_user_site": use_user_site,
                    "req_tracker": req_tracker,
                }
                preparer = ctx.enter_context(preparer_provider(**preparer_kwargs))
            check_bin = check_binary_allowed if check_binary_allowed else lambda x: True
            builder_kwargs = {
                "requirement_set": reqset,
                "finder": finder,
                "preparer": preparer,
                "wheel_cache": wheel_cache,
                "no_clean": no_clean,
                "build_options": build_options,
                "global_options": global_options,
                "check_binary_allowed": check_bin,
            }
            builder = call_function_with_correct_args(
                wheel_builder_provider, **builder_kwargs
            )
            if req and not reqset:
                if not output_dir:
                    output_dir = get_ireq_output_path(wheel_cache, req)
                if use_pep517 is not None:
                    req.use_pep517 = use_pep517
                yield builder._build_one(req, output_dir)
            else:
                yield builder.build(reqset)
