Exemple #1
0
    def test_supported_multiarch_darwin(self) -> None:
        """
        Multi-arch wheels (intel) are supported on components (i386, x86_64)
        """
        universal = compatibility_tags.get_supported(
            "27", platforms=["macosx_10_5_universal"], impl="cp")
        intel = compatibility_tags.get_supported(
            "27", platforms=["macosx_10_5_intel"], impl="cp")
        x64 = compatibility_tags.get_supported(
            "27", platforms=["macosx_10_5_x86_64"], impl="cp")
        i386 = compatibility_tags.get_supported("27",
                                                platforms=["macosx_10_5_i386"],
                                                impl="cp")
        ppc = compatibility_tags.get_supported("27",
                                               platforms=["macosx_10_5_ppc"],
                                               impl="cp")
        ppc64 = compatibility_tags.get_supported(
            "27", platforms=["macosx_10_5_ppc64"], impl="cp")

        w = Wheel("simple-0.1-cp27-none-macosx_10_5_intel.whl")
        assert w.supported(tags=intel)
        assert w.supported(tags=x64)
        assert w.supported(tags=i386)
        assert not w.supported(tags=universal)
        assert not w.supported(tags=ppc)
        assert not w.supported(tags=ppc64)
        w = Wheel("simple-0.1-cp27-none-macosx_10_5_universal.whl")
        assert w.supported(tags=universal)
        assert w.supported(tags=intel)
        assert w.supported(tags=x64)
        assert w.supported(tags=i386)
        assert w.supported(tags=ppc)
        assert w.supported(tags=ppc64)
Exemple #2
0
    def test_supported_multiarch_darwin(self):
        """
        Multi-arch wheels (intel) are supported on components (i386, x86_64)
        """
        universal = compatibility_tags.get_supported(
            '27', platforms=['macosx_10_5_universal'], impl='cp')
        intel = compatibility_tags.get_supported(
            '27', platforms=['macosx_10_5_intel'], impl='cp')
        x64 = compatibility_tags.get_supported(
            '27', platforms=['macosx_10_5_x86_64'], impl='cp')
        i386 = compatibility_tags.get_supported('27',
                                                platforms=['macosx_10_5_i386'],
                                                impl='cp')
        ppc = compatibility_tags.get_supported('27',
                                               platforms=['macosx_10_5_ppc'],
                                               impl='cp')
        ppc64 = compatibility_tags.get_supported(
            '27', platforms=['macosx_10_5_ppc64'], impl='cp')

        w = Wheel('simple-0.1-cp27-none-macosx_10_5_intel.whl')
        assert w.supported(tags=intel)
        assert w.supported(tags=x64)
        assert w.supported(tags=i386)
        assert not w.supported(tags=universal)
        assert not w.supported(tags=ppc)
        assert not w.supported(tags=ppc64)
        w = Wheel('simple-0.1-cp27-none-macosx_10_5_universal.whl')
        assert w.supported(tags=universal)
        assert w.supported(tags=intel)
        assert w.supported(tags=x64)
        assert w.supported(tags=i386)
        assert w.supported(tags=ppc)
        assert w.supported(tags=ppc64)
Exemple #3
0
 def test_supported_osx_version(self):
     """
     Wheels built for macOS 10.6 are supported on 10.9
     """
     tags = compatibility_tags.get_supported(
         '27', platforms=['macosx_10_9_intel'], impl='cp')
     w = Wheel('simple-0.1-cp27-none-macosx_10_6_intel.whl')
     assert w.supported(tags=tags)
     w = Wheel('simple-0.1-cp27-none-macosx_10_9_intel.whl')
     assert w.supported(tags=tags)
Exemple #4
0
 def test_supported_osx_version(self) -> None:
     """
     Wheels built for macOS 10.6 are supported on 10.9
     """
     tags = compatibility_tags.get_supported(
         "27", platforms=["macosx_10_9_intel"], impl="cp")
     w = Wheel("simple-0.1-cp27-none-macosx_10_6_intel.whl")
     assert w.supported(tags=tags)
     w = Wheel("simple-0.1-cp27-none-macosx_10_9_intel.whl")
     assert w.supported(tags=tags)
