예제 #1
0
 def get_metadata(self, name: str) -> str:
     try:
         return self._metadata[name].decode()
     except UnicodeDecodeError as e:
         # Augment the default error with the origin of the file.
         raise UnsupportedWheel(
             f"Error decoding metadata for {self._wheel_name}: {e} in {name} file"
         )
예제 #2
0
 def _fail_if_link_is_unsupported_wheel(self, link: Link) -> None:
     if not link.is_wheel:
         return
     wheel = Wheel(link.filename)
     if wheel.supported(self._finder.target_python.get_tags()):
         return
     msg = f"{link.filename} is not a supported wheel on this platform."
     raise UnsupportedWheel(msg)
예제 #3
0
 def get_metadata(self, name):
     # type: (str) -> str
     try:
         return super().get_metadata(name)
     except UnicodeDecodeError as e:
         # Augment the default error with the origin of the file.
         raise UnsupportedWheel(
             f"Error decoding metadata for {self._wheel_name}: {e}")
예제 #4
0
def wheel_dist_info_dir(source, name):

    # type: (ZipFile, str) -> str
    """Returns the name of the contained .dist-info directory.



    Raises AssertionError or UnsupportedWheel if not found, >1 found, or

    it doesn't match the provided name.

    """

    # Zip file path separators must be /

    subdirs = {p.split("/", 1)[0] for p in source.namelist()}

    info_dirs = [s for s in subdirs if s.endswith('.dist-info')]

    if not info_dirs:

        raise UnsupportedWheel(".dist-info directory not found")

    if len(info_dirs) > 1:

        raise UnsupportedWheel(
            "multiple .dist-info directories found: {}".format(
                ", ".join(info_dirs)))

    info_dir = info_dirs[0]

    info_dir_name = canonicalize_name(info_dir)

    canonical_name = canonicalize_name(name)

    if not info_dir_name.startswith(canonical_name):

        raise UnsupportedWheel(
            ".dist-info directory {!r} does not start with {!r}".format(
                info_dir, canonical_name))

    # Zip file paths can be unicode or str depending on the zip entry flags,

    # so normalize it.

    return ensure_str(info_dir)
예제 #5
0
def read_wheel_metadata_file(source, path):
    # type: (ZipFile, str) -> bytes
    try:
        return source.read(path)
        # BadZipFile for general corruption, KeyError for missing entry,
        # and RuntimeError for password-protected files
    except (BadZipFile, KeyError, RuntimeError) as e:
        raise UnsupportedWheel(f"could not read {path!r} file: {e!r}")
예제 #6
0
 def from_wheel(cls, wheel: Wheel, name: str) -> BaseDistribution:
     try:
         with wheel.as_zipfile() as zf:
             dist = WheelDistribution.from_zipfile(zf, name, wheel.location)
     except zipfile.BadZipFile as e:
         raise InvalidWheel(wheel.location, name) from e
     except UnsupportedWheel as e:
         raise UnsupportedWheel(f"{name} has an invalid wheel, {e}")
     return cls(dist, dist.info_location,
                pathlib.PurePosixPath(wheel.location))
