test
This commit is contained in:
		| @ -0,0 +1,124 @@ | ||||
| import contextlib | ||||
| import hashlib | ||||
| import logging | ||||
| import os | ||||
| from types import TracebackType | ||||
| from typing import Dict, Generator, Optional, Set, Type, Union | ||||
|  | ||||
| from pip._internal.models.link import Link | ||||
| from pip._internal.req.req_install import InstallRequirement | ||||
| from pip._internal.utils.temp_dir import TempDirectory | ||||
|  | ||||
| logger = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| @contextlib.contextmanager | ||||
| def update_env_context_manager(**changes: str) -> Generator[None, None, None]: | ||||
|     target = os.environ | ||||
|  | ||||
|     # Save values from the target and change them. | ||||
|     non_existent_marker = object() | ||||
|     saved_values: Dict[str, Union[object, str]] = {} | ||||
|     for name, new_value in changes.items(): | ||||
|         try: | ||||
|             saved_values[name] = target[name] | ||||
|         except KeyError: | ||||
|             saved_values[name] = non_existent_marker | ||||
|         target[name] = new_value | ||||
|  | ||||
|     try: | ||||
|         yield | ||||
|     finally: | ||||
|         # Restore original values in the target. | ||||
|         for name, original_value in saved_values.items(): | ||||
|             if original_value is non_existent_marker: | ||||
|                 del target[name] | ||||
|             else: | ||||
|                 assert isinstance(original_value, str)  # for mypy | ||||
|                 target[name] = original_value | ||||
|  | ||||
|  | ||||
| @contextlib.contextmanager | ||||
| def get_build_tracker() -> Generator["BuildTracker", None, None]: | ||||
|     root = os.environ.get("PIP_BUILD_TRACKER") | ||||
|     with contextlib.ExitStack() as ctx: | ||||
|         if root is None: | ||||
|             root = ctx.enter_context(TempDirectory(kind="build-tracker")).path | ||||
|             ctx.enter_context(update_env_context_manager(PIP_BUILD_TRACKER=root)) | ||||
|             logger.debug("Initialized build tracking at %s", root) | ||||
|  | ||||
|         with BuildTracker(root) as tracker: | ||||
|             yield tracker | ||||
|  | ||||
|  | ||||
| class BuildTracker: | ||||
|     def __init__(self, root: str) -> None: | ||||
|         self._root = root | ||||
|         self._entries: Set[InstallRequirement] = set() | ||||
|         logger.debug("Created build tracker: %s", self._root) | ||||
|  | ||||
|     def __enter__(self) -> "BuildTracker": | ||||
|         logger.debug("Entered build tracker: %s", self._root) | ||||
|         return self | ||||
|  | ||||
|     def __exit__( | ||||
|         self, | ||||
|         exc_type: Optional[Type[BaseException]], | ||||
|         exc_val: Optional[BaseException], | ||||
|         exc_tb: Optional[TracebackType], | ||||
|     ) -> None: | ||||
|         self.cleanup() | ||||
|  | ||||
|     def _entry_path(self, link: Link) -> str: | ||||
|         hashed = hashlib.sha224(link.url_without_fragment.encode()).hexdigest() | ||||
|         return os.path.join(self._root, hashed) | ||||
|  | ||||
|     def add(self, req: InstallRequirement) -> None: | ||||
|         """Add an InstallRequirement to build tracking.""" | ||||
|  | ||||
|         assert req.link | ||||
|         # Get the file to write information about this requirement. | ||||
|         entry_path = self._entry_path(req.link) | ||||
|  | ||||
|         # Try reading from the file. If it exists and can be read from, a build | ||||
|         # is already in progress, so a LookupError is raised. | ||||
|         try: | ||||
|             with open(entry_path) as fp: | ||||
|                 contents = fp.read() | ||||
|         except FileNotFoundError: | ||||
|             pass | ||||
|         else: | ||||
|             message = "{} is already being built: {}".format(req.link, contents) | ||||
|             raise LookupError(message) | ||||
|  | ||||
|         # If we're here, req should really not be building already. | ||||
|         assert req not in self._entries | ||||
|  | ||||
|         # Start tracking this requirement. | ||||
|         with open(entry_path, "w", encoding="utf-8") as fp: | ||||
|             fp.write(str(req)) | ||||
|         self._entries.add(req) | ||||
|  | ||||
|         logger.debug("Added %s to build tracker %r", req, self._root) | ||||
|  | ||||
|     def remove(self, req: InstallRequirement) -> None: | ||||
|         """Remove an InstallRequirement from build tracking.""" | ||||
|  | ||||
|         assert req.link | ||||
|         # Delete the created file and the corresponding entries. | ||||
|         os.unlink(self._entry_path(req.link)) | ||||
|         self._entries.remove(req) | ||||
|  | ||||
|         logger.debug("Removed %s from build tracker %r", req, self._root) | ||||
|  | ||||
|     def cleanup(self) -> None: | ||||
|         for req in set(self._entries): | ||||
|             self.remove(req) | ||||
|  | ||||
|         logger.debug("Removed build tracker: %r", self._root) | ||||
|  | ||||
|     @contextlib.contextmanager | ||||
|     def track(self, req: InstallRequirement) -> Generator[None, None, None]: | ||||
|         self.add(req) | ||||
|         yield | ||||
|         self.remove(req) | ||||
| @ -0,0 +1,39 @@ | ||||
| """Metadata generation logic for source distributions. | ||||
| """ | ||||
|  | ||||
| import os | ||||
|  | ||||
| from pip._vendor.pep517.wrappers import Pep517HookCaller | ||||
|  | ||||
| from pip._internal.build_env import BuildEnvironment | ||||
| from pip._internal.exceptions import ( | ||||
|     InstallationSubprocessError, | ||||
|     MetadataGenerationFailed, | ||||
| ) | ||||
| from pip._internal.utils.subprocess import runner_with_spinner_message | ||||
| from pip._internal.utils.temp_dir import TempDirectory | ||||
|  | ||||
|  | ||||
| def generate_metadata( | ||||
|     build_env: BuildEnvironment, backend: Pep517HookCaller, details: str | ||||
| ) -> str: | ||||
|     """Generate metadata using mechanisms described in PEP 517. | ||||
|  | ||||
|     Returns the generated metadata directory. | ||||
|     """ | ||||
|     metadata_tmpdir = TempDirectory(kind="modern-metadata", globally_managed=True) | ||||
|  | ||||
|     metadata_dir = metadata_tmpdir.path | ||||
|  | ||||
|     with build_env: | ||||
|         # Note that Pep517HookCaller implements a fallback for | ||||
|         # prepare_metadata_for_build_wheel, so we don't have to | ||||
|         # consider the possibility that this hook doesn't exist. | ||||
|         runner = runner_with_spinner_message("Preparing metadata (pyproject.toml)") | ||||
|         with backend.subprocess_runner(runner): | ||||
|             try: | ||||
|                 distinfo_dir = backend.prepare_metadata_for_build_wheel(metadata_dir) | ||||
|             except InstallationSubprocessError as error: | ||||
|                 raise MetadataGenerationFailed(package_details=details) from error | ||||
|  | ||||
|     return os.path.join(metadata_dir, distinfo_dir) | ||||
| @ -0,0 +1,41 @@ | ||||
| """Metadata generation logic for source distributions. | ||||
| """ | ||||
|  | ||||
| import os | ||||
|  | ||||
| from pip._vendor.pep517.wrappers import Pep517HookCaller | ||||
|  | ||||
| from pip._internal.build_env import BuildEnvironment | ||||
| from pip._internal.exceptions import ( | ||||
|     InstallationSubprocessError, | ||||
|     MetadataGenerationFailed, | ||||
| ) | ||||
| from pip._internal.utils.subprocess import runner_with_spinner_message | ||||
| from pip._internal.utils.temp_dir import TempDirectory | ||||
|  | ||||
|  | ||||
| def generate_editable_metadata( | ||||
|     build_env: BuildEnvironment, backend: Pep517HookCaller, details: str | ||||
| ) -> str: | ||||
|     """Generate metadata using mechanisms described in PEP 660. | ||||
|  | ||||
|     Returns the generated metadata directory. | ||||
|     """ | ||||
|     metadata_tmpdir = TempDirectory(kind="modern-metadata", globally_managed=True) | ||||
|  | ||||
|     metadata_dir = metadata_tmpdir.path | ||||
|  | ||||
|     with build_env: | ||||
|         # Note that Pep517HookCaller implements a fallback for | ||||
|         # prepare_metadata_for_build_wheel/editable, so we don't have to | ||||
|         # consider the possibility that this hook doesn't exist. | ||||
|         runner = runner_with_spinner_message( | ||||
|             "Preparing editable metadata (pyproject.toml)" | ||||
|         ) | ||||
|         with backend.subprocess_runner(runner): | ||||
|             try: | ||||
|                 distinfo_dir = backend.prepare_metadata_for_build_editable(metadata_dir) | ||||
|             except InstallationSubprocessError as error: | ||||
|                 raise MetadataGenerationFailed(package_details=details) from error | ||||
|  | ||||
|     return os.path.join(metadata_dir, distinfo_dir) | ||||
| @ -0,0 +1,74 @@ | ||||
| """Metadata generation logic for legacy source distributions. | ||||
| """ | ||||
|  | ||||
| import logging | ||||
| import os | ||||
|  | ||||
| from pip._internal.build_env import BuildEnvironment | ||||
| from pip._internal.cli.spinners import open_spinner | ||||
| from pip._internal.exceptions import ( | ||||
|     InstallationError, | ||||
|     InstallationSubprocessError, | ||||
|     MetadataGenerationFailed, | ||||
| ) | ||||
| from pip._internal.utils.setuptools_build import make_setuptools_egg_info_args | ||||
| from pip._internal.utils.subprocess import call_subprocess | ||||
| from pip._internal.utils.temp_dir import TempDirectory | ||||
|  | ||||
| logger = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| def _find_egg_info(directory: str) -> str: | ||||
|     """Find an .egg-info subdirectory in `directory`.""" | ||||
|     filenames = [f for f in os.listdir(directory) if f.endswith(".egg-info")] | ||||
|  | ||||
|     if not filenames: | ||||
|         raise InstallationError(f"No .egg-info directory found in {directory}") | ||||
|  | ||||
|     if len(filenames) > 1: | ||||
|         raise InstallationError( | ||||
|             "More than one .egg-info directory found in {}".format(directory) | ||||
|         ) | ||||
|  | ||||
|     return os.path.join(directory, filenames[0]) | ||||
|  | ||||
|  | ||||
| def generate_metadata( | ||||
|     build_env: BuildEnvironment, | ||||
|     setup_py_path: str, | ||||
|     source_dir: str, | ||||
|     isolated: bool, | ||||
|     details: str, | ||||
| ) -> str: | ||||
|     """Generate metadata using setup.py-based defacto mechanisms. | ||||
|  | ||||
|     Returns the generated metadata directory. | ||||
|     """ | ||||
|     logger.debug( | ||||
|         "Running setup.py (path:%s) egg_info for package %s", | ||||
|         setup_py_path, | ||||
|         details, | ||||
|     ) | ||||
|  | ||||
|     egg_info_dir = TempDirectory(kind="pip-egg-info", globally_managed=True).path | ||||
|  | ||||
|     args = make_setuptools_egg_info_args( | ||||
|         setup_py_path, | ||||
|         egg_info_dir=egg_info_dir, | ||||
|         no_user_config=isolated, | ||||
|     ) | ||||
|  | ||||
|     with build_env: | ||||
|         with open_spinner("Preparing metadata (setup.py)") as spinner: | ||||
|             try: | ||||
|                 call_subprocess( | ||||
|                     args, | ||||
|                     cwd=source_dir, | ||||
|                     command_desc="python setup.py egg_info", | ||||
|                     spinner=spinner, | ||||
|                 ) | ||||
|             except InstallationSubprocessError as error: | ||||
|                 raise MetadataGenerationFailed(package_details=details) from error | ||||
|  | ||||
|     # Return the .egg-info directory. | ||||
|     return _find_egg_info(egg_info_dir) | ||||
| @ -0,0 +1,37 @@ | ||||
| import logging | ||||
| import os | ||||
| from typing import Optional | ||||
|  | ||||
| from pip._vendor.pep517.wrappers import Pep517HookCaller | ||||
|  | ||||
| from pip._internal.utils.subprocess import runner_with_spinner_message | ||||
|  | ||||
| logger = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| def build_wheel_pep517( | ||||
|     name: str, | ||||
|     backend: Pep517HookCaller, | ||||
|     metadata_directory: str, | ||||
|     tempd: str, | ||||
| ) -> Optional[str]: | ||||
|     """Build one InstallRequirement using the PEP 517 build process. | ||||
|  | ||||
|     Returns path to wheel if successfully built. Otherwise, returns None. | ||||
|     """ | ||||
|     assert metadata_directory is not None | ||||
|     try: | ||||
|         logger.debug("Destination directory: %s", tempd) | ||||
|  | ||||
|         runner = runner_with_spinner_message( | ||||
|             f"Building wheel for {name} (pyproject.toml)" | ||||
|         ) | ||||
|         with backend.subprocess_runner(runner): | ||||
|             wheel_name = backend.build_wheel( | ||||
|                 tempd, | ||||
|                 metadata_directory=metadata_directory, | ||||
|             ) | ||||
|     except Exception: | ||||
|         logger.error("Failed building wheel for %s", name) | ||||
|         return None | ||||
|     return os.path.join(tempd, wheel_name) | ||||
| @ -0,0 +1,46 @@ | ||||
| import logging | ||||
| import os | ||||
| from typing import Optional | ||||
|  | ||||
| from pip._vendor.pep517.wrappers import HookMissing, Pep517HookCaller | ||||
|  | ||||
| from pip._internal.utils.subprocess import runner_with_spinner_message | ||||
|  | ||||
| logger = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| def build_wheel_editable( | ||||
|     name: str, | ||||
|     backend: Pep517HookCaller, | ||||
|     metadata_directory: str, | ||||
|     tempd: str, | ||||
| ) -> Optional[str]: | ||||
|     """Build one InstallRequirement using the PEP 660 build process. | ||||
|  | ||||
|     Returns path to wheel if successfully built. Otherwise, returns None. | ||||
|     """ | ||||
|     assert metadata_directory is not None | ||||
|     try: | ||||
|         logger.debug("Destination directory: %s", tempd) | ||||
|  | ||||
|         runner = runner_with_spinner_message( | ||||
|             f"Building editable for {name} (pyproject.toml)" | ||||
|         ) | ||||
|         with backend.subprocess_runner(runner): | ||||
|             try: | ||||
|                 wheel_name = backend.build_editable( | ||||
|                     tempd, | ||||
|                     metadata_directory=metadata_directory, | ||||
|                 ) | ||||
|             except HookMissing as e: | ||||
|                 logger.error( | ||||
|                     "Cannot build editable %s because the build " | ||||
|                     "backend does not have the %s hook", | ||||
|                     name, | ||||
|                     e, | ||||
|                 ) | ||||
|                 return None | ||||
|     except Exception: | ||||
|         logger.error("Failed building editable for %s", name) | ||||
|         return None | ||||
|     return os.path.join(tempd, wheel_name) | ||||
| @ -0,0 +1,102 @@ | ||||
| import logging | ||||
| import os.path | ||||
| from typing import List, Optional | ||||
|  | ||||
| from pip._internal.cli.spinners import open_spinner | ||||
| from pip._internal.utils.setuptools_build import make_setuptools_bdist_wheel_args | ||||
| from pip._internal.utils.subprocess import call_subprocess, format_command_args | ||||
|  | ||||
| logger = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| def format_command_result( | ||||
|     command_args: List[str], | ||||
|     command_output: str, | ||||
| ) -> str: | ||||
|     """Format command information for logging.""" | ||||
|     command_desc = format_command_args(command_args) | ||||
|     text = f"Command arguments: {command_desc}\n" | ||||
|  | ||||
|     if not command_output: | ||||
|         text += "Command output: None" | ||||
|     elif logger.getEffectiveLevel() > logging.DEBUG: | ||||
|         text += "Command output: [use --verbose to show]" | ||||
|     else: | ||||
|         if not command_output.endswith("\n"): | ||||
|             command_output += "\n" | ||||
|         text += f"Command output:\n{command_output}" | ||||
|  | ||||
|     return text | ||||
|  | ||||
|  | ||||
| def get_legacy_build_wheel_path( | ||||
|     names: List[str], | ||||
|     temp_dir: str, | ||||
|     name: str, | ||||
|     command_args: List[str], | ||||
|     command_output: str, | ||||
| ) -> Optional[str]: | ||||
|     """Return the path to the wheel in the temporary build directory.""" | ||||
|     # Sort for determinism. | ||||
|     names = sorted(names) | ||||
|     if not names: | ||||
|         msg = ("Legacy build of wheel for {!r} created no files.\n").format(name) | ||||
|         msg += format_command_result(command_args, command_output) | ||||
|         logger.warning(msg) | ||||
|         return None | ||||
|  | ||||
|     if len(names) > 1: | ||||
|         msg = ( | ||||
|             "Legacy build of wheel for {!r} created more than one file.\n" | ||||
|             "Filenames (choosing first): {}\n" | ||||
|         ).format(name, names) | ||||
|         msg += format_command_result(command_args, command_output) | ||||
|         logger.warning(msg) | ||||
|  | ||||
|     return os.path.join(temp_dir, names[0]) | ||||
|  | ||||
|  | ||||
| def build_wheel_legacy( | ||||
|     name: str, | ||||
|     setup_py_path: str, | ||||
|     source_dir: str, | ||||
|     global_options: List[str], | ||||
|     build_options: List[str], | ||||
|     tempd: str, | ||||
| ) -> Optional[str]: | ||||
|     """Build one unpacked package using the "legacy" build process. | ||||
|  | ||||
|     Returns path to wheel if successfully built. Otherwise, returns None. | ||||
|     """ | ||||
|     wheel_args = make_setuptools_bdist_wheel_args( | ||||
|         setup_py_path, | ||||
|         global_options=global_options, | ||||
|         build_options=build_options, | ||||
|         destination_dir=tempd, | ||||
|     ) | ||||
|  | ||||
|     spin_message = f"Building wheel for {name} (setup.py)" | ||||
|     with open_spinner(spin_message) as spinner: | ||||
|         logger.debug("Destination directory: %s", tempd) | ||||
|  | ||||
|         try: | ||||
|             output = call_subprocess( | ||||
|                 wheel_args, | ||||
|                 command_desc="python setup.py bdist_wheel", | ||||
|                 cwd=source_dir, | ||||
|                 spinner=spinner, | ||||
|             ) | ||||
|         except Exception: | ||||
|             spinner.finish("error") | ||||
|             logger.error("Failed building wheel for %s", name) | ||||
|             return None | ||||
|  | ||||
|         names = os.listdir(tempd) | ||||
|         wheel_path = get_legacy_build_wheel_path( | ||||
|             names=names, | ||||
|             temp_dir=tempd, | ||||
|             name=name, | ||||
|             command_args=wheel_args, | ||||
|             command_output=output, | ||||
|         ) | ||||
|         return wheel_path | ||||
		Reference in New Issue
	
	Block a user