Exemple #5
0
 def test_support_index_min(self):
     """
     Test results from `support_index_min`
     """
     tags = [
         Tag('py2', 'none', 'TEST'),
         Tag('py2', 'TEST', 'any'),
         Tag('py2', 'none', 'any'),
     ]
     w = Wheel('simple-0.1-py2-none-any.whl')
     assert w.support_index_min(tags=tags) == 2
     w = Wheel('simple-0.1-py2-none-TEST.whl')
     assert w.support_index_min(tags=tags) == 0
Exemple #6
0
 def test_support_index_min(self) -> None:
     """
     Test results from `support_index_min`
     """
     tags = [
         Tag("py2", "none", "TEST"),
         Tag("py2", "TEST", "any"),
         Tag("py2", "none", "any"),
     ]
     w = Wheel("simple-0.1-py2-none-any.whl")
     assert w.support_index_min(tags=tags) == 2
     w = Wheel("simple-0.1-py2-none-TEST.whl")
     assert w.support_index_min(tags=tags) == 0
Exemple #7
0
    def test_not_supported_multiarch_darwin(self):
        """
        Single-arch wheels (x86_64) are not supported on multi-arch (intel)
        """
        universal = compatibility_tags.get_supported(
            '27', platforms=['macosx_10_5_universal'], impl='cp')
        intel = compatibility_tags.get_supported(
            '27', platforms=['macosx_10_5_intel'], impl='cp')

        w = Wheel('simple-0.1-cp27-none-macosx_10_5_i386.whl')
        assert not w.supported(tags=intel)
        assert not w.supported(tags=universal)
        w = Wheel('simple-0.1-cp27-none-macosx_10_5_x86_64.whl')
        assert not w.supported(tags=intel)
        assert not w.supported(tags=universal)
Exemple #8
0
    def test_not_supported_multiarch_darwin(self) -> None:
        """
        Single-arch wheels (x86_64) are not supported on multi-arch (intel)
        """
        universal = compatibility_tags.get_supported(
            "27", platforms=["macosx_10_5_universal"], impl="cp")
        intel = compatibility_tags.get_supported(
            "27", platforms=["macosx_10_5_intel"], impl="cp")

        w = Wheel("simple-0.1-cp27-none-macosx_10_5_i386.whl")
        assert not w.supported(tags=intel)
        assert not w.supported(tags=universal)
        w = Wheel("simple-0.1-cp27-none-macosx_10_5_x86_64.whl")
        assert not w.supported(tags=intel)
        assert not w.supported(tags=universal)
Exemple #9
0
    def get(
            self,
            link,  # type: Link
            package_name,  # type: Optional[str]
            supported_tags,  # type: List[Pep425Tag]
    ):
        # type: (...) -> Link
        candidates = []

        if not package_name:
            return link

        canonical_package_name = canonicalize_name(package_name)
        for wheel_name in self._get_candidates(link, canonical_package_name):
            try:
                wheel = Wheel(wheel_name)
            except InvalidWheelFilename:
                continue
            if canonicalize_name(wheel.name) != canonical_package_name:
                logger.debug(
                    "Ignoring cached wheel {} for {} as it "
                    "does not match the expected distribution name {}.".format(
                        wheel_name, link, package_name))
                continue
            if not wheel.supported(supported_tags):
                # Built for a different python/arch/etc
                continue
            candidates.append(
                (wheel.support_index_min(supported_tags), wheel_name))

        if not candidates:
            return link

        return self._link_for_candidate(link, min(candidates)[1])
Exemple #10
0
 def test_wheel_pattern_multi_values(self):
     w = Wheel('simple-1.1-py2.py3-abi1.abi2-any.whl')
     assert w.name == 'simple'
     assert w.version == '1.1'
     assert w.pyversions == ['py2', 'py3']
     assert w.abis == ['abi1', 'abi2']
     assert w.plats == ['any']
Exemple #11
0
 def test_std_wheel_pattern(self) -> None:
     w = Wheel("simple-1.1.1-py2-none-any.whl")
     assert w.name == "simple"
     assert w.version == "1.1.1"
     assert w.pyversions == ["py2"]
     assert w.abis == ["none"]
     assert w.plats == ["any"]
