|
1 | 1 | # Copyright (c) Microsoft Corporation. All rights reserved. |
2 | 2 | # Licensed under the MIT License. |
3 | 3 |
|
| 4 | +# This file is now a standalone Python script that |
| 5 | +# updates the vendored get-pip.py and cgmanifest.json |
| 6 | +# to the latest pip release. |
| 7 | + |
| 8 | +import argparse |
4 | 9 | import json |
5 | 10 | import pathlib |
6 | | -import urllib.request as url_lib |
7 | | - |
8 | | -from packaging.version import parse as version_parser |
| 11 | +import urllib.request |
| 12 | +from typing import Optional |
9 | 13 |
|
10 | 14 | EXTENSION_ROOT = pathlib.Path(__file__).parent.parent |
11 | | -GET_PIP_DEST = EXTENSION_ROOT / "python_files" |
12 | | -PIP_PACKAGE = "pip" |
13 | | -PIP_VERSION = "latest" # Can be "latest", or specific version "23.1.2" |
14 | | - |
15 | | - |
16 | | -def _get_package_data(): |
17 | | - json_uri = f"https://pypi.org/pypi/{PIP_PACKAGE}/json" |
18 | | - # Response format: https://warehouse.readthedocs.io/api-reference/json/#project |
19 | | - # Release metadata format: https://github.com/pypa/interoperability-peps/blob/master/pep-0426-core-metadata.rst |
20 | | - with url_lib.urlopen(json_uri) as response: |
21 | | - return json.loads(response.read()) |
22 | | - |
23 | | - |
24 | | -def _download_and_save(root, version): |
25 | | - root = pathlib.Path.cwd() if root is None or root == "." else pathlib.Path(root) |
26 | | - url = f"https://raw.githubusercontent.com/pypa/get-pip/{version}/public/get-pip.py" |
27 | | - print(url) |
28 | | - with url_lib.urlopen(url) as response: |
29 | | - data = response.read() |
30 | | - get_pip_file = root / "get-pip.py" |
31 | | - get_pip_file.write_bytes(data) |
32 | | - |
33 | | - |
34 | | -def main(root): |
35 | | - data = _get_package_data() |
36 | | - |
37 | | - if PIP_VERSION == "latest": |
38 | | - # Pick latest 5 versions to try and get-pip |
39 | | - sorted_versions = sorted(data["releases"].keys(), key=version_parser, reverse=True)[:5] |
40 | | - downloaded = False |
41 | | - while sorted_versions: |
42 | | - use_version = sorted_versions.pop(0) |
43 | | - try: |
44 | | - print(f"Trying version: get-pip == {use_version}") |
45 | | - _download_and_save(root, use_version) |
46 | | - downloaded = True |
47 | | - break |
48 | | - except Exception as e: |
49 | | - print(f"Failed to download get-pip == {use_version}: {e}") |
50 | | - print(f"NExt attempt(s) with versions: {sorted_versions}") |
51 | | - if not downloaded: |
52 | | - raise Exception("Failed to download get-pip.py") |
53 | | - else: |
54 | | - use_version = PIP_VERSION |
55 | | - _download_and_save(root, use_version) |
| 15 | +GET_PIP_DEST = EXTENSION_ROOT / "python_files" / "get-pip.py" |
| 16 | +CGMANIFEST_PATH = EXTENSION_ROOT / "cgmanifest.json" |
| 17 | +PIP_METADATA_URL = "https://pypi.org/pypi/pip/json" |
| 18 | +GET_PIP_URL = "https://raw.githubusercontent.com/pypa/get-pip/{version}/public/get-pip.py" |
| 19 | + |
| 20 | + |
| 21 | +def _get_latest_version() -> str: |
| 22 | + with urllib.request.urlopen(PIP_METADATA_URL) as response: |
| 23 | + metadata = json.load(response) |
| 24 | + |
| 25 | + version = metadata.get("info", {}).get("version") |
| 26 | + if not isinstance(version, str) or not version: |
| 27 | + raise ValueError(f"PyPI metadata from {PIP_METADATA_URL} did not contain a version") |
| 28 | + return version |
| 29 | + |
| 30 | + |
| 31 | +def _download_get_pip(version: str) -> bytes: |
| 32 | + url = GET_PIP_URL.format(version=version) |
| 33 | + print(f"Downloading {url}") |
| 34 | + with urllib.request.urlopen(url) as response: |
| 35 | + get_pip = response.read() |
| 36 | + |
| 37 | + expected_version = f"pip (version {version})".encode() |
| 38 | + if expected_version not in get_pip[:1024]: |
| 39 | + raise ValueError(f"Downloaded get-pip.py did not contain pip {version}") |
| 40 | + return get_pip |
| 41 | + |
| 42 | + |
| 43 | +def _update_cgmanifest(version: str) -> str: |
| 44 | + manifest = json.loads(CGMANIFEST_PATH.read_text(encoding="utf-8")) |
| 45 | + for registration in manifest["Registrations"]: |
| 46 | + component = registration.get("Component", {}).get("Other", {}) |
| 47 | + if component.get("Name") == "get-pip": |
| 48 | + component["Version"] = version |
| 49 | + return f"{json.dumps(manifest, indent=4)}\n" |
| 50 | + |
| 51 | + raise ValueError(f"get-pip registration was not found in {CGMANIFEST_PATH}") |
| 52 | + |
| 53 | + |
| 54 | +def refresh_get_pip(version: Optional[str] = None) -> None: |
| 55 | + version = version or _get_latest_version() |
| 56 | + get_pip = _download_get_pip(version) |
| 57 | + cgmanifest = _update_cgmanifest(version) |
| 58 | + |
| 59 | + GET_PIP_DEST.write_bytes(get_pip) |
| 60 | + CGMANIFEST_PATH.write_text(cgmanifest, encoding="utf-8") |
| 61 | + print(f"Updated {GET_PIP_DEST} and {CGMANIFEST_PATH} to get-pip {version}") |
| 62 | + |
| 63 | + |
| 64 | +def main() -> None: |
| 65 | + parser = argparse.ArgumentParser(description="Refresh the vendored get-pip.py") |
| 66 | + parser.add_argument( |
| 67 | + "--version", |
| 68 | + help="pip release to vendor; defaults to the latest release on PyPI", |
| 69 | + ) |
| 70 | + args = parser.parse_args() |
| 71 | + refresh_get_pip(args.version) |
56 | 72 |
|
57 | 73 |
|
58 | 74 | if __name__ == "__main__": |
59 | | - main(GET_PIP_DEST) |
| 75 | + main() |
0 commit comments