예제 #7
0
    def _sort_key(self, candidate):
        # type: (InstallationCandidate) -> CandidateSortingKey
        """
        Function to pass as the `key` argument to a call to sorted() to sort
        InstallationCandidates by preference.

        Returns a tuple such that tuples sorting as greater using Python's
        default comparison operator are more preferred.

        The preference is as follows:

        First and foremost, yanked candidates (in the sense of PEP 592) are
        always less preferred than candidates that haven't been yanked. Then:

        If not finding wheels, they are sorted by version only.
        If finding wheels, then the sort order is by version, then:
          1. existing installs
          2. wheels ordered via Wheel.support_index_min(self._supported_tags)
          3. source archives
        If prefer_binary was set, then all wheels are sorted above sources.

        Note: it was considered to embed this logic into the Link
              comparison operators, but then different sdist links
              with the same version, would have to be considered equal
        """
        valid_tags = self._supported_tags
        support_num = len(valid_tags)
        build_tag = tuple()  # type: BuildTag
        binary_preference = 0
        link = candidate.location
        if link.is_wheel:
            # can raise InvalidWheelFilename
            wheel = Wheel(link.filename)
            if not wheel.supported(valid_tags):
                raise UnsupportedWheel(
                    "%s is not a supported wheel for this platform. It "
                    "can't be sorted." % wheel.filename)
            if self._prefer_binary:
                binary_preference = 1
            pri = -(wheel.support_index_min(valid_tags))
            if wheel.build_tag is not None:
                match = re.match(r'^(\d+)(.*)$', wheel.build_tag)
                build_tag_groups = match.groups()
                build_tag = (int(build_tag_groups[0]), build_tag_groups[1])
        else:  # sdist
            pri = -(support_num)
        yank_value = -1 * int(link.is_yanked)  # -1 for yanked.
        return (
            yank_value,
            binary_preference,
            candidate.version,
            build_tag,
            pri,
        )
예제 #8
0
 def read_text(self, filename: str) -> Optional[str]:
     try:
         data = self._files[pathlib.PurePosixPath(filename)]
     except KeyError:
         return None
     try:
         text = data.decode("utf-8")
     except UnicodeDecodeError as e:
         wheel = self.info_location.parent
         error = f"Error decoding metadata for {wheel}: {e} in {filename} file"
         raise UnsupportedWheel(error)
     return text
예제 #9
0
파일: wheel.py 프로젝트: hapsunday/pip
def wheel_metadata(source, dist_info_dir):
    # type: (ZipFile, str) -> Message
    """Return the WHEEL metadata of an extracted wheel, if possible.
    Otherwise, raise UnsupportedWheel.
    """
    try:
        # Zip file path separators must be /
        wheel_contents = source.read("{}/WHEEL".format(dist_info_dir))
        # BadZipFile for general corruption, KeyError for missing entry,
        # and RuntimeError for password-protected files
    except (BadZipFile, KeyError, RuntimeError) as e:
        raise UnsupportedWheel("could not read WHEEL file: {!r}".format(e))

    try:
        wheel_text = ensure_str(wheel_contents)
    except UnicodeDecodeError as e:
        raise UnsupportedWheel("error decoding WHEEL: {!r}".format(e))

    # FeedParser (used by Parser) does not raise any exceptions. The returned
    # message may have .defects populated, but for backwards-compatibility we
    # currently ignore them.
    return Parser().parsestr(wheel_text)
예제 #10
0
def check_compatibility(version, name):
    """
    Raises errors or warns if called with an incompatible Wheel-Version.

    Pip should refuse to install a Wheel-Version that's a major series
    ahead of what it's compatible with (e.g 2.0 > 1.1); and warn when
    installing a version only minor version ahead (e.g 1.2 > 1.1).

    version: a 2-tuple representing a Wheel-Version (Major, Minor)
    name: name of wheel or package to raise exception about

    :raises UnsupportedWheel: when an incompatible Wheel-Version is given
    """
    if not version:
        raise UnsupportedWheel("%s is in an unsupported or invalid wheel" %
                               name)
    if version[0] > VERSION_COMPATIBLE[0]:
        raise UnsupportedWheel(
            "%s's Wheel-Version (%s) is not compatible with this version "
            "of pip" % (name, ".".join(map(str, version))))
    elif version > VERSION_COMPATIBLE:
        logger.warning("Installing from a newer Wheel-Version (%s)",
                       ".".join(map(str, version)))
예제 #11
0
def wheel_version(wheel_data):

    # type: (Message) -> Tuple[int, ...]
    """Given WHEEL metadata, return the parsed Wheel-Version.

    Otherwise, raise UnsupportedWheel.

    """

    version_text = wheel_data["Wheel-Version"]

    if version_text is None:

        raise UnsupportedWheel("WHEEL is missing Wheel-Version")

    version = version_text.strip()

    try:

        return tuple(map(int, version.split('.')))

    except ValueError:

        raise UnsupportedWheel(f"invalid Wheel-Version: {version!r}")