Exemple #12
0
 def test_std_wheel_pattern(self):
     w = Wheel('simple-1.1.1-py2-none-any.whl')
     assert w.name == 'simple'
     assert w.version == '1.1.1'
     assert w.pyversions == ['py2']
     assert w.abis == ['none']
     assert w.plats == ['any']
Exemple #13
0
 def test_wheel_pattern_multi_values(self) -> None:
     w = Wheel("simple-1.1-py2.py3-abi1.abi2-any.whl")
     assert w.name == "simple"
     assert w.version == "1.1"
     assert w.pyversions == ["py2", "py3"]
     assert w.abis == ["abi1", "abi2"]
     assert w.plats == ["any"]
Exemple #14
0
 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,
     )
     return self.make_requirement_from_candidate(cand)
Exemple #15
0
def _verify_one(req: InstallRequirement, wheel_path: str) -> None:
    canonical_name = canonicalize_name(req.name or "")
    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),
        )
    dist = get_wheel_distribution(FilesystemWheel(wheel_path), canonical_name)
    dist_verstr = str(dist.version)
    if canonicalize_version(dist_verstr) != canonicalize_version(w.version):
        raise InvalidWheelFilename(
            "Wheel has unexpected file name: expected {!r}, "
            "got {!r}".format(dist_verstr, w.version),
        )
    metadata_version_value = dist.metadata_version
    if metadata_version_value is None:
        raise UnsupportedWheel("Missing Metadata-Version")
    try:
        metadata_version = Version(metadata_version_value)
    except InvalidVersion:
        msg = f"Invalid Metadata-Version: {metadata_version_value}"
        raise UnsupportedWheel(msg)
    if metadata_version >= Version("1.2") and not isinstance(dist.version, Version):
        raise UnsupportedWheel(
            "Metadata 1.2 mandates PEP 440 version, "
            "but {!r} is not".format(dist_verstr)
        )
Exemple #16
0
 def test_version_underscore_conversion(self):
     """
     Test that we convert '_' to '-' for versions parsed out of wheel
     filenames
     """
     w = Wheel('simple-0.1_1-py2-none-any.whl')
     assert w.version == '0.1-1'
    def _fetch_metadata_using_lazy_wheel(self, link):
        # type: (Link) -> Optional[Distribution]
        """Fetch metadata using lazy wheel, if possible."""
        if not self.use_lazy_wheel:
            return None
        if self.require_hashes:
            logger.debug('Lazy wheel is not used as hash checking is required')
            return None
        if link.is_file or not link.is_wheel:
            logger.debug(
                'Lazy wheel is not used as '
                '%r does not points to a remote wheel',
                link,
            )
            return None

        wheel = Wheel(link.filename)
        name = canonicalize_name(wheel.name)
        logger.info(
            'Obtaining dependency information from %s %s',
            name,
            wheel.version,
        )
        url = link.url.split('#', 1)[0]
        try:
            return dist_from_wheel_url(name, url, self._session)
        except HTTPRangeRequestUnsupported:
            logger.debug('%s does not support range requests', url)
            return None
Exemple #18
0
 def test_support_index_min__none_supported(self):
     """
     Test a wheel not supported by the given tags.
     """
     w = Wheel('simple-0.1-py2-none-any.whl')
     with pytest.raises(ValueError):
         w.support_index_min(tags=[])
Exemple #19
0
 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)
Exemple #20
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, 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 = ()  # type: BuildTag
        binary_preference = 0
        link = candidate.link
        if link.is_wheel:
            # can raise InvalidWheelFilename
            wheel = Wheel(link.filename)
            if not wheel.supported(valid_tags):
                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
            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)
        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,
            build_tag, pri,
        )
Exemple #21
0
    def get(
            self,
            link,  # type: Link
            package_name,  # type: Optional[str]
            supported_tags,  # type: List[Tag]
    ):

        # type: (...) -> Link

        candidates = []

        if not package_name:

            return link

        canonical_package_name = canonicalize_name(package_name)

        for wheel_name, wheel_dir in self._get_candidates(
                link, canonical_package_name):

            try:

                wheel = Wheel(wheel_name)

            except InvalidWheelFilename:

                continue

            if canonicalize_name(wheel.name) != canonical_package_name:

                logger.debug(
                    "Ignoring cached wheel %s for %s as it "
                    "does not match the expected distribution name %s.",
                    wheel_name,
                    link,
                    package_name,
                )

                continue

            if not wheel.supported(supported_tags):

                # Built for a different python/arch/etc

                continue

            candidates.append((
                wheel.support_index_min(supported_tags),
                wheel_name,
                wheel_dir,
            ))

        if not candidates:

            return link

        _, wheel_name, wheel_dir = min(candidates)

        return Link(path_to_url(os.path.join(wheel_dir, wheel_name)))
