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._vendor import pkg_resources
|
||||||
|
|
||||||
from pip._internal.cache import WheelCache
|
from pip._internal.cache import WheelCache
|
||||||
|
from pip._internal.distributions import AbstractDistribution
|
||||||
from pip._internal.download import PipSession
|
from pip._internal.download import PipSession
|
||||||
from pip._internal.index import PackageFinder
|
from pip._internal.index import PackageFinder
|
||||||
from pip._internal.operations.prepare import RequirementPreparer
|
from pip._internal.operations.prepare import RequirementPreparer
|
||||||
|
@ -276,7 +277,7 @@ class Resolver(object):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _get_abstract_dist_for(self, req):
|
def _get_abstract_dist_for(self, req):
|
||||||
# type: (InstallRequirement) -> DistAbstraction
|
# type: (InstallRequirement) -> AbstractDistribution
|
||||||
"""Takes a InstallRequirement and returns a single AbstractDist \
|
"""Takes a InstallRequirement and returns a single AbstractDist \
|
||||||
representing a prepared variant of the same.
|
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.packaging.utils import canonicalize_name
|
||||||
from pip._vendor.pkg_resources import RequirementParseError
|
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.misc import get_installed_distributions
|
||||||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,10 @@
|
||||||
import logging
|
import logging
|
||||||
import os
|
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 (
|
from pip._internal.download import (
|
||||||
is_dir_url, is_file_url, is_vcs_url, unpack_url, url_to_path,
|
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
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
if 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.download import PipSession
|
||||||
from pip._internal.index import PackageFinder
|
from pip._internal.index import PackageFinder
|
||||||
from pip._internal.req.req_install import InstallRequirement
|
from pip._internal.req.req_install import InstallRequirement
|
||||||
|
@ -31,159 +33,6 @@ if MYPY_CHECK_RUNNING:
|
||||||
logger = logging.getLogger(__name__)
|
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):
|
class RequirementPreparer(object):
|
||||||
"""Prepares a Requirement
|
"""Prepares a Requirement
|
||||||
"""
|
"""
|
||||||
|
@ -249,7 +98,7 @@ class RequirementPreparer(object):
|
||||||
upgrade_allowed, # type: bool
|
upgrade_allowed, # type: bool
|
||||||
require_hashes # type: bool
|
require_hashes # type: bool
|
||||||
):
|
):
|
||||||
# type: (...) -> DistAbstraction
|
# type: (...) -> AbstractDistribution
|
||||||
"""Prepare a requirement that would be obtained from req.link
|
"""Prepare a requirement that would be obtained from req.link
|
||||||
"""
|
"""
|
||||||
# TODO: Breakup into smaller functions
|
# TODO: Breakup into smaller functions
|
||||||
|
@ -374,7 +223,7 @@ class RequirementPreparer(object):
|
||||||
use_user_site, # type: bool
|
use_user_site, # type: bool
|
||||||
finder # type: PackageFinder
|
finder # type: PackageFinder
|
||||||
):
|
):
|
||||||
# type: (...) -> DistAbstraction
|
# type: (...) -> AbstractDistribution
|
||||||
"""Prepare an editable requirement
|
"""Prepare an editable requirement
|
||||||
"""
|
"""
|
||||||
assert req.editable, "cannot prepare a non-editable req as editable"
|
assert req.editable, "cannot prepare a non-editable req as editable"
|
||||||
|
@ -401,8 +250,13 @@ class RequirementPreparer(object):
|
||||||
|
|
||||||
return abstract_dist
|
return abstract_dist
|
||||||
|
|
||||||
def prepare_installed_requirement(self, req, require_hashes, skip_reason):
|
def prepare_installed_requirement(
|
||||||
# type: (InstallRequirement, bool, Optional[str]) -> DistAbstraction
|
self,
|
||||||
|
req, # type: InstallRequirement
|
||||||
|
require_hashes, # type: bool
|
||||||
|
skip_reason # type: str
|
||||||
|
):
|
||||||
|
# type: (...) -> AbstractDistribution
|
||||||
"""Prepare an already-installed requirement
|
"""Prepare an already-installed requirement
|
||||||
"""
|
"""
|
||||||
assert req.satisfied_by, "req should have been satisfied but isn't"
|
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 '
|
'completely repeatable environment, install into an '
|
||||||
'empty virtualenv.'
|
'empty virtualenv.'
|
||||||
)
|
)
|
||||||
abstract_dist = Installed(req)
|
abstract_dist = InstalledDistribution(req)
|
||||||
|
|
||||||
return abstract_dist
|
return abstract_dist
|
||||||
|
|
Loading…
Reference in New Issue