137 lines
4.6 KiB
Python
137 lines
4.6 KiB
Python
"""Metadata generation logic for source distributions.
|
|
"""
|
|
|
|
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
|
|
from pip._internal.req.req_install import InstallRequirement
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def get_metadata_generator(install_req):
|
|
# type: (InstallRequirement) -> Callable[[InstallRequirement], str]
|
|
"""Return a callable metadata generator for this InstallRequirement.
|
|
|
|
A metadata generator takes an InstallRequirement (install_req) as an input,
|
|
generates metadata via the appropriate process for that install_req and
|
|
returns the generated metadata directory.
|
|
"""
|
|
if not install_req.use_pep517:
|
|
return _generate_metadata_legacy
|
|
|
|
return _generate_metadata
|
|
|
|
|
|
def _find_egg_info(source_directory, is_editable):
|
|
# type: (str, bool) -> str
|
|
"""Find an .egg-info in `source_directory`, based on `is_editable`.
|
|
"""
|
|
|
|
def looks_like_virtual_env(path):
|
|
# type: (str) -> bool
|
|
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):
|
|
# type: (str) -> List[str]
|
|
candidates = [] # type: List[str]
|
|
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_):
|
|
# type: (str) -> int
|
|
return (
|
|
dir_.count(os.path.sep) +
|
|
(os.path.altsep and dir_.count(os.path.altsep) or 0)
|
|
)
|
|
|
|
base = source_directory
|
|
if is_editable:
|
|
filenames = locate_editable_egg_info(base)
|
|
else:
|
|
base = os.path.join(base, '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)
|
|
logger.debug(
|
|
'Running setup.py (path:%s) egg_info for package %s',
|
|
install_req.setup_py_path, req_details_str,
|
|
)
|
|
|
|
# Compose arguments for subprocess call
|
|
base_cmd = make_setuptools_shim_args(install_req.setup_py_path)
|
|
if install_req.isolated:
|
|
base_cmd += ["--no-user-cfg"]
|
|
|
|
# For non-editable installs, don't put the .egg-info files at the root,
|
|
# to avoid confusion due to the source code being considered an installed
|
|
# egg.
|
|
egg_base_option = [] # type: List[str]
|
|
if not install_req.editable:
|
|
egg_info_dir = os.path.join(
|
|
install_req.unpacked_source_directory, 'pip-egg-info',
|
|
)
|
|
egg_base_option = ['--egg-base', egg_info_dir]
|
|
|
|
# setuptools complains if the target directory does not exist.
|
|
ensure_dir(egg_info_dir)
|
|
|
|
with install_req.build_env:
|
|
call_subprocess(
|
|
base_cmd + ["egg_info"] + egg_base_option,
|
|
cwd=install_req.unpacked_source_directory,
|
|
command_desc='python setup.py egg_info',
|
|
)
|
|
|
|
# Return the .egg-info directory.
|
|
return _find_egg_info(
|
|
install_req.unpacked_source_directory,
|
|
install_req.editable,
|
|
)
|
|
|
|
|
|
def _generate_metadata(install_req):
|
|
# type: (InstallRequirement) -> str
|
|
return install_req.prepare_pep517_metadata()
|