예제 #12
0
def pkg_resources_distribution_for_wheel(wheel_zip, name, location):

    # type: (ZipFile, str, str) -> Distribution
    """Get a pkg_resources distribution given a wheel.



    :raises UnsupportedWheel: on any errors

    """

    info_dir, _ = parse_wheel(wheel_zip, name)

    metadata_files = [
        p for p in wheel_zip.namelist() if p.startswith(f"{info_dir}/")
    ]

    metadata_text = {}  # type: Dict[str, bytes]

    for path in metadata_files:

        # If a flag is set, namelist entries may be unicode in Python 2.

        # We coerce them to native str type to match the types used in the rest

        # of the code. This cannot fail because unicode can always be encoded

        # with UTF-8.

        full_path = ensure_str(path)

        _, metadata_name = full_path.split("/", 1)

        try:

            metadata_text[metadata_name] = read_wheel_metadata_file(
                wheel_zip, full_path)

        except UnsupportedWheel as e:

            raise UnsupportedWheel("{} has an invalid wheel, {}".format(
                name, str(e)))

    metadata = WheelMetadata(metadata_text, location)

    return DistInfoDistribution(location=location,
                                metadata=metadata,
                                project_name=name)
예제 #13
0
def wheel_metadata(source: ZipFile, dist_info_dir: str) -> Message:
    """Return the WHEEL metadata of an extracted wheel, if possible.
    Otherwise, raise UnsupportedWheel.
    """
    path = f"{dist_info_dir}/WHEEL"
    # Zip file path separators must be /
    wheel_contents = read_wheel_metadata_file(source, path)

    try:
        wheel_text = wheel_contents.decode()
    except UnicodeDecodeError as e:
        raise UnsupportedWheel(f"error decoding {path!r}: {e!r}")

    # FeedParser (used by Parser) does not raise any exceptions. The returned
    # message may have .defects populated, but for backwards-compatibility we
    # currently ignore them.
    return Parser().parsestr(wheel_text)
예제 #14
0
def parse_wheel(wheel_zip, name):
    # type: (ZipFile, str) -> Tuple[str, Message]
    """Extract information from the provided wheel, ensuring it meets basic
    standards.

    Returns the name of the .dist-info directory and the parsed WHEEL metadata.
    """
    try:
        info_dir = wheel_dist_info_dir(wheel_zip, name)
        metadata = wheel_metadata(wheel_zip, info_dir)
        version = wheel_version(metadata)
    except UnsupportedWheel as e:
        raise UnsupportedWheel("{} has an invalid wheel, {}".format(name, str(e)))

    check_compatibility(version, name)

    return info_dir, metadata
예제 #15
0
 def from_wheel(cls, wheel: Wheel, name: str) -> BaseDistribution:
     try:
         with wheel.as_zipfile() as zf:
             info_dir, _ = parse_wheel(zf, name)
             metadata_text = {
                 path.split("/", 1)[-1]: read_wheel_metadata_file(zf, path)
                 for path in zf.namelist()
                 if path.startswith(f"{info_dir}/")
             }
     except zipfile.BadZipFile as e:
         raise InvalidWheel(wheel.location, name) from e
     except UnsupportedWheel as e:
         raise UnsupportedWheel(f"{name} has an invalid wheel, {e}")
     dist = pkg_resources.DistInfoDistribution(
         location=wheel.location,
         metadata=WheelMetadata(metadata_text, wheel.location),
         project_name=name,
     )
     return cls(dist)
예제 #16
0
def check_compatibility(version, name):
    # type: (Tuple[int, ...], str) -> None
    """Raises errors or warns if called with an incompatible Wheel-Version.

    Pip should refuse to install a Wheel-Version that's a major series
    ahead of what it's compatible with (e.g 2.0 > 1.1); and warn when
    installing a version only minor version ahead (e.g 1.2 > 1.1).

    version: a 2-tuple representing a Wheel-Version (Major, Minor)
    name: name of wheel or package to raise exception about

    :raises UnsupportedWheel: when an incompatible Wheel-Version is given
    """
    if version[0] > VERSION_COMPATIBLE[0]:
        raise UnsupportedWheel(
            "%s's Wheel-Version (%s) is not compatible with this version "
            "of pip" % (name, '.'.join(map(str, version)))
        )
    elif version > VERSION_COMPATIBLE:
