diff --git a/src/pip/_internal/operations/generate_metadata.py b/src/pip/_internal/operations/generate_metadata.py index 4d06ca7af..9123c2bc7 100644 --- a/src/pip/_internal/operations/generate_metadata.py +++ b/src/pip/_internal/operations/generate_metadata.py @@ -4,10 +4,12 @@ import logging import os +from pip._internal.exceptions import InstallationError from pip._internal.utils.misc import ensure_dir from pip._internal.utils.setuptools_build import make_setuptools_shim_args from pip._internal.utils.subprocess import call_subprocess from pip._internal.utils.typing import MYPY_CHECK_RUNNING +from pip._internal.vcs import vcs if MYPY_CHECK_RUNNING: from typing import Callable, List @@ -30,6 +32,61 @@ def get_metadata_generator(install_req): return _generate_metadata +def find_egg_info(install_req): + # type: (InstallRequirement) -> str + + def looks_like_virtual_env(path): + return ( + os.path.lexists(os.path.join(path, 'bin', 'python')) or + os.path.exists(os.path.join(path, 'Scripts', 'Python.exe')) + ) + + def locate_editable_egg_info(base): + candidates = [] + for root, dirs, files in os.walk(base): + for dir_ in vcs.dirnames: + if dir_ in dirs: + dirs.remove(dir_) + # Iterate over a copy of ``dirs``, since mutating + # a list while iterating over it can cause trouble. + # (See https://github.com/pypa/pip/pull/462.) + for dir_ in list(dirs): + if looks_like_virtual_env(os.path.join(root, dir_)): + dirs.remove(dir_) + # Also don't search through tests + elif dir_ == 'test' or dir_ == 'tests': + dirs.remove(dir_) + candidates.extend(os.path.join(root, dir_) for dir_ in dirs) + return [f for f in candidates if f.endswith('.egg-info')] + + def depth_of_directory(dir_): + return ( + dir_.count(os.path.sep) + + (os.path.altsep and dir_.count(os.path.altsep) or 0) + ) + + if install_req.editable: + base = install_req.source_dir + filenames = locate_editable_egg_info(base) + else: + dir_ = install_req.unpacked_source_directory + base = os.path.join(dir_, 'pip-egg-info') + filenames = os.listdir(base) + + if not filenames: + raise InstallationError( + "Files/directories not found in %s" % base + ) + + # If we have more than one match, we pick the toplevel one. This + # can easily be the case if there is a dist folder which contains + # an extracted tarball for testing purposes. + if len(filenames) > 1: + filenames.sort(key=depth_of_directory) + + return os.path.join(base, filenames[0]) + + def _generate_metadata_legacy(install_req): # type: (InstallRequirement) -> str req_details_str = install_req.name or "from {}".format(install_req.link) @@ -63,8 +120,8 @@ def _generate_metadata_legacy(install_req): command_desc='python setup.py egg_info', ) - # Return the metadata directory. - return install_req.find_egg_info() + # Return the .egg-info directory. + return find_egg_info(install_req) def _generate_metadata(install_req): diff --git a/src/pip/_internal/req/req_install.py b/src/pip/_internal/req/req_install.py index c59a4166e..5f1fdb74b 100644 --- a/src/pip/_internal/req/req_install.py +++ b/src/pip/_internal/req/req_install.py @@ -613,58 +613,6 @@ class InstallRequirement(object): return os.path.join(metadata_dir, distinfo_dir) - def find_egg_info(self): - # type: () -> str - def looks_like_virtual_env(path): - return ( - os.path.lexists(os.path.join(path, 'bin', 'python')) or - os.path.exists(os.path.join(path, 'Scripts', 'Python.exe')) - ) - - def locate_editable_egg_info(base): - candidates = [] - for root, dirs, files in os.walk(base): - for dir_ in vcs.dirnames: - if dir_ in dirs: - dirs.remove(dir_) - # Iterate over a copy of ``dirs``, since mutating - # a list while iterating over it can cause trouble. - # (See https://github.com/pypa/pip/pull/462.) - for dir_ in list(dirs): - if looks_like_virtual_env(os.path.join(root, dir_)): - dirs.remove(dir_) - # Also don't search through tests - elif dir_ == 'test' or dir_ == 'tests': - dirs.remove(dir_) - candidates.extend(os.path.join(root, dir_) for dir_ in dirs) - return [f for f in candidates if f.endswith('.egg-info')] - - def depth_of_directory(dir_): - return ( - dir_.count(os.path.sep) + - (os.path.altsep and dir_.count(os.path.altsep) or 0) - ) - - if self.editable: - base = self.source_dir - filenames = locate_editable_egg_info(base) - else: - base = os.path.join(self.unpacked_source_directory, 'pip-egg-info') - filenames = os.listdir(base) - - if not filenames: - raise InstallationError( - "Files/directories not found in %s" % base - ) - - # If we have more than one match, we pick the toplevel one. This - # can easily be the case if there is a dist folder which contains - # an extracted tarball for testing purposes. - if len(filenames) > 1: - filenames.sort(key=depth_of_directory) - - return os.path.join(base, filenames[0]) - @property def metadata(self): # type: () -> Any