| """Utility class to inspect an extracted wheel directory""" |
| import glob |
| import os |
| import stat |
| import zipfile |
| from typing import Dict, Optional, Set |
| |
| import pkg_resources |
| import pkginfo |
| |
| |
| def current_umask() -> int: |
| """Get the current umask which involves having to set it temporarily.""" |
| mask = os.umask(0) |
| os.umask(mask) |
| return mask |
| |
| |
| def set_extracted_file_to_default_mode_plus_executable(path: str) -> None: |
| """ |
| Make file present at path have execute for user/group/world |
| (chmod +x) is no-op on windows per python docs |
| """ |
| os.chmod(path, (0o777 & ~current_umask() | 0o111)) |
| |
| |
| class Wheel: |
| """Representation of the compressed .whl file""" |
| |
| def __init__(self, path: str): |
| self._path = path |
| |
| @property |
| def path(self) -> str: |
| return self._path |
| |
| @property |
| def name(self) -> str: |
| return str(self.metadata.name) |
| |
| @property |
| def metadata(self) -> pkginfo.Wheel: |
| return pkginfo.get_metadata(self.path) |
| |
| def dependencies(self, extras_requested: Optional[Set[str]] = None) -> Set[str]: |
| dependency_set = set() |
| |
| for wheel_req in self.metadata.requires_dist: |
| req = pkg_resources.Requirement(wheel_req) # type: ignore |
| |
| if req.marker is None or any( |
| req.marker.evaluate({"extra": extra}) |
| for extra in extras_requested or [""] |
| ): |
| dependency_set.add(req.name) # type: ignore |
| |
| return dependency_set |
| |
| def unzip(self, directory: str) -> None: |
| with zipfile.ZipFile(self.path, "r") as whl: |
| whl.extractall(directory) |
| # The following logic is borrowed from Pip: |
| # https://github.com/pypa/pip/blob/cc48c07b64f338ac5e347d90f6cb4efc22ed0d0b/src/pip/_internal/utils/unpacking.py#L240 |
| for info in whl.infolist(): |
| name = info.filename |
| # Do not attempt to modify directories. |
| if name.endswith("/") or name.endswith("\\"): |
| continue |
| mode = info.external_attr >> 16 |
| # if mode and regular file and any execute permissions for |
| # user/group/world? |
| if mode and stat.S_ISREG(mode) and mode & 0o111: |
| name = os.path.join(directory, name) |
| set_extracted_file_to_default_mode_plus_executable(name) |
| |
| |
| def get_dist_info(wheel_dir: str) -> str: |
| """"Returns the relative path to the dist-info directory if it exists. |
| |
| Args: |
| wheel_dir: The root of the extracted wheel directory. |
| |
| Returns: |
| Relative path to the dist-info directory if it exists, else, None. |
| """ |
| dist_info_dirs = glob.glob(os.path.join(wheel_dir, "*.dist-info")) |
| if not dist_info_dirs: |
| raise ValueError( |
| "No *.dist-info directory found. %s is not a valid Wheel." % wheel_dir |
| ) |
| |
| if len(dist_info_dirs) > 1: |
| raise ValueError( |
| "Found more than 1 *.dist-info directory. %s is not a valid Wheel." |
| % wheel_dir |
| ) |
| |
| return dist_info_dirs[0] |
| |
| |
| def get_dot_data_directory(wheel_dir: str) -> Optional[str]: |
| """Returns the relative path to the data directory if it exists. |
| |
| See: https://www.python.org/dev/peps/pep-0491/#the-data-directory |
| |
| Args: |
| wheel_dir: The root of the extracted wheel directory. |
| |
| Returns: |
| Relative path to the data directory if it exists, else, None. |
| """ |
| |
| dot_data_dirs = glob.glob(os.path.join(wheel_dir, "*.data")) |
| if not dot_data_dirs: |
| return None |
| |
| if len(dot_data_dirs) > 1: |
| raise ValueError( |
| "Found more than 1 *.data directory. %s is not a valid Wheel." % wheel_dir |
| ) |
| |
| return dot_data_dirs[0] |
| |
| |
| def parse_wheel_meta_file(wheel_dir: str) -> Dict[str, str]: |
| """Parses the given WHEEL file into a dictionary. |
| |
| Args: |
| wheel_dir: The file path of the WHEEL metadata file in dist-info. |
| |
| Returns: |
| The WHEEL file mapped into a dictionary. |
| """ |
| contents = {} |
| with open(wheel_dir, "r") as wheel_file: |
| for line in wheel_file: |
| cleaned = line.strip() |
| if not cleaned: |
| continue |
| try: |
| key, value = cleaned.split(":", maxsplit=1) |
| contents[key] = value.strip() |
| except ValueError: |
| raise RuntimeError( |
| "Encounted invalid line in WHEEL file: '%s'" % cleaned |
| ) |
| return contents |