mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 19:08:04 +00:00

Closes https://github.com/astral-sh/uv/issues/3857 Instead of using custom `Arch`, `Os`, and `Libc` types I just use `target-lexicon`'s which enumerate way more variants and implement display and parsing. We use a wrapper type to represent a couple special cases to support the "x86" alias for "i686" and "macos" for "darwin". Alternatively we could try to use our `platform-tags` types but those capture more information (like operating system versions) that we don't have for downloads. As discussed in https://github.com/astral-sh/uv/pull/4160, this is not sufficient for proper libc detection but that work is larger and will be handled separately.
128 lines
3.1 KiB
Python
Executable file
128 lines
3.1 KiB
Python
Executable file
#!/usr/bin/env python3.12
|
|
"""
|
|
Generate static Rust code from Python version download metadata.
|
|
|
|
Generates the `downloads.inc` file from the `downloads.inc.mustache` template.
|
|
|
|
Usage:
|
|
|
|
python template-download-metadata.py
|
|
"""
|
|
|
|
import sys
|
|
import logging
|
|
import argparse
|
|
import json
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
CRATE_ROOT = Path(__file__).parent
|
|
WORKSPACE_ROOT = CRATE_ROOT.parent.parent
|
|
VERSION_METADATA = CRATE_ROOT / "download-metadata.json"
|
|
TEMPLATE = CRATE_ROOT / "src" / "downloads.inc.mustache"
|
|
TARGET = TEMPLATE.with_suffix("")
|
|
|
|
|
|
try:
|
|
import chevron_blue
|
|
except ImportError:
|
|
print(
|
|
"missing requirement `chevron-blue`",
|
|
file=sys.stderr,
|
|
)
|
|
exit(1)
|
|
|
|
|
|
def prepare_name(name: str) -> str:
|
|
match name:
|
|
case "cpython":
|
|
return "CPython"
|
|
case _:
|
|
raise ValueError(f"Unknown implementation name: {name}")
|
|
|
|
|
|
def prepare_libc(libc: str) -> str | None:
|
|
if libc == "none":
|
|
return None
|
|
else:
|
|
return libc.title()
|
|
|
|
|
|
def prepare_arch(arch: str) -> str:
|
|
match arch:
|
|
# Special constructors
|
|
case "i686":
|
|
return "X86_32(target_lexicon::X86_32Architecture::I686)"
|
|
case "aarch64":
|
|
return "Aarch64(target_lexicon::Aarch64Architecture::Aarch64)"
|
|
case "armv7":
|
|
return "Arm(target_lexicon::ArmArchitecture::Armv7)"
|
|
case _:
|
|
return arch.capitalize()
|
|
|
|
|
|
def prepare_value(value: dict) -> dict:
|
|
value["os"] = value["os"].title()
|
|
value["arch"] = prepare_arch(value["arch"])
|
|
value["name"] = prepare_name(value["name"])
|
|
value["libc"] = prepare_libc(value["libc"])
|
|
return value
|
|
|
|
|
|
def main():
|
|
debug = logging.getLogger().getEffectiveLevel() <= logging.DEBUG
|
|
|
|
data = {}
|
|
data["generated_with"] = Path(__file__).relative_to(WORKSPACE_ROOT)
|
|
data["generated_from"] = TEMPLATE.relative_to(WORKSPACE_ROOT)
|
|
data["versions"] = [
|
|
{"key": key, "value": prepare_value(value)}
|
|
for key, value in json.loads(VERSION_METADATA.read_text()).items()
|
|
]
|
|
|
|
# Render the template
|
|
logging.info(f"Rendering `{TEMPLATE.name}`...")
|
|
output = chevron_blue.render(
|
|
template=TEMPLATE.read_text(), data=data, no_escape=True, warn=debug
|
|
)
|
|
|
|
# Update the file
|
|
logging.info(f"Updating `{TARGET}`...")
|
|
TARGET.write_text(output)
|
|
subprocess.check_call(
|
|
["rustfmt", str(TARGET)],
|
|
stderr=subprocess.STDOUT,
|
|
stdout=sys.stderr if debug else subprocess.DEVNULL,
|
|
)
|
|
|
|
logging.info("Done!")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(
|
|
description="Generates Rust code for Python version metadata.",
|
|
)
|
|
parser.add_argument(
|
|
"-v",
|
|
"--verbose",
|
|
action="store_true",
|
|
help="Enable debug logging",
|
|
)
|
|
parser.add_argument(
|
|
"-q",
|
|
"--quiet",
|
|
action="store_true",
|
|
help="Disable logging",
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
if args.quiet:
|
|
log_level = logging.CRITICAL
|
|
elif args.verbose:
|
|
log_level = logging.DEBUG
|
|
else:
|
|
log_level = logging.INFO
|
|
|
|
logging.basicConfig(level=log_level, format="%(message)s")
|
|
|
|
main()
|