17db96d56Sopenharmony_ci#! /usr/bin/env python3
27db96d56Sopenharmony_ci
37db96d56Sopenharmony_ci"""
47db96d56Sopenharmony_ciCompare checksums for wheels in :mod:`ensurepip` against the Cheeseshop.
57db96d56Sopenharmony_ci
67db96d56Sopenharmony_ciWhen GitHub Actions executes the script, output is formatted accordingly.
77db96d56Sopenharmony_cihttps://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-a-notice-message
87db96d56Sopenharmony_ci"""
97db96d56Sopenharmony_ci
107db96d56Sopenharmony_ciimport hashlib
117db96d56Sopenharmony_ciimport json
127db96d56Sopenharmony_ciimport os
137db96d56Sopenharmony_ciimport re
147db96d56Sopenharmony_cifrom pathlib import Path
157db96d56Sopenharmony_cifrom urllib.request import urlopen
167db96d56Sopenharmony_ci
177db96d56Sopenharmony_ciPACKAGE_NAMES = ("pip", "setuptools")
187db96d56Sopenharmony_ciENSURE_PIP_ROOT = Path(__file__).parent.parent.parent / "Lib/ensurepip"
197db96d56Sopenharmony_ciWHEEL_DIR = ENSURE_PIP_ROOT / "_bundled"
207db96d56Sopenharmony_ciENSURE_PIP_INIT_PY_TEXT = (ENSURE_PIP_ROOT / "__init__.py").read_text(encoding="utf-8")
217db96d56Sopenharmony_ciGITHUB_ACTIONS = os.getenv("GITHUB_ACTIONS") == "true"
227db96d56Sopenharmony_ci
237db96d56Sopenharmony_ci
247db96d56Sopenharmony_cidef print_notice(file_path: str, message: str) -> None:
257db96d56Sopenharmony_ci    if GITHUB_ACTIONS:
267db96d56Sopenharmony_ci        message = f"::notice file={file_path}::{message}"
277db96d56Sopenharmony_ci    print(message, end="\n\n")
287db96d56Sopenharmony_ci
297db96d56Sopenharmony_ci
307db96d56Sopenharmony_cidef print_error(file_path: str, message: str) -> None:
317db96d56Sopenharmony_ci    if GITHUB_ACTIONS:
327db96d56Sopenharmony_ci        message = f"::error file={file_path}::{message}"
337db96d56Sopenharmony_ci    print(message, end="\n\n")
347db96d56Sopenharmony_ci
357db96d56Sopenharmony_ci
367db96d56Sopenharmony_cidef verify_wheel(package_name: str) -> bool:
377db96d56Sopenharmony_ci    # Find the package on disk
387db96d56Sopenharmony_ci    package_path = next(WHEEL_DIR.glob(f"{package_name}*.whl"), None)
397db96d56Sopenharmony_ci    if not package_path:
407db96d56Sopenharmony_ci        print_error("", f"Could not find a {package_name} wheel on disk.")
417db96d56Sopenharmony_ci        return False
427db96d56Sopenharmony_ci
437db96d56Sopenharmony_ci    print(f"Verifying checksum for {package_path}.")
447db96d56Sopenharmony_ci
457db96d56Sopenharmony_ci    # Find the version of the package used by ensurepip
467db96d56Sopenharmony_ci    package_version_match = re.search(
477db96d56Sopenharmony_ci        f'_{package_name.upper()}_VERSION = "([^"]+)', ENSURE_PIP_INIT_PY_TEXT
487db96d56Sopenharmony_ci    )
497db96d56Sopenharmony_ci    if not package_version_match:
507db96d56Sopenharmony_ci        print_error(
517db96d56Sopenharmony_ci            package_path,
527db96d56Sopenharmony_ci            f"No {package_name} version found in Lib/ensurepip/__init__.py.",
537db96d56Sopenharmony_ci        )
547db96d56Sopenharmony_ci        return False
557db96d56Sopenharmony_ci    package_version = package_version_match[1]
567db96d56Sopenharmony_ci
577db96d56Sopenharmony_ci    # Get the SHA 256 digest from the Cheeseshop
587db96d56Sopenharmony_ci    try:
597db96d56Sopenharmony_ci        raw_text = urlopen(f"https://pypi.org/pypi/{package_name}/json").read()
607db96d56Sopenharmony_ci    except (OSError, ValueError):
617db96d56Sopenharmony_ci        print_error(package_path, f"Could not fetch JSON metadata for {package_name}.")
627db96d56Sopenharmony_ci        return False
637db96d56Sopenharmony_ci
647db96d56Sopenharmony_ci    release_files = json.loads(raw_text)["releases"][package_version]
657db96d56Sopenharmony_ci    for release_info in release_files:
667db96d56Sopenharmony_ci        if package_path.name != release_info["filename"]:
677db96d56Sopenharmony_ci            continue
687db96d56Sopenharmony_ci        expected_digest = release_info["digests"].get("sha256", "")
697db96d56Sopenharmony_ci        break
707db96d56Sopenharmony_ci    else:
717db96d56Sopenharmony_ci        print_error(package_path, f"No digest for {package_name} found from PyPI.")
727db96d56Sopenharmony_ci        return False
737db96d56Sopenharmony_ci
747db96d56Sopenharmony_ci    # Compute the SHA 256 digest of the wheel on disk
757db96d56Sopenharmony_ci    actual_digest = hashlib.sha256(package_path.read_bytes()).hexdigest()
767db96d56Sopenharmony_ci
777db96d56Sopenharmony_ci    print(f"Expected digest: {expected_digest}")
787db96d56Sopenharmony_ci    print(f"Actual digest:   {actual_digest}")
797db96d56Sopenharmony_ci
807db96d56Sopenharmony_ci    if actual_digest != expected_digest:
817db96d56Sopenharmony_ci        print_error(
827db96d56Sopenharmony_ci            package_path, f"Failed to verify the checksum of the {package_name} wheel."
837db96d56Sopenharmony_ci        )
847db96d56Sopenharmony_ci        return False
857db96d56Sopenharmony_ci
867db96d56Sopenharmony_ci    print_notice(
877db96d56Sopenharmony_ci        package_path,
887db96d56Sopenharmony_ci        f"Successfully verified the checksum of the {package_name} wheel.",
897db96d56Sopenharmony_ci    )
907db96d56Sopenharmony_ci    return True
917db96d56Sopenharmony_ci
927db96d56Sopenharmony_ci
937db96d56Sopenharmony_ciif __name__ == "__main__":
947db96d56Sopenharmony_ci    exit_status = 0
957db96d56Sopenharmony_ci    for package_name in PACKAGE_NAMES:
967db96d56Sopenharmony_ci        if not verify_wheel(package_name):
977db96d56Sopenharmony_ci            exit_status = 1
987db96d56Sopenharmony_ci    raise SystemExit(exit_status)
99