예제 #17
0
def _verify_one(req, wheel_path):
    # type: (InstallRequirement, str) -> None
    canonical_name = canonicalize_name(req.name)
    w = Wheel(os.path.basename(wheel_path))
    if canonicalize_name(w.name) != canonical_name:
        raise InvalidWheelFilename(
            "Wheel has unexpected file name: expected {!r}, "
            "got {!r}".format(canonical_name, w.name), )
    with zipfile.ZipFile(wheel_path, allowZip64=True) as zf:
        dist = pkg_resources_distribution_for_wheel(
            zf,
            canonical_name,
            wheel_path,
        )
    if canonicalize_version(dist.version) != canonicalize_version(w.version):
        raise InvalidWheelFilename(
            "Wheel has unexpected file name: expected {!r}, "
            "got {!r}".format(dist.version, w.version), )
    if _get_metadata_version(dist) >= Version("1.2") and not isinstance(
            dist.parsed_version, Version):
        raise UnsupportedWheel("Metadata 1.2 mandates PEP 440 version, "
                               "but {!r} is not".format(dist.version))
예제 #18
0
 def _sort_key(self, candidate):
     # type: (InstallationCandidate) -> CandidateSortingKey
     """
     Function used to generate link sort key for link tuples.
     The greater the return value, the more preferred it is.
     If not finding wheels, then sorted by version only.
     If finding wheels, then the sort order is by version, then:
       1. existing installs
       2. wheels ordered via Wheel.support_index_min(self._valid_tags)
       3. source archives
     If prefer_binary was set, then all wheels are sorted above sources.
     Note: it was considered to embed this logic into the Link
           comparison operators, but then different sdist links
           with the same version, would have to be considered equal
     """
     valid_tags = self._target_python.get_tags()
     support_num = len(valid_tags)
     build_tag = tuple()  # type: BuildTag
     binary_preference = 0
     if candidate.location.is_wheel:
         # can raise InvalidWheelFilename
         wheel = Wheel(candidate.location.filename)
         if not self._is_wheel_supported(wheel):
             raise UnsupportedWheel(
                 "%s is not a supported wheel for this platform. It "
                 "can't be sorted." % wheel.filename)
         if self._prefer_binary:
             binary_preference = 1
         pri = -(wheel.support_index_min(valid_tags))
         if wheel.build_tag is not None:
             match = re.match(r'^(\d+)(.*)$', wheel.build_tag)
             build_tag_groups = match.groups()
             build_tag = (int(build_tag_groups[0]), build_tag_groups[1])
     else:  # sdist
         pri = -(support_num)
     return (binary_preference, candidate.version, build_tag, pri)
예제 #19
0
파일: factory.py 프로젝트: khaledbkn/pip
 def make_requirement_from_install_req(self, ireq, requested_extras):
     # type: (InstallRequirement, Iterable[str]) -> Optional[Requirement]
     if not ireq.match_markers(requested_extras):
         logger.info(
             "Ignoring %s: markers '%s' don't match your environment",
             ireq.name,
             ireq.markers,
         )
         return None
     if not ireq.link:
         return SpecifierRequirement(ireq)
     if ireq.link.is_wheel:
         wheel = Wheel(ireq.link.filename)
         if not wheel.supported(self._finder.target_python.get_tags()):
             msg = "{} is not a supported wheel on this platform.".format(
                 wheel.filename,
             )
             raise UnsupportedWheel(msg)
     cand = self._make_candidate_from_link(
         ireq.link,
         extras=frozenset(ireq.extras),
         template=ireq,
         name=canonicalize_name(ireq.name) if ireq.name else None,
         version=None,
     )
     if cand is None:
         # There's no way we can satisfy a URL requirement if the underlying
         # candidate fails to build. An unnamed URL must be user-supplied, so
         # we fail eagerly. If the URL is named, an unsatisfiable requirement
         # can make the resolver do the right thing, either backtrack (and
         # maybe find some other requirement that's buildable) or raise a
         # ResolutionImpossible eventually.
         if not ireq.name:
             raise self._build_failures[ireq.link]
         return UnsatisfiableRequirement(canonicalize_name(ireq.name))
     return self.make_requirement_from_candidate(cand)