Exemple #22
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)
Exemple #23
0
 def test_wheel_with_build_tag(self) -> None:
     # pip doesn't do anything with build tags, but theoretically, we might
     # see one, in this case the build tag = '4'
     w = Wheel("simple-1.1-4-py2-none-any.whl")
     assert w.name == "simple"
     assert w.version == "1.1"
     assert w.pyversions == ["py2"]
     assert w.abis == ["none"]
     assert w.plats == ["any"]
Exemple #24
0
 def test_not_supported_osx_version(self):
     """
     Wheels built for macOS 10.9 are not supported on 10.6
     """
     tags = pep425tags.get_supported('27',
                                     platform='macosx_10_6_intel',
                                     impl='cp')
     w = Wheel('simple-0.1-cp27-none-macosx_10_9_intel.whl')
     assert not w.supported(tags=tags)
Exemple #25
0
 def test_wheel_with_build_tag(self):
     # pip doesn't do anything with build tags, but theoretically, we might
     # see one, in this case the build tag = '4'
     w = Wheel('simple-1.1-4-py2-none-any.whl')
     assert w.name == 'simple'
     assert w.version == '1.1'
     assert w.pyversions == ['py2']
     assert w.abis == ['none']
     assert w.plats == ['any']
Exemple #26
0
    def __init__(
            self,
            link,  # type: Link
            template,  # type: InstallRequirement
            factory,  # type: Factory
            name=None,  # type: Optional[str]
            version=None,  # type: Optional[_BaseVersion]
    ):

        # type: (...) -> None

        source_link = link

        cache_entry = factory.get_wheel_cache_entry(link, name)

        if cache_entry is not None:

            logger.debug("Using cached wheel link: %s", cache_entry.link)

            link = cache_entry.link

        ireq = make_install_req_from_link(link, template)

        assert ireq.link == link

        if ireq.link.is_wheel and not ireq.link.is_file:

            wheel = Wheel(ireq.link.filename)

            wheel_name = canonicalize_name(wheel.name)

            assert name == wheel_name, (
                f"{name!r} != {wheel_name!r} for wheel")

            # Version may not be present for PEP 508 direct URLs

            if version is not None:

                wheel_version = Version(wheel.version)

                assert version == wheel_version, (
                    "{!r} != {!r} for wheel {}".format(version, wheel_version,
                                                       name))

        if (cache_entry is not None and cache_entry.persistent
                and template.link is template.original_link):

            ireq.original_link_is_in_wheel_cache = True

        super().__init__(
            link=link,
            source_link=source_link,
            ireq=ireq,
            factory=factory,
            name=name,
            version=version,
        )
Exemple #27
0
    def __init__(
        self,
        link: Link,
        template: InstallRequirement,
        factory: "Factory",
        name: Optional[NormalizedName] = None,
        version: Optional[CandidateVersion] = None,
    ) -> None:
        source_link = link
        cache_entry = factory.get_wheel_cache_entry(link, name)
        if cache_entry is not None:
            logger.debug("Using cached wheel link: %s", cache_entry.link)
            link = cache_entry.link
        ireq = make_install_req_from_link(link, template)
        assert ireq.link == link
        if ireq.link.is_wheel and not ireq.link.is_file:
            wheel = Wheel(ireq.link.filename)
            wheel_name = canonicalize_name(wheel.name)
            assert name == wheel_name, f"{name!r} != {wheel_name!r} for wheel"
            # Version may not be present for PEP 508 direct URLs
            if version is not None:
                wheel_version = Version(wheel.version)
                assert version == wheel_version, "{!r} != {!r} for wheel {}".format(
                    version, wheel_version, name
                )

        if cache_entry is not None:
            if cache_entry.persistent and template.link is template.original_link:
                ireq.original_link_is_in_wheel_cache = True
            if cache_entry.origin is not None:
                ireq.download_info = cache_entry.origin
            else:
                # Legacy cache entry that does not have origin.json.
                # download_info may miss the archive_info.hash field.
                ireq.download_info = direct_url_from_link(
                    source_link, link_is_in_wheel_cache=cache_entry.persistent
                )

        super().__init__(
            link=link,
            source_link=source_link,
            ireq=ireq,
            factory=factory,
            name=name,
            version=version,
        )
