def analyze_dists(root: Path, dist_dir: str) -> Optional[DistInfo]:
    if not dist_dir:
        return None
    name = None
    version = None
    tarball_path = None
    dists = root / dist_dir
    for dist in dists.iterdir():
        if ".tar" in dist.suffixes:
            tmp_path = Path(tempfile.mkdtemp(suffix="tarfile"))
            shutil.unpack_archive(dist, tmp_path)
            tarball_path = tmp_path / dist.name
            # drop '.tar.gz' suffix
            tarball_path = tarball_path.with_suffix("").with_suffix("")
            assert tarball_path.is_dir()
            nam, ver = parse_sdist_filename(dist.name)
        else:
            nam, ver, build, tags = parse_wheel_filename(dist.name)
        if name is not None:
            assert name == nam, f"{nam} != {name} for {dist}"
        else:
            name = nam
        if version is not None:
            assert version == ver, f"{ver} != {version} for {dist}"
        else:
            version = ver

    assert version is not None
    assert name is not None
    assert tarball_path is not None
    return DistInfo(str(version), name, tarball_path)
Example #2
0
    def from_url(url: str) -> "WheelInfo":
        """Parse wheels URL and extract available metadata

        See https://www.python.org/dev/peps/pep-0427/#file-name-convention
        """
        file_name = Path(url).name
        name, version, build, tags = parse_wheel_filename(file_name)
        return WheelInfo(
            name=name,
            version=version,
            filename=file_name,
            build=build,
            tags=tags,
            url=url,
        )
Example #3
0
def find_compatible_wheel(wheels: Sequence[T], identifier: str) -> T | None:
    """
    Finds a wheel with an abi3 or a none ABI tag in `wheels` compatible with the Python interpreter
    specified by `identifier` that is previously built.
    """

    interpreter, platform = identifier.split("-")
    for wheel in wheels:
        _, _, _, tags = parse_wheel_filename(wheel.name)
        for tag in tags:
            if tag.abi == "abi3":
                # ABI3 wheels must start with cp3 for impl and tag
                if not (interpreter.startswith("cp3")
                        and tag.interpreter.startswith("cp3")):
                    continue
            elif tag.abi == "none":
                # CPythonless wheels must include py3 tag
                if tag.interpreter[:3] != "py3":
                    continue
            else:
                # Other types of wheels are not detected, this is looking for previously built wheels.
                continue

            if tag.interpreter != "py3" and int(tag.interpreter[3:]) > int(
                    interpreter[3:]):
                # If a minor version number is given, it has to be lower than the current one.
                continue

            if platform.startswith(("manylinux", "musllinux", "macosx")):
                # Linux, macOS require the beginning and ending match (macos/manylinux version doesn't need to)
                os_, arch = platform.split("_", 1)
                if not tag.platform.startswith(os_):
                    continue
                if not tag.platform.endswith(f"_{arch}"):
                    continue
            else:
                # Windows should exactly match
                if not tag.platform == platform:
                    continue

            # If all the filters above pass, then the wheel is a previously built compatible wheel.
            return wheel

    return None
Example #4
0
def find_matching_wheels(wheel_paths: Iterable[Path]) -> Iterator[Path]:
    """
    Returns the sequence wheels whose tags match the Pyodide interpreter.

    Parameters
    ----------
    wheel_paths
        A list of paths to wheels

    Returns
    -------
    The subset of wheel_paths that have tags that match the Pyodide interpreter.
    """
    wheel_paths = list(wheel_paths)
    wheel_tags_list: list[frozenset[Tag]] = []
    for wheel in wheel_paths:
        _, _, _, tags = parse_wheel_filename(wheel.name)
        wheel_tags_list.append(tags)
    for supported_tag in pyodide_tags():
        for wheel_path, wheel_tags in zip(wheel_paths, wheel_tags_list):
            if supported_tag in wheel_tags:
                yield wheel_path
Example #5
0
def test_parse_wheel_filename(filename, name, version, build, tags):
    assert parse_wheel_filename(filename) == (name, version, build, tags)
Example #6
0
def test_parse_wheel_invalid_filename(filename):
    with pytest.raises(InvalidWheelFilename):
        parse_wheel_filename(filename)
Example #7
0
def add_platforms(
    in_wheel: str,
    platforms: Iterable[str],
    out_path: Optional[str] = None,
    clobber: bool = False,
) -> Optional[str]:
    """Add platform tags `platforms` to `in_wheel` filename and WHEEL tags

    Add any platform tags in `platforms` that are missing from `in_wheel`
    filename.

    Add any platform tags in `platforms` that are missing from `in_wheel`
    ``WHEEL`` file.

    Parameters
    ----------
    in_wheel : str
        Filename of wheel to which to add platform tags
    platforms : iterable
        platform tags to add to wheel filename and WHEEL tags - e.g.
        ``('macosx_10_9_intel', 'macosx_10_9_x86_64')
    out_path : None or str, optional
        Directory to which to write new wheel.  Default is directory containing
        `in_wheel`
    clobber : bool, optional
        If True, overwrite existing output filename, otherwise raise error

    Returns
    -------
    out_wheel : None or str
        Absolute path of wheel file written, or None if no wheel file written.
    """
    in_wheel = abspath(in_wheel)
    out_path = dirname(in_wheel) if out_path is None else abspath(out_path)
    name, version, _, tags = parse_wheel_filename(basename(in_wheel))
    info_fname = f"{name}-{version}.dist-info/WHEEL"

    # Check what tags we have
    platform_tags = {tag.platform for tag in tags}
    extra_fname_tags = [tag for tag in platforms if tag not in platform_tags]
    in_wheel_base, ext = splitext(basename(in_wheel))
    out_wheel_base = ".".join([in_wheel_base] + extra_fname_tags)
    out_wheel = pjoin(out_path, out_wheel_base + ext)
    if exists(out_wheel) and not clobber:
        raise WheelToolsError(
            "Not overwriting {0}; set clobber=True to overwrite".format(
                out_wheel))
    with InWheelCtx(in_wheel) as ctx:
        info = read_pkg_info(info_fname)
        if info["Root-Is-Purelib"] == "true":
            raise WheelToolsError("Cannot add platforms to pure wheel")
        in_info_tags = [tag for name, tag in info.items() if name == "Tag"]
        # Python version, C-API version combinations
        pyc_apis = ["-".join(tag.split("-")[:2]) for tag in in_info_tags]
        # unique Python version, C-API version combinations
        pyc_apis = unique_by_index(pyc_apis)
        # Add new platform tags for each Python version, C-API combination
        required_tags = ["-".join(tup) for tup in product(pyc_apis, platforms)]
        needs_write = False
        for req_tag in required_tags:
            if req_tag in in_info_tags:
                continue
            needs_write = True
            info.add_header("Tag", req_tag)
        if needs_write:
            write_pkg_info(info_fname, info)
            # Tell context manager to write wheel on exit by setting filename
            ctx.out_wheel = out_wheel
    return ctx.out_wheel
def get_info(wheel_path: str) -> Message:
    name, version, _, _ = parse_wheel_filename(basename(wheel_path))
    with ZipFile(wheel_path) as zip_file:
        return read_pkg_info_bytes(
            zip_file.read(f"{name}-{version}.dist-info/WHEEL")
        )