예제 #20
0
    def _sort_key(self,
                  candidate: InstallationCandidate) -> CandidateSortingKey:
        """
        Function to pass as the `key` argument to a call to sorted() to sort
        InstallationCandidates by preference.

        Returns a tuple such that tuples sorting as greater using Python's
        default comparison operator are more preferred.

        The preference is as follows:

        First and foremost, candidates with allowed (matching) hashes are
        always preferred over candidates without matching hashes. This is
        because e.g. if the only candidate with an allowed hash is yanked,
        we still want to use that candidate.

        Second, excepting hash considerations, candidates that have been
        yanked (in the sense of PEP 592) are always less preferred than
        candidates that haven't been yanked. Then:

        If not finding wheels, they are sorted by version only.
        If finding wheels, then the sort order is by version, then:
          1. existing installs
          2. wheels ordered via Wheel.support_index_min(self._supported_tags)
          3. source archives
        If prefer_binary was set, then all wheels are sorted above sources.

        Note: it was considered to embed this logic into the Link
              comparison operators, but then different sdist links
              with the same version, would have to be considered equal
        """
        valid_tags = self._supported_tags
        support_num = len(valid_tags)
        build_tag: BuildTag = ()
        binary_preference = 0
        link = candidate.link
        if link.is_wheel:
            # can raise InvalidWheelFilename
            wheel = Wheel(link.filename)
            try:
                pri = -(wheel.find_most_preferred_tag(
                    valid_tags, self._wheel_tag_preferences))
            except ValueError:
                raise UnsupportedWheel(
                    "{} is not a supported wheel for this platform. It "
                    "can't be sorted.".format(wheel.filename))
            if self._prefer_binary:
                binary_preference = 1
            if wheel.build_tag is not None:
                match = re.match(r"^(\d+)(.*)$", wheel.build_tag)
                build_tag_groups = match.groups()
                build_tag = (int(build_tag_groups[0]), build_tag_groups[1])
        else:  # sdist
            pri = -(support_num)
        has_allowed_hash = int(link.is_hash_allowed(self._hashes))
        yank_value = -1 * int(link.is_yanked)  # -1 for yanked.
        return (
            has_allowed_hash,
            yank_value,
            binary_preference,
            candidate.version,
            pri,
            build_tag,
        )
예제 #21
0
파일: wheel.py 프로젝트: guanym98k/Python_-
    """Returns the name of the contained .dist-info directory.

    Raises AssertionError or UnsupportedWheel if not found, >1 found, or
    it doesn't match the provided name.
    """
    # Zip file path separators must be /
<<<<<<< HEAD
    subdirs = set(p.split("/", 1)[0] for p in source.namelist())
=======
    subdirs = list(set(p.split("/")[0] for p in source.namelist()))