Exemple #28
0
 def _fetch_metadata(preparer, link):
     # type: (Link) -> Optional[Distribution]
     """Fetch metadata, using lazy wheel if possible."""
     use_lazy_wheel = preparer.use_lazy_wheel
     remote_wheel = link.is_wheel and not link.is_file
     if use_lazy_wheel and remote_wheel and not preparer.require_hashes:
         wheel = Wheel(link.filename)
         name = canonicalize_name(wheel.name)
         # If HTTPRangeRequestUnsupported is raised, fallback silently.
         with indent_log(), suppress(HTTPRangeRequestUnsupported):
             logger.info(
                 'Obtaining dependency information from %s %s',
                 name, wheel.version,
             )
             url = link.url.split('#', 1)[0]
             session = preparer.downloader._session
             return dist_from_wheel_url(name, url, session)
     return None
Exemple #29
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))
Exemple #30
0
    def evaluate_link(self, link):
        # type: (Link) -> Tuple[bool, Optional[str]]
        """
        Determine whether a link is a candidate for installation.

        :return: A tuple (is_candidate, result), where `result` is (1) a
            version string if `is_candidate` is True, and (2) if
            `is_candidate` is False, an optional string to log the reason
            the link fails to qualify.
        """
        version = None
        if link.is_yanked and not self._allow_yanked:
            reason = link.yanked_reason or '<none given>'
            return (False, f'yanked for reason: {reason}')

        if link.egg_fragment:
            egg_info = link.egg_fragment
            ext = link.ext
        else:
            egg_info, ext = link.splitext()
            if not ext:
                return (False, 'not a file')
            if ext not in SUPPORTED_EXTENSIONS:
                return (False, f'unsupported archive format: {ext}')
            if "binary" not in self._formats and ext == WHEEL_EXTENSION:
                reason = 'No binaries permitted for {}'.format(
                    self.project_name)
                return (False, reason)
            if "macosx10" in link.path and ext == '.zip':
                return (False, 'macosx10 one')
            if ext == WHEEL_EXTENSION:
                try:
                    wheel = Wheel(link.filename)
                except InvalidWheelFilename:
                    return (False, 'invalid wheel filename')
                if canonicalize_name(wheel.name) != self._canonical_name:
                    reason = 'wrong project name (not {})'.format(
                        self.project_name)
                    return (False, reason)

                supported_tags = self._target_python.get_tags()
                if not wheel.supported(supported_tags):
                    # Include the wheel's tags in the reason string to
                    # simplify troubleshooting compatibility issues.
                    file_tags = wheel.get_formatted_file_tags()
                    reason = ("none of the wheel's tags match: {}".format(
                        ', '.join(file_tags)))
                    return (False, reason)

                version = wheel.version

        # This should be up by the self.ok_binary check, but see issue 2700.
        if "source" not in self._formats and ext != WHEEL_EXTENSION:
            reason = f'No sources permitted for {self.project_name}'
            return (False, reason)

        if not version:
            version = _extract_version_from_fragment(
                egg_info,
                self._canonical_name,
            )
        if not version:
            reason = f'Missing project version for {self.project_name}'
            return (False, reason)

        match = self._py_version_re.search(version)
        if match:
            version = version[:match.start()]
            py_version = match.group(1)
            if py_version != self._target_python.py_version:
                return (False, 'Python version is incorrect')

        supports_python = _check_link_requires_python(
            link,
            version_info=self._target_python.py_version_info,
            ignore_requires_python=self._ignore_requires_python,
        )
        if not supports_python:
            # Return None for the reason text to suppress calling
            # _log_skipped_link().
            return (False, None)

        logger.debug('Found link %s, version: %s', link, version)

        return (True, version)