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:
Pradyun Gedam 2019-06-14 21:07:51 +05:30
parent d3987fe114
commit d43e31802b
No known key found for this signature in database
GPG Key ID: DA17C4B29CB32E4B
8 changed files with 153 additions and 163 deletions

View File

@ -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)

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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.
"""

View File

@ -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

View File

@ -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