>>>>>>> b66a76afa15ab74019740676a52a071b85ed8f71

    info_dirs = [s for s in subdirs if s.endswith('.dist-info')]

    if not info_dirs:
        raise UnsupportedWheel(".dist-info directory not found")

    if len(info_dirs) > 1:
        raise UnsupportedWheel(
            "multiple .dist-info directories found: {}".format(
                ", ".join(info_dirs)
            )
        )

    info_dir = info_dirs[0]

    info_dir_name = canonicalize_name(info_dir)
    canonical_name = canonicalize_name(name)
    if not info_dir_name.startswith(canonical_name):
        raise UnsupportedWheel(
            ".dist-info directory {!r} does not start with {!r}".format(
예제 #22
0
파일: wheel.py 프로젝트: jsirois/pip
def install_unpacked_wheel(
        name,  # type: str
        wheeldir,  # type: str
        scheme,  # type: Scheme
        req_description,  # type: str
        pycompile=True,  # type: bool
        warn_script_location=True  # type: bool
):
    # type: (...) -> None
    """Install a wheel.

    :param name: Name of the project to install
    :param wheeldir: Base directory of the unpacked wheel
    :param scheme: Distutils scheme dictating the install directories
    :param req_description: String used in place of the requirement, for
        logging
    :param pycompile: Whether to byte-compile installed Python files
    :param warn_script_location: Whether to check that scripts are installed
        into a directory on PATH
    :raises UnsupportedWheel:
        * when the directory holds an unpacked wheel with incompatible
          Wheel-Version
        * when the .dist-info dir does not match the wheel
    """
    # TODO: Investigate and break this up.
    # TODO: Look into moving this into a dedicated class for representing an
    #       installation.

    version = wheel_version(wheeldir)
    check_compatibility(version, name)

    if root_is_purelib(name, wheeldir):
        lib_dir = scheme.purelib
    else:
        lib_dir = scheme.platlib

    info_dir = []  # type: List[str]
    data_dirs = []
    source = wheeldir.rstrip(os.path.sep) + os.path.sep

    # Record details of the files moved
    #   installed = files copied from the wheel to the destination
    #   changed = files changed while installing (scripts #! line typically)
    #   generated = files newly generated during the install (script wrappers)
    installed = {}  # type: Dict[str, str]
    changed = set()
    generated = []  # type: List[str]

    # Compile all of the pyc files that we're going to be installing
    if pycompile:
        with captured_stdout() as stdout:
            with warnings.catch_warnings():
                warnings.filterwarnings('ignore')
                compileall.compile_dir(source, force=True, quiet=True)
        logger.debug(stdout.getvalue())

    def record_installed(srcfile, destfile, modified=False):
        # type: (str, str, bool) -> None
        """Map archive RECORD paths to installation RECORD paths."""
        oldpath = normpath(srcfile, wheeldir)
        newpath = normpath(destfile, lib_dir)
        installed[oldpath] = newpath
        if modified:
            changed.add(destfile)

    def clobber(
        source,  # type: str
        dest,  # type: str
        is_base,  # type: bool
        fixer=None,  # type: Optional[Callable[[str], Any]]
        filter=None  # type: Optional[Callable[[str], bool]]
    ):
        # type: (...) -> None
        ensure_dir(dest)  # common for the 'include' path

        for dir, subdirs, files in os.walk(source):
            basedir = dir[len(source):].lstrip(os.path.sep)
            destdir = os.path.join(dest, basedir)
            if is_base and basedir.split(os.path.sep, 1)[0].endswith('.data'):
                continue
            for s in subdirs:
                destsubdir = os.path.join(dest, basedir, s)
                if is_base and basedir == '' and destsubdir.endswith('.data'):
                    data_dirs.append(s)
                    continue
                elif (is_base and basedir == '' and s.endswith('.dist-info')):
                    assert not info_dir, (
                        'Multiple .dist-info directories: {}, '.format(
                            destsubdir) + ', '.join(info_dir))
                    info_dir.append(destsubdir)
            for f in files:
                # Skip unwanted files
                if filter and filter(f):
                    continue
                srcfile = os.path.join(dir, f)
                destfile = os.path.join(dest, basedir, f)
                # directory creation is lazy and after the file filtering above
                # to ensure we don't install empty dirs; empty dirs can't be
                # uninstalled.
                ensure_dir(destdir)

                # copyfile (called below) truncates the destination if it
                # exists and then writes the new contents. This is fine in most
                # cases, but can cause a segfault if pip has loaded a shared
                # object (e.g. from pyopenssl through its vendored urllib3)
                # Since the shared object is mmap'd an attempt to call a
                # symbol in it will then cause a segfault. Unlinking the file
                # allows writing of new contents while allowing the process to
                # continue to use the old copy.
                if os.path.exists(destfile):
                    os.unlink(destfile)

                # We use copyfile (not move, copy, or copy2) to be extra sure
                # that we are not moving directories over (copyfile fails for
                # directories) as well as to ensure that we are not copying
                # over any metadata because we want more control over what
                # metadata we actually copy over.
                shutil.copyfile(srcfile, destfile)

                # Copy over the metadata for the file, currently this only
                # includes the atime and mtime.
                st = os.stat(srcfile)
                if hasattr(os, "utime"):
                    os.utime(destfile, (st.st_atime, st.st_mtime))

                # If our file is executable, then make our destination file
                # executable.
                if os.access(srcfile, os.X_OK):
                    st = os.stat(srcfile)
                    permissions = (st.st_mode | stat.S_IXUSR | stat.S_IXGRP
                                   | stat.S_IXOTH)
                    os.chmod(destfile, permissions)

                changed = False
                if fixer:
                    changed = fixer(destfile)
                record_installed(srcfile, destfile, changed)

    clobber(source, lib_dir, True)

    assert info_dir, "{} .dist-info directory not found".format(
        req_description)

    info_dir_name = canonicalize_name(os.path.basename(info_dir[0]))
    canonical_name = canonicalize_name(name)
    if not info_dir_name.startswith(canonical_name):
        raise UnsupportedWheel(
            "{} .dist-info directory {!r} does not start with {!r}".format(
                req_description, os.path.basename(info_dir[0]),
                canonical_name))

    # Get the defined entry points
    ep_file = os.path.join(info_dir[0], 'entry_points.txt')
    console, gui = get_entrypoints(ep_file)

    def is_entrypoint_wrapper(name):
        # type: (str) -> bool
        # EP, EP.exe and EP-script.py are scripts generated for
        # entry point EP by setuptools
        if name.lower().endswith('.exe'):
            matchname = name[:-4]
        elif name.lower().endswith('-script.py'):
            matchname = name[:-10]
        elif name.lower().endswith(".pya"):
            matchname = name[:-4]
        else:
            matchname = name
        # Ignore setuptools-generated scripts
        return (matchname in console or matchname in gui)

    for datadir in data_dirs:
        fixer = None
        filter = None
        for subdir in os.listdir(os.path.join(wheeldir, datadir)):
            fixer = None
            if subdir == 'scripts':
                fixer = fix_script
                filter = is_entrypoint_wrapper
            source = os.path.join(wheeldir, datadir, subdir)
            dest = getattr(scheme, subdir)
            clobber(source, dest, False, fixer=fixer, filter=filter)

    maker = PipScriptMaker(None, scheme.scripts)

    # Ensure old scripts are overwritten.
    # See https://github.com/pypa/pip/issues/1800
    maker.clobber = True

    # Ensure we don't generate any variants for scripts because this is almost
    # never what somebody wants.
    # See https://bitbucket.org/pypa/distlib/issue/35/
    maker.variants = {''}

    # This is required because otherwise distlib creates scripts that are not
    # executable.
    # See https://bitbucket.org/pypa/distlib/issue/32/
    maker.set_mode = True

    scripts_to_generate = []

    # Special case pip and setuptools to generate versioned wrappers
    #
    # The issue is that some projects (specifically, pip and setuptools) use
    # code in setup.py to create "versioned" entry points - pip2.7 on Python
    # 2.7, pip3.3 on Python 3.3, etc. But these entry points are baked into
    # the wheel metadata at build time, and so if the wheel is installed with
    # a *different* version of Python the entry points will be wrong. The
    # correct fix for this is to enhance the metadata to be able to describe
    # such versioned entry points, but that won't happen till Metadata 2.0 is
    # available.
    # In the meantime, projects using versioned entry points will either have
    # incorrect versioned entry points, or they will not be able to distribute
    # "universal" wheels (i.e., they will need a wheel per Python version).
    #
    # Because setuptools and pip are bundled with _ensurepip and virtualenv,
    # we need to use universal wheels. So, as a stopgap until Metadata 2.0, we
    # override the versioned entry points in the wheel and generate the
    # correct ones. This code is purely a short-term measure until Metadata 2.0
    # is available.
    #
    # To add the level of hack in this section of code, in order to support
    # ensurepip this code will look for an ``ENSUREPIP_OPTIONS`` environment
    # variable which will control which version scripts get installed.
    #
    # ENSUREPIP_OPTIONS=altinstall
    #   - Only pipX.Y and easy_install-X.Y will be generated and installed
    # ENSUREPIP_OPTIONS=install
    #   - pipX.Y, pipX, easy_install-X.Y will be generated and installed. Note
    #     that this option is technically if ENSUREPIP_OPTIONS is set and is
    #     not altinstall
    # DEFAULT
    #   - The default behavior is to install pip, pipX, pipX.Y, easy_install
    #     and easy_install-X.Y.
    pip_script = console.pop('pip', None)
    if pip_script:
        if "ENSUREPIP_OPTIONS" not in os.environ:
            scripts_to_generate.append('pip = ' + pip_script)

        if os.environ.get("ENSUREPIP_OPTIONS", "") != "altinstall":
            scripts_to_generate.append('pip%s = %s' %
                                       (sys.version_info[0], pip_script))

        scripts_to_generate.append('pip%s = %s' %
                                   (get_major_minor_version(), pip_script))
        # Delete any other versioned pip entry points
        pip_ep = [k for k in console if re.match(r'pip(\d(\.\d)?)?$', k)]
        for k in pip_ep:
            del console[k]
    easy_install_script = console.pop('easy_install', None)
    if easy_install_script:
        if "ENSUREPIP_OPTIONS" not in os.environ:
            scripts_to_generate.append('easy_install = ' + easy_install_script)

        scripts_to_generate.append(
            'easy_install-%s = %s' %
            (get_major_minor_version(), easy_install_script))
        # Delete any other versioned easy_install entry points
        easy_install_ep = [
            k for k in console if re.match(r'easy_install(-\d\.\d)?$', k)
        ]
        for k in easy_install_ep:
            del console[k]

    # Generate the console and GUI entry points specified in the wheel
    scripts_to_generate.extend('%s = %s' % kv for kv in console.items())

    gui_scripts_to_generate = ['%s = %s' % kv for kv in gui.items()]

    generated_console_scripts = []  # type: List[str]

    try:
        generated_console_scripts = maker.make_multiple(scripts_to_generate)
        generated.extend(generated_console_scripts)

        generated.extend(
            maker.make_multiple(gui_scripts_to_generate, {'gui': True}))
    except MissingCallableSuffix as e:
        entry = e.args[0]
        raise InstallationError(
            "Invalid script entry point: {} for req: {} - A callable "
            "suffix is required. Cf https://packaging.python.org/"
            "specifications/entry-points/#use-for-scripts for more "
            "information.".format(entry, req_description))

    if warn_script_location:
        msg = message_about_scripts_not_on_PATH(generated_console_scripts)
        if msg is not None:
            logger.warning(msg)

    # Record pip as the installer
    installer = os.path.join(info_dir[0], 'INSTALLER')
    temp_installer = os.path.join(info_dir[0], 'INSTALLER.pip')
    with open(temp_installer, 'wb') as installer_file:
        installer_file.write(b'pip\n')
    shutil.move(temp_installer, installer)
    generated.append(installer)

    # Record details of all files installed
    record = os.path.join(info_dir[0], 'RECORD')
    temp_record = os.path.join(info_dir[0], 'RECORD.pip')
    with open_for_csv(record, 'r') as record_in:
        with open_for_csv(temp_record, 'w+') as record_out:
            reader = csv.reader(record_in)
            outrows = get_csv_rows_for_installed(
                reader,
                installed=installed,
                changed=changed,
                generated=generated,
                lib_dir=lib_dir,
            )
            writer = csv.writer(record_out)
            # Sort to simplify testing.
            for row in sorted_outrows(outrows):
                writer.writerow(row)
    shutil.move(temp_record, record)