Path: blob/master/venv/Lib/site-packages/pip/_internal/wheel_builder.py
811 views
"""Orchestrator for building wheels from InstallRequirements.1"""23# The following comment should be removed at some point in the future.4# mypy: strict-optional=False56import logging7import os.path8import re9import shutil1011from pip._internal.models.link import Link12from pip._internal.operations.build.wheel import build_wheel_pep51713from pip._internal.operations.build.wheel_legacy import build_wheel_legacy14from pip._internal.utils.logging import indent_log15from pip._internal.utils.misc import ensure_dir, hash_file, is_wheel_installed16from pip._internal.utils.setuptools_build import make_setuptools_clean_args17from pip._internal.utils.subprocess import call_subprocess18from pip._internal.utils.temp_dir import TempDirectory19from pip._internal.utils.typing import MYPY_CHECK_RUNNING20from pip._internal.utils.urls import path_to_url21from pip._internal.vcs import vcs2223if MYPY_CHECK_RUNNING:24from typing import (25Any, Callable, Iterable, List, Optional, Pattern, Tuple,26)2728from pip._internal.cache import WheelCache29from pip._internal.req.req_install import InstallRequirement3031BinaryAllowedPredicate = Callable[[InstallRequirement], bool]32BuildResult = Tuple[List[InstallRequirement], List[InstallRequirement]]3334logger = logging.getLogger(__name__)353637def _contains_egg_info(38s, _egg_info_re=re.compile(r'([a-z0-9_.]+)-([a-z0-9_.!+-]+)', re.I)):39# type: (str, Pattern[str]) -> bool40"""Determine whether the string looks like an egg_info.4142:param s: The string to parse. E.g. foo-2.143"""44return bool(_egg_info_re.search(s))454647def _should_build(48req, # type: InstallRequirement49need_wheel, # type: bool50check_binary_allowed, # type: BinaryAllowedPredicate51):52# type: (...) -> bool53"""Return whether an InstallRequirement should be built into a wheel."""54if req.constraint:55# never build requirements that are merely constraints56return False57if req.is_wheel:58if need_wheel:59logger.info(60'Skipping %s, due to already being wheel.', req.name,61)62return False6364if need_wheel:65# i.e. pip wheel, not pip install66return True6768# From this point, this concerns the pip install command only69# (need_wheel=False).7071if req.editable or not req.source_dir:72return False7374if not check_binary_allowed(req):75logger.info(76"Skipping wheel build for %s, due to binaries "77"being disabled for it.", req.name,78)79return False8081if not req.use_pep517 and not is_wheel_installed():82# we don't build legacy requirements if wheel is not installed83logger.info(84"Using legacy setup.py install for %s, "85"since package 'wheel' is not installed.", req.name,86)87return False8889return True909192def should_build_for_wheel_command(93req, # type: InstallRequirement94):95# type: (...) -> bool96return _should_build(97req, need_wheel=True, check_binary_allowed=_always_true98)99100101def should_build_for_install_command(102req, # type: InstallRequirement103check_binary_allowed, # type: BinaryAllowedPredicate104):105# type: (...) -> bool106return _should_build(107req, need_wheel=False, check_binary_allowed=check_binary_allowed108)109110111def _should_cache(112req, # type: InstallRequirement113):114# type: (...) -> Optional[bool]115"""116Return whether a built InstallRequirement can be stored in the persistent117wheel cache, assuming the wheel cache is available, and _should_build()118has determined a wheel needs to be built.119"""120if not should_build_for_install_command(121req, check_binary_allowed=_always_true122):123# never cache if pip install would not have built124# (editable mode, etc)125return False126127if req.link and req.link.is_vcs:128# VCS checkout. Do not cache129# unless it points to an immutable commit hash.130assert not req.editable131assert req.source_dir132vcs_backend = vcs.get_backend_for_scheme(req.link.scheme)133assert vcs_backend134if vcs_backend.is_immutable_rev_checkout(req.link.url, req.source_dir):135return True136return False137138base, ext = req.link.splitext()139if _contains_egg_info(base):140return True141142# Otherwise, do not cache.143return False144145146def _get_cache_dir(147req, # type: InstallRequirement148wheel_cache, # type: WheelCache149):150# type: (...) -> str151"""Return the persistent or temporary cache directory where the built152wheel need to be stored.153"""154cache_available = bool(wheel_cache.cache_dir)155if cache_available and _should_cache(req):156cache_dir = wheel_cache.get_path_for_link(req.link)157else:158cache_dir = wheel_cache.get_ephem_path_for_link(req.link)159return cache_dir160161162def _always_true(_):163# type: (Any) -> bool164return True165166167def _build_one(168req, # type: InstallRequirement169output_dir, # type: str170build_options, # type: List[str]171global_options, # type: List[str]172):173# type: (...) -> Optional[str]174"""Build one wheel.175176:return: The filename of the built wheel, or None if the build failed.177"""178try:179ensure_dir(output_dir)180except OSError as e:181logger.warning(182"Building wheel for %s failed: %s",183req.name, e,184)185return None186187# Install build deps into temporary directory (PEP 518)188with req.build_env:189return _build_one_inside_env(190req, output_dir, build_options, global_options191)192193194def _build_one_inside_env(195req, # type: InstallRequirement196output_dir, # type: str197build_options, # type: List[str]198global_options, # type: List[str]199):200# type: (...) -> Optional[str]201with TempDirectory(kind="wheel") as temp_dir:202if req.use_pep517:203wheel_path = build_wheel_pep517(204name=req.name,205backend=req.pep517_backend,206metadata_directory=req.metadata_directory,207build_options=build_options,208tempd=temp_dir.path,209)210else:211wheel_path = build_wheel_legacy(212name=req.name,213setup_py_path=req.setup_py_path,214source_dir=req.unpacked_source_directory,215global_options=global_options,216build_options=build_options,217tempd=temp_dir.path,218)219220if wheel_path is not None:221wheel_name = os.path.basename(wheel_path)222dest_path = os.path.join(output_dir, wheel_name)223try:224wheel_hash, length = hash_file(wheel_path)225shutil.move(wheel_path, dest_path)226logger.info('Created wheel for %s: '227'filename=%s size=%d sha256=%s',228req.name, wheel_name, length,229wheel_hash.hexdigest())230logger.info('Stored in directory: %s', output_dir)231return dest_path232except Exception as e:233logger.warning(234"Building wheel for %s failed: %s",235req.name, e,236)237# Ignore return, we can't do anything else useful.238if not req.use_pep517:239_clean_one_legacy(req, global_options)240return None241242243def _clean_one_legacy(req, global_options):244# type: (InstallRequirement, List[str]) -> bool245clean_args = make_setuptools_clean_args(246req.setup_py_path,247global_options=global_options,248)249250logger.info('Running setup.py clean for %s', req.name)251try:252call_subprocess(clean_args, cwd=req.source_dir)253return True254except Exception:255logger.error('Failed cleaning build dir for %s', req.name)256return False257258259def build(260requirements, # type: Iterable[InstallRequirement]261wheel_cache, # type: WheelCache262build_options, # type: List[str]263global_options, # type: List[str]264):265# type: (...) -> BuildResult266"""Build wheels.267268:return: The list of InstallRequirement that succeeded to build and269the list of InstallRequirement that failed to build.270"""271if not requirements:272return [], []273274# Build the wheels.275logger.info(276'Building wheels for collected packages: %s',277', '.join(req.name for req in requirements),278)279280with indent_log():281build_successes, build_failures = [], []282for req in requirements:283cache_dir = _get_cache_dir(req, wheel_cache)284wheel_file = _build_one(285req, cache_dir, build_options, global_options286)287if wheel_file:288# Update the link for this.289req.link = Link(path_to_url(wheel_file))290req.local_file_path = req.link.file_path291assert req.link.is_wheel292build_successes.append(req)293else:294build_failures.append(req)295296# notify success/failure297if build_successes:298logger.info(299'Successfully built %s',300' '.join([req.name for req in build_successes]),301)302if build_failures:303logger.info(304'Failed to build %s',305' '.join([req.name for req in build_failures]),306)307# Return a list of requirements that failed to build308return build_successes, build_failures309310311