mirror of https://github.com/pypa/pip
Add distributions sub-package for holding build logic
This moves the DistAbstraction logic into a dedicated 'distributions' sub-package, with separated modules for the concrete implementations of various distribution handling logic.
This commit is contained in:
parent
d3987fe114
commit
d43e31802b
|
@ -0,0 +1,23 @@
|
|||
from pip._internal.distributions.source import SourceDistribution
|
||||
from pip._internal.distributions.wheel import WheelDistribution
|
||||
|
||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from pip._internal.distributions.base import AbstractDistribution
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
|
||||
|
||||
def make_abstract_dist(install_req):
|
||||
# type: (InstallRequirement) -> AbstractDistribution
|
||||
"""Returns a Distribution for the given InstallRequirement
|
||||
"""
|
||||
# If it's not an editable, is a wheel, it's a WheelDistribution
|
||||
if install_req.editable:
|
||||
return SourceDistribution(install_req)
|
||||
|
||||
if install_req.link and install_req.is_wheel:
|
||||
return WheelDistribution(install_req)
|
||||
|
||||
# Otherwise, a SourceDistribution
|
||||
return SourceDistribution(install_req)
|
|
@ -0,0 +1,19 @@
|
|||
import abc
|
||||
|
||||
from pip._vendor.six import add_metaclass
|
||||
|
||||
|
||||
@add_metaclass(abc.ABCMeta)
|
||||
class AbstractDistribution(object):
|
||||
|
||||
def __init__(self, req):
|
||||
super(AbstractDistribution, self).__init__()
|
||||
self.req = req
|
||||
|
||||
@abc.abstractmethod
|
||||
def dist(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def prep_for_dist(self, finder, build_isolation):
|
||||
raise NotImplementedError()
|
|
@ -0,0 +1,10 @@
|
|||
from pip._internal.distributions.base import AbstractDistribution
|
||||
|
||||
|
||||
class InstalledDistribution(AbstractDistribution):
|
||||
|
||||
def dist(self):
|
||||
return self.req.satisfied_by
|
||||
|
||||
def prep_for_dist(self, finder, build_isolation):
|
||||
pass
|
|
@ -0,0 +1,70 @@
|
|||
import logging
|
||||
|
||||
from pip._internal.build_env import BuildEnvironment
|
||||
from pip._internal.distributions.base import AbstractDistribution
|
||||
from pip._internal.exceptions import InstallationError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SourceDistribution(AbstractDistribution):
|
||||
|
||||
def dist(self):
|
||||
return self.req.get_dist()
|
||||
|
||||
def prep_for_dist(self, finder, build_isolation):
|
||||
# Prepare for building. We need to:
|
||||
# 1. Load pyproject.toml (if it exists)
|
||||
# 2. Set up the build environment
|
||||
|
||||
self.req.load_pyproject_toml()
|
||||
should_isolate = self.req.use_pep517 and build_isolation
|
||||
|
||||
def _raise_conflicts(conflicting_with, conflicting_reqs):
|
||||
raise InstallationError(
|
||||
"Some build dependencies for %s conflict with %s: %s." % (
|
||||
self.req, conflicting_with, ', '.join(
|
||||
'%s is incompatible with %s' % (installed, wanted)
|
||||
for installed, wanted in sorted(conflicting))))
|
||||
|
||||
if should_isolate:
|
||||
# Isolate in a BuildEnvironment and install the build-time
|
||||
# requirements.
|
||||
self.req.build_env = BuildEnvironment()
|
||||
self.req.build_env.install_requirements(
|
||||
finder, self.req.pyproject_requires, 'overlay',
|
||||
"Installing build dependencies"
|
||||
)
|
||||
conflicting, missing = self.req.build_env.check_requirements(
|
||||
self.req.requirements_to_check
|
||||
)
|
||||
if conflicting:
|
||||
_raise_conflicts("PEP 517/518 supported requirements",
|
||||
conflicting)
|
||||
if missing:
|
||||
logger.warning(
|
||||
"Missing build requirements in pyproject.toml for %s.",
|
||||
self.req,
|
||||
)
|
||||
logger.warning(
|
||||
"The project does not specify a build backend, and "
|
||||
"pip cannot fall back to setuptools without %s.",
|
||||
" and ".join(map(repr, sorted(missing)))
|
||||
)
|
||||
# Install any extra build dependencies that the backend requests.
|
||||
# This must be done in a second pass, as the pyproject.toml
|
||||
# dependencies must be installed before we can call the backend.
|
||||
with self.req.build_env:
|
||||
# We need to have the env active when calling the hook.
|
||||
self.req.spin_message = "Getting requirements to build wheel"
|
||||
reqs = self.req.pep517_backend.get_requires_for_build_wheel()
|
||||
conflicting, missing = self.req.build_env.check_requirements(reqs)
|
||||
if conflicting:
|
||||
_raise_conflicts("the backend dependencies", conflicting)
|
||||
self.req.build_env.install_requirements(
|
||||
finder, missing, 'normal',
|
||||
"Installing backend dependencies"
|
||||
)
|
||||
|
||||
self.req.prepare_metadata()
|
||||
self.req.assert_source_matches_version()
|
|
@ -0,0 +1,13 @@
|
|||
from pip._vendor import pkg_resources
|
||||
|
||||
from pip._internal.distributions.base import AbstractDistribution
|
||||
|
||||
|
||||
class WheelDistribution(AbstractDistribution):
|
||||
|
||||
def dist(self):
|
||||
return list(pkg_resources.find_distributions(
|
||||
self.req.source_dir))[0]
|
||||
|
||||
def prep_for_dist(self, finder, build_isolation):
|
||||
pass
|
|
@ -36,6 +36,7 @@ if MYPY_CHECK_RUNNING:
|
|||
from pip._vendor import pkg_resources
|
||||
|
||||
from pip._internal.cache import WheelCache
|
||||
from pip._internal.distributions import AbstractDistribution
|
||||
from pip._internal.download import PipSession
|
||||
from pip._internal.index import PackageFinder
|
||||
from pip._internal.operations.prepare import RequirementPreparer
|
||||
|
@ -276,7 +277,7 @@ class Resolver(object):
|
|||
return None
|
||||
|
||||
def _get_abstract_dist_for(self, req):
|
||||
# type: (InstallRequirement) -> DistAbstraction
|
||||
# type: (InstallRequirement) -> AbstractDistribution
|
||||
"""Takes a InstallRequirement and returns a single AbstractDist \
|
||||
representing a prepared variant of the same.
|
||||
"""
|
||||
|
|
|
@ -7,7 +7,7 @@ from collections import namedtuple
|
|||
from pip._vendor.packaging.utils import canonicalize_name
|
||||
from pip._vendor.pkg_resources import RequirementParseError
|
||||
|
||||
from pip._internal.operations.prepare import make_abstract_dist
|
||||
from pip._internal.distributions import make_abstract_dist
|
||||
from pip._internal.utils.misc import get_installed_distributions
|
||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
|
|
|
@ -4,9 +4,10 @@
|
|||
import logging
|
||||
import os
|
||||
|
||||
from pip._vendor import pkg_resources, requests
|
||||
from pip._vendor import requests
|
||||
|
||||
from pip._internal.build_env import BuildEnvironment
|
||||
from pip._internal.distributions import make_abstract_dist
|
||||
from pip._internal.distributions.installed import InstalledDistribution
|
||||
from pip._internal.download import (
|
||||
is_dir_url, is_file_url, is_vcs_url, unpack_url, url_to_path,
|
||||
)
|
||||
|
@ -21,8 +22,9 @@ from pip._internal.utils.misc import display_path, normalize_path
|
|||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||
|
||||
if MYPY_CHECK_RUNNING:
|
||||
from typing import Any, Optional
|
||||
from typing import Optional
|
||||
|
||||
from pip._internal.distributions import AbstractDistribution
|
||||
from pip._internal.download import PipSession
|
||||
from pip._internal.index import PackageFinder
|
||||
from pip._internal.req.req_install import InstallRequirement
|
||||
|
@ -31,159 +33,6 @@ if MYPY_CHECK_RUNNING:
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def make_abstract_dist(req):
|
||||
# type: (InstallRequirement) -> DistAbstraction
|
||||
"""Factory to make an abstract dist object.
|
||||
|
||||
Preconditions: Either an editable req with a source_dir, or satisfied_by or
|
||||
a wheel link, or a non-editable req with a source_dir.
|
||||
|
||||
:return: A concrete DistAbstraction.
|
||||
"""
|
||||
if req.editable:
|
||||
return IsSDist(req)
|
||||
elif req.link and req.link.is_wheel:
|
||||
return IsWheel(req)
|
||||
else:
|
||||
return IsSDist(req)
|
||||
|
||||
|
||||
class DistAbstraction(object):
|
||||
"""Abstracts out the wheel vs non-wheel Resolver.resolve() logic.
|
||||
|
||||
The requirements for anything installable are as follows:
|
||||
- we must be able to determine the requirement name
|
||||
(or we can't correctly handle the non-upgrade case).
|
||||
- we must be able to generate a list of run-time dependencies
|
||||
without installing any additional packages (or we would
|
||||
have to either burn time by doing temporary isolated installs
|
||||
or alternatively violate pips 'don't start installing unless
|
||||
all requirements are available' rule - neither of which are
|
||||
desirable).
|
||||
- for packages with setup requirements, we must also be able
|
||||
to determine their requirements without installing additional
|
||||
packages (for the same reason as run-time dependencies)
|
||||
- we must be able to create a Distribution object exposing the
|
||||
above metadata.
|
||||
"""
|
||||
|
||||
def __init__(self, req):
|
||||
# type: (InstallRequirement) -> None
|
||||
self.req = req # type: InstallRequirement
|
||||
|
||||
def dist(self):
|
||||
# type: () -> Any
|
||||
"""Return a setuptools Dist object."""
|
||||
raise NotImplementedError
|
||||
|
||||
def prep_for_dist(self, finder, build_isolation):
|
||||
# type: (PackageFinder, bool) -> Any
|
||||
"""Ensure that we can get a Dist for this requirement."""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class IsWheel(DistAbstraction):
|
||||
|
||||
def dist(self):
|
||||
# type: () -> pkg_resources.Distribution
|
||||
return list(pkg_resources.find_distributions(
|
||||
self.req.source_dir))[0]
|
||||
|
||||
def prep_for_dist(self, finder, build_isolation):
|
||||
# type: (PackageFinder, bool) -> Any
|
||||
# FIXME:https://github.com/pypa/pip/issues/1112
|
||||
pass
|
||||
|
||||
|
||||
class IsSDist(DistAbstraction):
|
||||
|
||||
def dist(self):
|
||||
return self.req.get_dist()
|
||||
|
||||
def _raise_conflicts(self, conflicting_with, conflicting_reqs):
|
||||
conflict_messages = [
|
||||
'%s is incompatible with %s' % (installed, wanted)
|
||||
for installed, wanted in sorted(conflicting_reqs)
|
||||
]
|
||||
raise InstallationError(
|
||||
"Some build dependencies for %s conflict with %s: %s." % (
|
||||
self.req, conflicting_with, ', '.join(conflict_messages))
|
||||
)
|
||||
|
||||
def install_backend_dependencies(self, finder):
|
||||
# type: (PackageFinder) -> None
|
||||
"""
|
||||
Install any extra build dependencies that the backend requests.
|
||||
|
||||
:param finder: a PackageFinder object.
|
||||
"""
|
||||
req = self.req
|
||||
with req.build_env:
|
||||
# We need to have the env active when calling the hook.
|
||||
req.spin_message = "Getting requirements to build wheel"
|
||||
reqs = req.pep517_backend.get_requires_for_build_wheel()
|
||||
conflicting, missing = req.build_env.check_requirements(reqs)
|
||||
if conflicting:
|
||||
self._raise_conflicts("the backend dependencies", conflicting)
|
||||
req.build_env.install_requirements(
|
||||
finder, missing, 'normal',
|
||||
"Installing backend dependencies"
|
||||
)
|
||||
|
||||
def prep_for_dist(self, finder, build_isolation):
|
||||
# type: (PackageFinder, bool) -> None
|
||||
# Prepare for building. We need to:
|
||||
# 1. Load pyproject.toml (if it exists)
|
||||
# 2. Set up the build environment
|
||||
|
||||
self.req.load_pyproject_toml()
|
||||
should_isolate = self.req.use_pep517 and build_isolation
|
||||
|
||||
if should_isolate:
|
||||
# Isolate in a BuildEnvironment and install the build-time
|
||||
# requirements.
|
||||
self.req.build_env = BuildEnvironment()
|
||||
self.req.build_env.install_requirements(
|
||||
finder, self.req.pyproject_requires, 'overlay',
|
||||
"Installing build dependencies"
|
||||
)
|
||||
conflicting, missing = self.req.build_env.check_requirements(
|
||||
self.req.requirements_to_check
|
||||
)
|
||||
if conflicting:
|
||||
self._raise_conflicts("PEP 517/518 supported requirements",
|
||||
conflicting)
|
||||
if missing:
|
||||
logger.warning(
|
||||
"Missing build requirements in pyproject.toml for %s.",
|
||||
self.req,
|
||||
)
|
||||
logger.warning(
|
||||
"The project does not specify a build backend, and "
|
||||
"pip cannot fall back to setuptools without %s.",
|
||||
" and ".join(map(repr, sorted(missing)))
|
||||
)
|
||||
|
||||
# Install any extra build dependencies that the backend requests.
|
||||
# This must be done in a second pass, as the pyproject.toml
|
||||
# dependencies must be installed before we can call the backend.
|
||||
self.install_backend_dependencies(finder=finder)
|
||||
|
||||
self.req.prepare_metadata()
|
||||
self.req.assert_source_matches_version()
|
||||
|
||||
|
||||
class Installed(DistAbstraction):
|
||||
|
||||
def dist(self):
|
||||
# type: () -> pkg_resources.Distribution
|
||||
return self.req.satisfied_by
|
||||
|
||||
def prep_for_dist(self, finder, build_isolation):
|
||||
# type: (PackageFinder, bool) -> Any
|
||||
pass
|
||||
|
||||
|
||||
class RequirementPreparer(object):
|
||||
"""Prepares a Requirement
|
||||
"""
|
||||
|
@ -249,7 +98,7 @@ class RequirementPreparer(object):
|
|||
upgrade_allowed, # type: bool
|
||||
require_hashes # type: bool
|
||||
):
|
||||
# type: (...) -> DistAbstraction
|
||||
# type: (...) -> AbstractDistribution
|
||||
"""Prepare a requirement that would be obtained from req.link
|
||||
"""
|
||||
# TODO: Breakup into smaller functions
|
||||
|
@ -374,7 +223,7 @@ class RequirementPreparer(object):
|
|||
use_user_site, # type: bool
|
||||
finder # type: PackageFinder
|
||||
):
|
||||
# type: (...) -> DistAbstraction
|
||||
# type: (...) -> AbstractDistribution
|
||||
"""Prepare an editable requirement
|
||||
"""
|
||||
assert req.editable, "cannot prepare a non-editable req as editable"
|
||||
|
@ -401,8 +250,13 @@ class RequirementPreparer(object):
|
|||
|
||||
return abstract_dist
|
||||
|
||||
def prepare_installed_requirement(self, req, require_hashes, skip_reason):
|
||||
# type: (InstallRequirement, bool, Optional[str]) -> DistAbstraction
|
||||
def prepare_installed_requirement(
|
||||
self,
|
||||
req, # type: InstallRequirement
|
||||
require_hashes, # type: bool
|
||||
skip_reason # type: str
|
||||
):
|
||||
# type: (...) -> AbstractDistribution
|
||||
"""Prepare an already-installed requirement
|
||||
"""
|
||||
assert req.satisfied_by, "req should have been satisfied but isn't"
|
||||
|
@ -422,6 +276,6 @@ class RequirementPreparer(object):
|
|||
'completely repeatable environment, install into an '
|
||||
'empty virtualenv.'
|
||||
)
|
||||
abstract_dist = Installed(req)
|
||||
abstract_dist = InstalledDistribution(req)
|
||||
|
||||
return abstract_dist
|
||||
|
|
Loading…
Reference in New Issue