From ba0f8b0daf269c1050a2fe1a72c5a7c0620895ac Mon Sep 17 00:00:00 2001 From: Ivan Pozdeev Date: Sun, 29 Jun 2025 20:12:44 +0300 Subject: [PATCH 01/15] Add a script to generate CPython installation scripts --- plugins/python-build/scripts/add_cpython.py | 416 ++++++++++++++++++++ 1 file changed, 416 insertions(+) create mode 100644 plugins/python-build/scripts/add_cpython.py diff --git a/plugins/python-build/scripts/add_cpython.py b/plugins/python-build/scripts/add_cpython.py new file mode 100644 index 00000000..45d8d7cb --- /dev/null +++ b/plugins/python-build/scripts/add_cpython.py @@ -0,0 +1,416 @@ +#!/usr/bin/env python3 +"""Script to add CPython releases. + +Checks the CPython download archives for new versions, +then writes a build script for any which do not exist locally, +saving it to plugins/python-build/share/python-build. + +""" +import logging +import re +import string +import sys +import textwrap +from argparse import ArgumentParser +from collections import defaultdict +from dataclasses import dataclass +from enum import Enum +from functools import total_ordering +from pathlib import Path +from typing import NamedTuple, List, Optional, DefaultDict, Dict + +import requests_html + +logger = logging.getLogger(__name__) + +REPO = "https://www.python.org/ftp/python/" + +install_script_fmt = """ +case "$(anaconda_architecture 2>/dev/null || true)" in +{install_lines} +* ) + {{ echo + colorize 1 "ERROR" + echo ": The binary distribution of {tflavor} is not available for $(anaconda_architecture 2>/dev/null || true)." + echo + }} >&2 + exit 1 + ;; +esac +""".lstrip() + +install_line_fmt = """ +"{os}-{arch}" ) + install_script "{tflavor}{suffix}-{version_py_version}{version_str}-{os}-{arch}" "{repo}/{tflavor}{suffix}-{version_py_version}{version_str}-{os}-{arch}.sh#{md5}" "{flavor}" verify_{py_version} + ;; +""".strip() + +here = Path(__file__).resolve() +out_dir: Path = here.parent.parent / "share" / "python-build" + + +class StrEnum(str, Enum): + """Enum subclass whose members are also instances of str + and directly comparable to strings. str type is forced at declaration. + + Adapted from https://github.com/kissgyorgy/enum34-custom/blob/dbc89596761c970398701d26c6a5bbcfcf70f548/enum_custom.py#L100 + (MIT license) + """ + + def __new__(cls, *args): + for arg in args: + if not isinstance(arg, str): + raise TypeError("Not text %s:" % arg) + + return super(StrEnum, cls).__new__(cls, *args) + + def __str__(self): + return str(self.value) + + +class SupportedOS(StrEnum): + LINUX = "Linux" + MACOSX = "MacOSX" + + +class SupportedArch(StrEnum): + AARCH64 = "aarch64" + ARM64 = "arm64" + PPC64LE = "ppc64le" + S390X = "s390x" + X86_64 = "x86_64" + X86 = "x86" + + +class Flavor(StrEnum): + ANACONDA = "anaconda" + MINICONDA = "miniconda" + + +class TFlavor(StrEnum): + ANACONDA = "Anaconda" + MINICONDA = "Miniconda" + + +class Suffix(StrEnum): + TWO = "2" + THREE = "3" + NONE = "" + + +PyVersion = None +class PyVersionMeta(type): + def __getattr__(self, name): + """Generate PyVersion.PYXXX on demand to future-proof it""" + if PyVersion is not None: + return PyVersion(name.lower()) + return super(PyVersionMeta,self).__getattr__(self, name) + + +@dataclass(frozen=True) +class PyVersion(metaclass=PyVersionMeta): + major: str + minor: str + + def __init__(self, value): + (major, minor) = re.match(r"py(\d)(\d+)", value).groups() + object.__setattr__(self, "major", major) + object.__setattr__(self, "minor", minor) + + @property + def value(self): + return f"py{self.major}{self.minor}" + + def version(self): + return f"{self.major}.{self.minor}" + + def version_info(self): + return (self.major, self.minor) + + def __str__(self): + return self.value + + +@total_ordering +class VersionStr(str): + def info(self): + return tuple(int(n) for n in self.replace("-", ".").split(".")) + + def __eq__(self, other): + return str(self) == str(other) + + def __lt__(self, other): + if isinstance(other, VersionStr): + return self.info() < other.info() + raise ValueError("VersionStr can only be compared to other VersionStr") + + @classmethod + def from_info(cls, version_info): + return VersionStr(".".join(str(n) for n in version_info)) + + def __hash__(self): + return hash(str(self)) + + +class CondaVersion(NamedTuple): + flavor: Flavor + suffix: Suffix + version_str: VersionStr + py_version: Optional[PyVersion] + + @classmethod + def from_str(cls, s): + """ + Convert a string of the form "miniconda_n-ver" or "miniconda_n-py_ver-ver" to a :class:`CondaVersion` object. + """ + miniconda_n, _, remainder = s.partition("-") + suffix = miniconda_n[-1] + if suffix in string.digits: + flavor = miniconda_n[:-1] + else: + flavor = miniconda_n + suffix = "" + + components = remainder.split("-") + if flavor == Flavor.MINICONDA and len(components) >= 2: + py_ver, *ver_parts = components + py_ver = PyVersion(f"py{py_ver.replace('.', '')}") + ver = "-".join(ver_parts) + else: + ver = "-".join(components) + py_ver = None + + return CondaVersion(Flavor(flavor), Suffix(suffix), VersionStr(ver), py_ver) + + def to_filename(self): + if self.py_version: + return f"{self.flavor}{self.suffix}-{self.py_version.version()}-{self.version_str}" + else: + return f"{self.flavor}{self.suffix}-{self.version_str}" + + def default_py_version(self): + """ + :class:`PyVersion` of Python used with this Miniconda version + """ + if self.py_version: + return self.py_version + elif self.suffix == Suffix.TWO: + return PyVersion.PY27 + + v = self.version_str.info() + if self.flavor == "miniconda": + # https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-python.html + if v < (4, 7): + return PyVersion.PY36 + if v < (4, 8): + return PyVersion.PY37 + else: + # since 4.8, Miniconda specifies versions explicitly in the file name + raise ValueError("Miniconda 4.8+ is supposed to specify a Python version explicitly") + if self.flavor == "anaconda": + # https://docs.anaconda.com/free/anaconda/reference/release-notes/ + if v >= (2024,6): + return PyVersion.PY312 + if v >= (2023,7): + return PyVersion.PY311 + if v >= (2023,3): + return PyVersion.PY310 + if v >= (2021,11): + return PyVersion.PY39 + if v >= (2020,7): + return PyVersion.PY38 + if v >= (2020,2): + return PyVersion.PY37 + if v >= (5,3,0): + return PyVersion.PY37 + return PyVersion.PY36 + + raise ValueError(self.flavor) + + +class CondaSpec(NamedTuple): + tflavor: TFlavor + version: CondaVersion + os: SupportedOS + arch: SupportedArch + md5: str + repo: str + py_version: Optional[PyVersion] = None + + @classmethod + def from_filestem(cls, stem, md5, repo, py_version=None): + # The `*vers` captures the new trailing `-1` in some file names (a build number?) + # so they can be processed properly. + miniconda_n, *vers, os, arch = stem.split("-") + ver = "-".join(vers) + suffix = miniconda_n[-1] + if suffix in string.digits: + tflavor = miniconda_n[:-1] + else: + tflavor = miniconda_n + suffix = "" + flavor = tflavor.lower() + + if ver.startswith("py"): + py_ver, ver = ver.split("_", maxsplit=1) + py_ver = PyVersion(py_ver) + else: + py_ver = None + spec = CondaSpec( + TFlavor(tflavor), + CondaVersion(Flavor(flavor), Suffix(suffix), VersionStr(ver), py_ver), + SupportedOS(os), + SupportedArch(arch), + md5, + repo, + py_ver + ) + if py_version is None and py_ver is None and ver != "latest": + spec = spec.with_py_version(spec.version.default_py_version()) + return spec + + def to_install_lines(self): + """ + Installation command for this version of Miniconda for use in a Pyenv installation script + """ + return install_line_fmt.format( + tflavor=self.tflavor, + flavor=self.version.flavor, + repo=self.repo, + suffix=self.version.suffix, + version_str=self.version.version_str, + version_py_version=f"{self.version.py_version}_" if self.version.py_version else "", + os=self.os, + arch=self.arch, + md5=self.md5, + py_version=self.py_version, + ) + + def with_py_version(self, py_version: PyVersion): + return CondaSpec(*self[:-1], py_version=py_version) + + +def make_script(specs: List[CondaSpec]): + install_lines = [s.to_install_lines() for s in specs] + return install_script_fmt.format( + install_lines="\n".join(install_lines), + tflavor=specs[0].tflavor, + ) + + +def get_existing_condas(name): + """ + Enumerate existing Miniconda installation scripts in share/python-build/ except rolling releases. + + :returns: A generator of :class:`CondaVersion` objects. + """ + logger.info("Getting known %(name)s versions",locals()) + for p in out_dir.iterdir(): + entry_name = p.name + if not p.is_file() or not entry_name.startswith(name): + continue + try: + v = CondaVersion.from_str(entry_name) + if v.version_str != "latest": + logger.debug("Found existing %(name)s version %(v)s", locals()) + yield v + except ValueError as e: + logger.error("Unable to parse existing version %s: %s", entry_name, e) + + +def get_available_condas(name, repo): + """ + Fetch remote miniconda versions. + + :returns: A generator of :class:`CondaSpec` objects for each release available for download + except rolling releases. + """ + logger.info("Fetching remote %(name)s versions",locals()) + session = requests_html.HTMLSession() + response = session.get(repo) + page: requests_html.HTML = response.html + table = page.find("table", first=True) + rows = table.find("tr")[1:] + for row in rows: + f, size, date, md5 = row.find("td") + fname = f.text + md5 = md5.text + + if not fname.endswith(".sh"): + continue + stem = fname[:-3] + + try: + s = CondaSpec.from_filestem(stem, md5, repo) + if s.version.version_str != "latest": + logger.debug("Found remote %(name)s version %(s)s", locals()) + yield s + except ValueError: + pass + + +def key_fn(spec: CondaSpec): + return ( + spec.tflavor, + spec.version.version_str.info(), + spec.version.suffix.value, + spec.os.value, + spec.arch.value, + ) + + +if __name__ == "__main__": + parser = ArgumentParser(description=__doc__) + parser.add_argument( + "-d", "--dry-run", action="store_true", + help="Do not write scripts, just report them to stdout", + ) + parser.add_argument( + "-v", "--verbose", action="store_true", default=0, + help="Increase verbosity of logging", + ) + parsed = parser.parse_args() + + logging.basicConfig(level=logging.DEBUG if parsed.verbose else logging.INFO) + + existing_versions = set() + available_specs = set() + for name,repo in ("miniconda",MINICONDA_REPO),("anaconda",ANACONDA_REPO): + existing_versions |= set(get_existing_condas(name)) + available_specs |= set(get_available_condas(name, repo)) + + # version triple to triple-ified spec to raw spec + to_add: DefaultDict[ + CondaVersion, Dict[CondaSpec, CondaSpec] + ] = defaultdict(dict) + + logger.info("Checking for new versions") + for s in sorted(available_specs, key=key_fn): + key = s.version + vv = key.version_str.info() + + reason = None + if key in existing_versions: + reason = "already exists" + elif key.version_str.info() <= (4, 3, 30): + reason = "too old" + elif len(key.version_str.info()) >= 4 and "-" not in key.version_str: + reason = "ignoring hotfix releases" + + if reason: + logger.debug("Ignoring version %(s)s (%(reason)s)", locals()) + continue + + to_add[key][s] = s + + logger.info("Writing %s scripts", len(to_add)) + for ver, d in to_add.items(): + specs = list(d.values()) + fpath = out_dir / ver.to_filename() + script_str = make_script(specs) + logger.info("Writing script for %s", ver) + if parsed.dry_run: + print(f"Would write spec to {fpath}:\n" + textwrap.indent(script_str, " ")) + else: + with open(fpath, "w") as f: + f.write(script_str) From 770920b0828127188591259d9a3a2b60315f3359 Mon Sep 17 00:00:00 2001 From: Ivan Pozdeev Date: Sun, 6 Jul 2025 23:47:44 +0300 Subject: [PATCH 02/15] WIP --- plugins/python-build/scripts/add_cpython.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/plugins/python-build/scripts/add_cpython.py b/plugins/python-build/scripts/add_cpython.py index 45d8d7cb..aadf86ec 100644 --- a/plugins/python-build/scripts/add_cpython.py +++ b/plugins/python-build/scripts/add_cpython.py @@ -25,26 +25,6 @@ logger = logging.getLogger(__name__) REPO = "https://www.python.org/ftp/python/" -install_script_fmt = """ -case "$(anaconda_architecture 2>/dev/null || true)" in -{install_lines} -* ) - {{ echo - colorize 1 "ERROR" - echo ": The binary distribution of {tflavor} is not available for $(anaconda_architecture 2>/dev/null || true)." - echo - }} >&2 - exit 1 - ;; -esac -""".lstrip() - -install_line_fmt = """ -"{os}-{arch}" ) - install_script "{tflavor}{suffix}-{version_py_version}{version_str}-{os}-{arch}" "{repo}/{tflavor}{suffix}-{version_py_version}{version_str}-{os}-{arch}.sh#{md5}" "{flavor}" verify_{py_version} - ;; -""".strip() - here = Path(__file__).resolve() out_dir: Path = here.parent.parent / "share" / "python-build" From a358c7fd101353bddd964eded4aac0bdd5232858 Mon Sep 17 00:00:00 2001 From: Ivan Pozdeev Date: Sun, 3 Aug 2025 09:15:24 +0300 Subject: [PATCH 03/15] Add anaconda3-2025.06-0, anaconda3-2025.06-1, miniconda3-25.3.1-1, miniconda3-25.5.1-0, miniconda3-25.5.1-1 --- .../share/python-build/anaconda3-2025.06-0 | 22 +++++++++++++++++++ .../share/python-build/anaconda3-2025.06-1 | 22 +++++++++++++++++++ .../python-build/miniconda3-3.10-25.3.1-1 | 22 +++++++++++++++++++ .../python-build/miniconda3-3.10-25.5.1-0 | 22 +++++++++++++++++++ .../python-build/miniconda3-3.10-25.5.1-1 | 22 +++++++++++++++++++ .../python-build/miniconda3-3.11-25.3.1-1 | 22 +++++++++++++++++++ .../python-build/miniconda3-3.11-25.5.1-0 | 22 +++++++++++++++++++ .../python-build/miniconda3-3.11-25.5.1-1 | 22 +++++++++++++++++++ .../python-build/miniconda3-3.12-25.3.1-1 | 22 +++++++++++++++++++ .../python-build/miniconda3-3.12-25.5.1-0 | 22 +++++++++++++++++++ .../python-build/miniconda3-3.12-25.5.1-1 | 22 +++++++++++++++++++ .../python-build/miniconda3-3.13-25.3.1-1 | 22 +++++++++++++++++++ .../python-build/miniconda3-3.13-25.5.1-0 | 22 +++++++++++++++++++ .../python-build/miniconda3-3.13-25.5.1-1 | 22 +++++++++++++++++++ .../python-build/miniconda3-3.9-25.3.1-1 | 22 +++++++++++++++++++ .../python-build/miniconda3-3.9-25.5.1-0 | 22 +++++++++++++++++++ .../python-build/miniconda3-3.9-25.5.1-1 | 22 +++++++++++++++++++ 17 files changed, 374 insertions(+) create mode 100644 plugins/python-build/share/python-build/anaconda3-2025.06-0 create mode 100644 plugins/python-build/share/python-build/anaconda3-2025.06-1 create mode 100644 plugins/python-build/share/python-build/miniconda3-3.10-25.3.1-1 create mode 100644 plugins/python-build/share/python-build/miniconda3-3.10-25.5.1-0 create mode 100644 plugins/python-build/share/python-build/miniconda3-3.10-25.5.1-1 create mode 100644 plugins/python-build/share/python-build/miniconda3-3.11-25.3.1-1 create mode 100644 plugins/python-build/share/python-build/miniconda3-3.11-25.5.1-0 create mode 100644 plugins/python-build/share/python-build/miniconda3-3.11-25.5.1-1 create mode 100644 plugins/python-build/share/python-build/miniconda3-3.12-25.3.1-1 create mode 100644 plugins/python-build/share/python-build/miniconda3-3.12-25.5.1-0 create mode 100644 plugins/python-build/share/python-build/miniconda3-3.12-25.5.1-1 create mode 100644 plugins/python-build/share/python-build/miniconda3-3.13-25.3.1-1 create mode 100644 plugins/python-build/share/python-build/miniconda3-3.13-25.5.1-0 create mode 100644 plugins/python-build/share/python-build/miniconda3-3.13-25.5.1-1 create mode 100644 plugins/python-build/share/python-build/miniconda3-3.9-25.3.1-1 create mode 100644 plugins/python-build/share/python-build/miniconda3-3.9-25.5.1-0 create mode 100644 plugins/python-build/share/python-build/miniconda3-3.9-25.5.1-1 diff --git a/plugins/python-build/share/python-build/anaconda3-2025.06-0 b/plugins/python-build/share/python-build/anaconda3-2025.06-0 new file mode 100644 index 00000000..2ad64f94 --- /dev/null +++ b/plugins/python-build/share/python-build/anaconda3-2025.06-0 @@ -0,0 +1,22 @@ +case "$(anaconda_architecture 2>/dev/null || true)" in +"Linux-aarch64" ) + install_script "Anaconda3-2025.06-0-Linux-aarch64" "https://repo.anaconda.com/archive/Anaconda3-2025.06-0-Linux-aarch64.sh#e6177f844f6156f07978c85b1f18ca3b7724d5ab5dba44b88a7f6b2452ba6271" "anaconda" verify_py312 + ;; +"Linux-x86_64" ) + install_script "Anaconda3-2025.06-0-Linux-x86_64" "https://repo.anaconda.com/archive/Anaconda3-2025.06-0-Linux-x86_64.sh#08db13f6db5dd4f99b3a6db96c68d064d50ea23742013b70f588e3a27a5204da" "anaconda" verify_py312 + ;; +"MacOSX-arm64" ) + install_script "Anaconda3-2025.06-0-MacOSX-arm64" "https://repo.anaconda.com/archive/Anaconda3-2025.06-0-MacOSX-arm64.sh#195f234204e2f18803cea38bbebefcaac5a3d8d95e2e4ee106d1b87b23b9fc4a" "anaconda" verify_py312 + ;; +"MacOSX-x86_64" ) + install_script "Anaconda3-2025.06-0-MacOSX-x86_64" "https://repo.anaconda.com/archive/Anaconda3-2025.06-0-MacOSX-x86_64.sh#8625a155ff1d2848afa360e70357e14c25f0ac7ac21e4e4bf15015bc58b08d06" "anaconda" verify_py312 + ;; +* ) + { echo + colorize 1 "ERROR" + echo ": The binary distribution of Anaconda is not available for $(anaconda_architecture 2>/dev/null || true)." + echo + } >&2 + exit 1 + ;; +esac diff --git a/plugins/python-build/share/python-build/anaconda3-2025.06-1 b/plugins/python-build/share/python-build/anaconda3-2025.06-1 new file mode 100644 index 00000000..10f343c7 --- /dev/null +++ b/plugins/python-build/share/python-build/anaconda3-2025.06-1 @@ -0,0 +1,22 @@ +case "$(anaconda_architecture 2>/dev/null || true)" in +"Linux-aarch64" ) + install_script "Anaconda3-2025.06-1-Linux-aarch64" "https://repo.anaconda.com/archive/Anaconda3-2025.06-1-Linux-aarch64.sh#98dfb82732991f3f4d385a265da5fd2190d65ec31ceafa0b93f3c5edf4431a5b" "anaconda" verify_py312 + ;; +"Linux-x86_64" ) + install_script "Anaconda3-2025.06-1-Linux-x86_64" "https://repo.anaconda.com/archive/Anaconda3-2025.06-1-Linux-x86_64.sh#82976426a2c91fe1453281def386f9ebebd8fdb45dc6c970b54cfef4e9120857" "anaconda" verify_py312 + ;; +"MacOSX-arm64" ) + install_script "Anaconda3-2025.06-1-MacOSX-arm64" "https://repo.anaconda.com/archive/Anaconda3-2025.06-1-MacOSX-arm64.sh#f9366a024ac6f043dc224986c54f1b4e1226e6ccb6291d83bdadb489f159d847" "anaconda" verify_py312 + ;; +"MacOSX-x86_64" ) + install_script "Anaconda3-2025.06-1-MacOSX-x86_64" "https://repo.anaconda.com/archive/Anaconda3-2025.06-1-MacOSX-x86_64.sh#58139fe195337f3041259e3a611339ed3afa4d164cc9aa2a7e8e84c0673e3670" "anaconda" verify_py312 + ;; +* ) + { echo + colorize 1 "ERROR" + echo ": The binary distribution of Anaconda is not available for $(anaconda_architecture 2>/dev/null || true)." + echo + } >&2 + exit 1 + ;; +esac diff --git a/plugins/python-build/share/python-build/miniconda3-3.10-25.3.1-1 b/plugins/python-build/share/python-build/miniconda3-3.10-25.3.1-1 new file mode 100644 index 00000000..66d105e5 --- /dev/null +++ b/plugins/python-build/share/python-build/miniconda3-3.10-25.3.1-1 @@ -0,0 +1,22 @@ +case "$(anaconda_architecture 2>/dev/null || true)" in +"Linux-aarch64" ) + install_script "Miniconda3-py310_25.3.1-1-Linux-aarch64" "https://repo.anaconda.com/miniconda/Miniconda3-py310_25.3.1-1-Linux-aarch64.sh#7879e8e2c89d26d8c4776fbad72656afda697e67e2e70b9b8eef368b111d2253" "miniconda" verify_py310 + ;; +"Linux-x86_64" ) + install_script "Miniconda3-py310_25.3.1-1-Linux-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py310_25.3.1-1-Linux-x86_64.sh#fa277fc23e40625d4c12a00c0378b4bbb2baee96660796431ee76185fe1b5025" "miniconda" verify_py310 + ;; +"MacOSX-arm64" ) + install_script "Miniconda3-py310_25.3.1-1-MacOSX-arm64" "https://repo.anaconda.com/miniconda/Miniconda3-py310_25.3.1-1-MacOSX-arm64.sh#79036d813ac3035b8d833ea427df6ccd02d25798d7abb5e5b4423f6642088a33" "miniconda" verify_py310 + ;; +"MacOSX-x86_64" ) + install_script "Miniconda3-py310_25.3.1-1-MacOSX-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py310_25.3.1-1-MacOSX-x86_64.sh#adf482eea0ffa1f6ead1cb217ee0a2cc8c0732399782a609c4333dc96bf9d6a6" "miniconda" verify_py310 + ;; +* ) + { echo + colorize 1 "ERROR" + echo ": The binary distribution of Miniconda is not available for $(anaconda_architecture 2>/dev/null || true)." + echo + } >&2 + exit 1 + ;; +esac diff --git a/plugins/python-build/share/python-build/miniconda3-3.10-25.5.1-0 b/plugins/python-build/share/python-build/miniconda3-3.10-25.5.1-0 new file mode 100644 index 00000000..ca482d37 --- /dev/null +++ b/plugins/python-build/share/python-build/miniconda3-3.10-25.5.1-0 @@ -0,0 +1,22 @@ +case "$(anaconda_architecture 2>/dev/null || true)" in +"Linux-aarch64" ) + install_script "Miniconda3-py310_25.5.1-0-Linux-aarch64" "https://repo.anaconda.com/miniconda/Miniconda3-py310_25.5.1-0-Linux-aarch64.sh#6886af5e5db79dfb4429cde937b6f806d02b0e98dd11249c74917661fc7b1f4b" "miniconda" verify_py310 + ;; +"Linux-x86_64" ) + install_script "Miniconda3-py310_25.5.1-0-Linux-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py310_25.5.1-0-Linux-x86_64.sh#aa8cbd6aa7ef85d0528616d2dffce8bd0b51924e7ddd18734ede6c1c63c462d0" "miniconda" verify_py310 + ;; +"MacOSX-arm64" ) + install_script "Miniconda3-py310_25.5.1-0-MacOSX-arm64" "https://repo.anaconda.com/miniconda/Miniconda3-py310_25.5.1-0-MacOSX-arm64.sh#169baa18136f7ee46052ec4e1719401e1b99427bba3cf0507da0a6980b7e32a1" "miniconda" verify_py310 + ;; +"MacOSX-x86_64" ) + install_script "Miniconda3-py310_25.5.1-0-MacOSX-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py310_25.5.1-0-MacOSX-x86_64.sh#7d7aa6302fe696afa5b46b3c8f6eaf80fcf3c1355df0c3a8910514cf02bf02af" "miniconda" verify_py310 + ;; +* ) + { echo + colorize 1 "ERROR" + echo ": The binary distribution of Miniconda is not available for $(anaconda_architecture 2>/dev/null || true)." + echo + } >&2 + exit 1 + ;; +esac diff --git a/plugins/python-build/share/python-build/miniconda3-3.10-25.5.1-1 b/plugins/python-build/share/python-build/miniconda3-3.10-25.5.1-1 new file mode 100644 index 00000000..ce3d7e7b --- /dev/null +++ b/plugins/python-build/share/python-build/miniconda3-3.10-25.5.1-1 @@ -0,0 +1,22 @@ +case "$(anaconda_architecture 2>/dev/null || true)" in +"Linux-aarch64" ) + install_script "Miniconda3-py310_25.5.1-1-Linux-aarch64" "https://repo.anaconda.com/miniconda/Miniconda3-py310_25.5.1-1-Linux-aarch64.sh#630f724622a3f45f66b3b08f708bc44e2cb595ad147f68750271c6352cb4d734" "miniconda" verify_py310 + ;; +"Linux-x86_64" ) + install_script "Miniconda3-py310_25.5.1-1-Linux-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py310_25.5.1-1-Linux-x86_64.sh#84bd8f60fea2e09f1fa72f258132337aff7a20ec1e12dea9d1eff96830cf3d8c" "miniconda" verify_py310 + ;; +"MacOSX-arm64" ) + install_script "Miniconda3-py310_25.5.1-1-MacOSX-arm64" "https://repo.anaconda.com/miniconda/Miniconda3-py310_25.5.1-1-MacOSX-arm64.sh#4e8309e9c0ed87fe5f5ffc77ae71201f51939f5c163d00f6867154f1b9cee41a" "miniconda" verify_py310 + ;; +"MacOSX-x86_64" ) + install_script "Miniconda3-py310_25.5.1-1-MacOSX-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py310_25.5.1-1-MacOSX-x86_64.sh#564e63a326cf73b2ab9a4a8be5bdf51996388f10a8eb4170071cb74f836d1e4e" "miniconda" verify_py310 + ;; +* ) + { echo + colorize 1 "ERROR" + echo ": The binary distribution of Miniconda is not available for $(anaconda_architecture 2>/dev/null || true)." + echo + } >&2 + exit 1 + ;; +esac diff --git a/plugins/python-build/share/python-build/miniconda3-3.11-25.3.1-1 b/plugins/python-build/share/python-build/miniconda3-3.11-25.3.1-1 new file mode 100644 index 00000000..a1b88cfb --- /dev/null +++ b/plugins/python-build/share/python-build/miniconda3-3.11-25.3.1-1 @@ -0,0 +1,22 @@ +case "$(anaconda_architecture 2>/dev/null || true)" in +"Linux-aarch64" ) + install_script "Miniconda3-py311_25.3.1-1-Linux-aarch64" "https://repo.anaconda.com/miniconda/Miniconda3-py311_25.3.1-1-Linux-aarch64.sh#8d2470bdbee7e93d9b4972a3272b63673ba62113282378f98f23b423cc61ef59" "miniconda" verify_py311 + ;; +"Linux-x86_64" ) + install_script "Miniconda3-py311_25.3.1-1-Linux-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py311_25.3.1-1-Linux-x86_64.sh#6239d42dacc169e290ac37516b2eb6751763edbd977b237e9688884c7b4ea706" "miniconda" verify_py311 + ;; +"MacOSX-arm64" ) + install_script "Miniconda3-py311_25.3.1-1-MacOSX-arm64" "https://repo.anaconda.com/miniconda/Miniconda3-py311_25.3.1-1-MacOSX-arm64.sh#dc08b8f6fb2acb4f1c8b076a59226e29acf0a9dbeb7b5f643a8c19eb0d14a294" "miniconda" verify_py311 + ;; +"MacOSX-x86_64" ) + install_script "Miniconda3-py311_25.3.1-1-MacOSX-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py311_25.3.1-1-MacOSX-x86_64.sh#2494921f81db2c9b72e26e4aced35d5c58ea2a61ce040efd77ed2957ba4d8d3f" "miniconda" verify_py311 + ;; +* ) + { echo + colorize 1 "ERROR" + echo ": The binary distribution of Miniconda is not available for $(anaconda_architecture 2>/dev/null || true)." + echo + } >&2 + exit 1 + ;; +esac diff --git a/plugins/python-build/share/python-build/miniconda3-3.11-25.5.1-0 b/plugins/python-build/share/python-build/miniconda3-3.11-25.5.1-0 new file mode 100644 index 00000000..37ed9ef9 --- /dev/null +++ b/plugins/python-build/share/python-build/miniconda3-3.11-25.5.1-0 @@ -0,0 +1,22 @@ +case "$(anaconda_architecture 2>/dev/null || true)" in +"Linux-aarch64" ) + install_script "Miniconda3-py311_25.5.1-0-Linux-aarch64" "https://repo.anaconda.com/miniconda/Miniconda3-py311_25.5.1-0-Linux-aarch64.sh#3862c5ecba0949c3bdbeeb7d072664215924fbccb25f0d3f33782362c5405e22" "miniconda" verify_py311 + ;; +"Linux-x86_64" ) + install_script "Miniconda3-py311_25.5.1-0-Linux-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py311_25.5.1-0-Linux-x86_64.sh#a921abd74e16f5dee8a4d79b124635fac9b939c465ba2e942ea61b3fcd1451d8" "miniconda" verify_py311 + ;; +"MacOSX-arm64" ) + install_script "Miniconda3-py311_25.5.1-0-MacOSX-arm64" "https://repo.anaconda.com/miniconda/Miniconda3-py311_25.5.1-0-MacOSX-arm64.sh#23a6d150c1fc7426147f9458c20ec73fa97aa369e19152997b7206f23b983d66" "miniconda" verify_py311 + ;; +"MacOSX-x86_64" ) + install_script "Miniconda3-py311_25.5.1-0-MacOSX-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py311_25.5.1-0-MacOSX-x86_64.sh#cc23ad95d62e1d3c5754c2ead09f2c56bfe1112b38b69a4edc140a0a2c8d5300" "miniconda" verify_py311 + ;; +* ) + { echo + colorize 1 "ERROR" + echo ": The binary distribution of Miniconda is not available for $(anaconda_architecture 2>/dev/null || true)." + echo + } >&2 + exit 1 + ;; +esac diff --git a/plugins/python-build/share/python-build/miniconda3-3.11-25.5.1-1 b/plugins/python-build/share/python-build/miniconda3-3.11-25.5.1-1 new file mode 100644 index 00000000..ca4940e9 --- /dev/null +++ b/plugins/python-build/share/python-build/miniconda3-3.11-25.5.1-1 @@ -0,0 +1,22 @@ +case "$(anaconda_architecture 2>/dev/null || true)" in +"Linux-aarch64" ) + install_script "Miniconda3-py311_25.5.1-1-Linux-aarch64" "https://repo.anaconda.com/miniconda/Miniconda3-py311_25.5.1-1-Linux-aarch64.sh#39a6d853df41b74aabe37dd666732f7556f81584af789693d7e3005d7a04c64f" "miniconda" verify_py311 + ;; +"Linux-x86_64" ) + install_script "Miniconda3-py311_25.5.1-1-Linux-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py311_25.5.1-1-Linux-x86_64.sh#94b4cb34b583ce549fb17e3b5e93b6e4adfd4605cf0aa4d5f00bd06f620182e2" "miniconda" verify_py311 + ;; +"MacOSX-arm64" ) + install_script "Miniconda3-py311_25.5.1-1-MacOSX-arm64" "https://repo.anaconda.com/miniconda/Miniconda3-py311_25.5.1-1-MacOSX-arm64.sh#73b8ef14c88fa2bf9b74c70f7a888a0a19941a67929e4879850a67c948d4ac58" "miniconda" verify_py311 + ;; +"MacOSX-x86_64" ) + install_script "Miniconda3-py311_25.5.1-1-MacOSX-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py311_25.5.1-1-MacOSX-x86_64.sh#1060cbd0001c12c3e89bf39413c430cc481a894f065923a03e599c8426020229" "miniconda" verify_py311 + ;; +* ) + { echo + colorize 1 "ERROR" + echo ": The binary distribution of Miniconda is not available for $(anaconda_architecture 2>/dev/null || true)." + echo + } >&2 + exit 1 + ;; +esac diff --git a/plugins/python-build/share/python-build/miniconda3-3.12-25.3.1-1 b/plugins/python-build/share/python-build/miniconda3-3.12-25.3.1-1 new file mode 100644 index 00000000..98a6bc27 --- /dev/null +++ b/plugins/python-build/share/python-build/miniconda3-3.12-25.3.1-1 @@ -0,0 +1,22 @@ +case "$(anaconda_architecture 2>/dev/null || true)" in +"Linux-aarch64" ) + install_script "Miniconda3-py312_25.3.1-1-Linux-aarch64" "https://repo.anaconda.com/miniconda/Miniconda3-py312_25.3.1-1-Linux-aarch64.sh#4b8078ef311bad880b2497c098d734cd0e7316410a4f4fa1a8dbf6ab9ade98ea" "miniconda" verify_py312 + ;; +"Linux-x86_64" ) + install_script "Miniconda3-py312_25.3.1-1-Linux-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py312_25.3.1-1-Linux-x86_64.sh#8937a2c9a86cfc5495ba2234476f89686beff5c8e0569b1ed5771a927c75107b" "miniconda" verify_py312 + ;; +"MacOSX-arm64" ) + install_script "Miniconda3-py312_25.3.1-1-MacOSX-arm64" "https://repo.anaconda.com/miniconda/Miniconda3-py312_25.3.1-1-MacOSX-arm64.sh#a0986857f44b8e22eaa65965d462921cf22c0624fef0fb9ad005811e43152ab8" "miniconda" verify_py312 + ;; +"MacOSX-x86_64" ) + install_script "Miniconda3-py312_25.3.1-1-MacOSX-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py312_25.3.1-1-MacOSX-x86_64.sh#ef6583760e507a08006618c4c425075989a7c9b990ec463538bd95e329ac07f8" "miniconda" verify_py312 + ;; +* ) + { echo + colorize 1 "ERROR" + echo ": The binary distribution of Miniconda is not available for $(anaconda_architecture 2>/dev/null || true)." + echo + } >&2 + exit 1 + ;; +esac diff --git a/plugins/python-build/share/python-build/miniconda3-3.12-25.5.1-0 b/plugins/python-build/share/python-build/miniconda3-3.12-25.5.1-0 new file mode 100644 index 00000000..1f046692 --- /dev/null +++ b/plugins/python-build/share/python-build/miniconda3-3.12-25.5.1-0 @@ -0,0 +1,22 @@ +case "$(anaconda_architecture 2>/dev/null || true)" in +"Linux-aarch64" ) + install_script "Miniconda3-py312_25.5.1-0-Linux-aarch64" "https://repo.anaconda.com/miniconda/Miniconda3-py312_25.5.1-0-Linux-aarch64.sh#9bd5765f163568f4ac85b8189aa2b260d1febb1080d085bb2535a3cae2d7f0e4" "miniconda" verify_py312 + ;; +"Linux-x86_64" ) + install_script "Miniconda3-py312_25.5.1-0-Linux-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py312_25.5.1-0-Linux-x86_64.sh#b99e5bcdf8cd2df9ffd11019eac8a20cf84598267941500935d62e14a0e2a6f6" "miniconda" verify_py312 + ;; +"MacOSX-arm64" ) + install_script "Miniconda3-py312_25.5.1-0-MacOSX-arm64" "https://repo.anaconda.com/miniconda/Miniconda3-py312_25.5.1-0-MacOSX-arm64.sh#016845b69dc9476cc3aa950b6577c9d596aae705258ee30b3261b4d258033426" "miniconda" verify_py312 + ;; +"MacOSX-x86_64" ) + install_script "Miniconda3-py312_25.5.1-0-MacOSX-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py312_25.5.1-0-MacOSX-x86_64.sh#bf9f441d20e6af3f6035e07e892e245161a4dc4962cae3346ef77c75a62b033d" "miniconda" verify_py312 + ;; +* ) + { echo + colorize 1 "ERROR" + echo ": The binary distribution of Miniconda is not available for $(anaconda_architecture 2>/dev/null || true)." + echo + } >&2 + exit 1 + ;; +esac diff --git a/plugins/python-build/share/python-build/miniconda3-3.12-25.5.1-1 b/plugins/python-build/share/python-build/miniconda3-3.12-25.5.1-1 new file mode 100644 index 00000000..8d2b79ca --- /dev/null +++ b/plugins/python-build/share/python-build/miniconda3-3.12-25.5.1-1 @@ -0,0 +1,22 @@ +case "$(anaconda_architecture 2>/dev/null || true)" in +"Linux-aarch64" ) + install_script "Miniconda3-py312_25.5.1-1-Linux-aarch64" "https://repo.anaconda.com/miniconda/Miniconda3-py312_25.5.1-1-Linux-aarch64.sh#b007c6307c9a2081b8abfa0ff16caf0117226a7a9485031853fea125795de7ca" "miniconda" verify_py312 + ;; +"Linux-x86_64" ) + install_script "Miniconda3-py312_25.5.1-1-Linux-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py312_25.5.1-1-Linux-x86_64.sh#e3228df32afc6d43cb190a416b91937cdcd1c6308d9fe652274539a07142966f" "miniconda" verify_py312 + ;; +"MacOSX-arm64" ) + install_script "Miniconda3-py312_25.5.1-1-MacOSX-arm64" "https://repo.anaconda.com/miniconda/Miniconda3-py312_25.5.1-1-MacOSX-arm64.sh#b7fef84af2ae88c395e29bde17707b9b33e34a36e431a083f5f38348c75fa89f" "miniconda" verify_py312 + ;; +"MacOSX-x86_64" ) + install_script "Miniconda3-py312_25.5.1-1-MacOSX-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py312_25.5.1-1-MacOSX-x86_64.sh#f9501093dc8a509179fb7ac6c230ae5c35405fca47d8c95bfa97d9327781727a" "miniconda" verify_py312 + ;; +* ) + { echo + colorize 1 "ERROR" + echo ": The binary distribution of Miniconda is not available for $(anaconda_architecture 2>/dev/null || true)." + echo + } >&2 + exit 1 + ;; +esac diff --git a/plugins/python-build/share/python-build/miniconda3-3.13-25.3.1-1 b/plugins/python-build/share/python-build/miniconda3-3.13-25.3.1-1 new file mode 100644 index 00000000..af30da49 --- /dev/null +++ b/plugins/python-build/share/python-build/miniconda3-3.13-25.3.1-1 @@ -0,0 +1,22 @@ +case "$(anaconda_architecture 2>/dev/null || true)" in +"Linux-aarch64" ) + install_script "Miniconda3-py313_25.3.1-1-Linux-aarch64" "https://repo.anaconda.com/miniconda/Miniconda3-py313_25.3.1-1-Linux-aarch64.sh#4caa0c266ab726b440ccad40a74774167494e001da5de281b74f2d5673e4ace9" "miniconda" verify_py313 + ;; +"Linux-x86_64" ) + install_script "Miniconda3-py313_25.3.1-1-Linux-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py313_25.3.1-1-Linux-x86_64.sh#53a86109463cfd70ba7acab396d416e623012914eee004729e1ecd6fe94e8c69" "miniconda" verify_py313 + ;; +"MacOSX-arm64" ) + install_script "Miniconda3-py313_25.3.1-1-MacOSX-arm64" "https://repo.anaconda.com/miniconda/Miniconda3-py313_25.3.1-1-MacOSX-arm64.sh#d54b27ed4a6d3c31fedbad6f9f488377702196b0d8d89854e8e7d01f701f225b" "miniconda" verify_py313 + ;; +"MacOSX-x86_64" ) + install_script "Miniconda3-py313_25.3.1-1-MacOSX-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py313_25.3.1-1-MacOSX-x86_64.sh#614c455b74d85abe98c2d0fb9b00628bbf2d48932ea4b49ec05b5c4bee7e9239" "miniconda" verify_py313 + ;; +* ) + { echo + colorize 1 "ERROR" + echo ": The binary distribution of Miniconda is not available for $(anaconda_architecture 2>/dev/null || true)." + echo + } >&2 + exit 1 + ;; +esac diff --git a/plugins/python-build/share/python-build/miniconda3-3.13-25.5.1-0 b/plugins/python-build/share/python-build/miniconda3-3.13-25.5.1-0 new file mode 100644 index 00000000..2f0fcd47 --- /dev/null +++ b/plugins/python-build/share/python-build/miniconda3-3.13-25.5.1-0 @@ -0,0 +1,22 @@ +case "$(anaconda_architecture 2>/dev/null || true)" in +"Linux-aarch64" ) + install_script "Miniconda3-py313_25.5.1-0-Linux-aarch64" "https://repo.anaconda.com/miniconda/Miniconda3-py313_25.5.1-0-Linux-aarch64.sh#368d5250682c12d3e1810e8cf5ce4f08e95ccc56c96dcf559567a6b4f72b203d" "miniconda" verify_py313 + ;; +"Linux-x86_64" ) + install_script "Miniconda3-py313_25.5.1-0-Linux-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py313_25.5.1-0-Linux-x86_64.sh#ae15e3f987bdf93edfd6f9dd35b122c34ac72555822f80f5c7c8642595a13197" "miniconda" verify_py313 + ;; +"MacOSX-arm64" ) + install_script "Miniconda3-py313_25.5.1-0-MacOSX-arm64" "https://repo.anaconda.com/miniconda/Miniconda3-py313_25.5.1-0-MacOSX-arm64.sh#2559c460b128c0c1c0ce5e9d00fd03160baa7c15ae9f758eaaaab5506d442416" "miniconda" verify_py313 + ;; +"MacOSX-x86_64" ) + install_script "Miniconda3-py313_25.5.1-0-MacOSX-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py313_25.5.1-0-MacOSX-x86_64.sh#8507e1fcea7f744af3c858b8df4666d60ed42d383442fd560fe8f97949249d88" "miniconda" verify_py313 + ;; +* ) + { echo + colorize 1 "ERROR" + echo ": The binary distribution of Miniconda is not available for $(anaconda_architecture 2>/dev/null || true)." + echo + } >&2 + exit 1 + ;; +esac diff --git a/plugins/python-build/share/python-build/miniconda3-3.13-25.5.1-1 b/plugins/python-build/share/python-build/miniconda3-3.13-25.5.1-1 new file mode 100644 index 00000000..f0d9115d --- /dev/null +++ b/plugins/python-build/share/python-build/miniconda3-3.13-25.5.1-1 @@ -0,0 +1,22 @@ +case "$(anaconda_architecture 2>/dev/null || true)" in +"Linux-aarch64" ) + install_script "Miniconda3-py313_25.5.1-1-Linux-aarch64" "https://repo.anaconda.com/miniconda/Miniconda3-py313_25.5.1-1-Linux-aarch64.sh#b7d611dcaa638efd700a4a4eb24fbcb9f7b94cc1773d7c655959c330d0b68e16" "miniconda" verify_py313 + ;; +"Linux-x86_64" ) + install_script "Miniconda3-py313_25.5.1-1-Linux-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py313_25.5.1-1-Linux-x86_64.sh#612af113b49db0368e2be41ac4d51b7088eebd5f31daeeb89f23fff8f920db58" "miniconda" verify_py313 + ;; +"MacOSX-arm64" ) + install_script "Miniconda3-py313_25.5.1-1-MacOSX-arm64" "https://repo.anaconda.com/miniconda/Miniconda3-py313_25.5.1-1-MacOSX-arm64.sh#2ec6f7981770b3396a9ab426e07ac8ef5b12b4393aa2e4bcc984376fe3aa327e" "miniconda" verify_py313 + ;; +"MacOSX-x86_64" ) + install_script "Miniconda3-py313_25.5.1-1-MacOSX-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py313_25.5.1-1-MacOSX-x86_64.sh#4152f260040d452bfe00c67ac6b429aec7ff3b98f62bab8abe4c468e98e51891" "miniconda" verify_py313 + ;; +* ) + { echo + colorize 1 "ERROR" + echo ": The binary distribution of Miniconda is not available for $(anaconda_architecture 2>/dev/null || true)." + echo + } >&2 + exit 1 + ;; +esac diff --git a/plugins/python-build/share/python-build/miniconda3-3.9-25.3.1-1 b/plugins/python-build/share/python-build/miniconda3-3.9-25.3.1-1 new file mode 100644 index 00000000..63a64094 --- /dev/null +++ b/plugins/python-build/share/python-build/miniconda3-3.9-25.3.1-1 @@ -0,0 +1,22 @@ +case "$(anaconda_architecture 2>/dev/null || true)" in +"Linux-aarch64" ) + install_script "Miniconda3-py39_25.3.1-1-Linux-aarch64" "https://repo.anaconda.com/miniconda/Miniconda3-py39_25.3.1-1-Linux-aarch64.sh#435f9b5640716dd770e9abe404c54db7d3493cb61a814c5de1fe345ea017d4a6" "miniconda" verify_py39 + ;; +"Linux-x86_64" ) + install_script "Miniconda3-py39_25.3.1-1-Linux-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py39_25.3.1-1-Linux-x86_64.sh#2d94390e8858c72f6a28080954fd640ae4449d08d7b9d4ff8c94ad39beaf5f46" "miniconda" verify_py39 + ;; +"MacOSX-arm64" ) + install_script "Miniconda3-py39_25.3.1-1-MacOSX-arm64" "https://repo.anaconda.com/miniconda/Miniconda3-py39_25.3.1-1-MacOSX-arm64.sh#5838abb6f108de3f42aaaf3c3bb0747879b4202f70332e7ffb81dc3fa6b518a4" "miniconda" verify_py39 + ;; +"MacOSX-x86_64" ) + install_script "Miniconda3-py39_25.3.1-1-MacOSX-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py39_25.3.1-1-MacOSX-x86_64.sh#51e293033a02582295e5795b7ec440ac322251a46894c20bca65dd15399bd447" "miniconda" verify_py39 + ;; +* ) + { echo + colorize 1 "ERROR" + echo ": The binary distribution of Miniconda is not available for $(anaconda_architecture 2>/dev/null || true)." + echo + } >&2 + exit 1 + ;; +esac diff --git a/plugins/python-build/share/python-build/miniconda3-3.9-25.5.1-0 b/plugins/python-build/share/python-build/miniconda3-3.9-25.5.1-0 new file mode 100644 index 00000000..c89582fd --- /dev/null +++ b/plugins/python-build/share/python-build/miniconda3-3.9-25.5.1-0 @@ -0,0 +1,22 @@ +case "$(anaconda_architecture 2>/dev/null || true)" in +"Linux-aarch64" ) + install_script "Miniconda3-py39_25.5.1-0-Linux-aarch64" "https://repo.anaconda.com/miniconda/Miniconda3-py39_25.5.1-0-Linux-aarch64.sh#e4311fcce812cce6a914e7a82fb1180d278eaa705e40be6789795781d375fe53" "miniconda" verify_py39 + ;; +"Linux-x86_64" ) + install_script "Miniconda3-py39_25.5.1-0-Linux-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py39_25.5.1-0-Linux-x86_64.sh#eac177f8c3bf584c1816aded4a0e2d9201253a4305cd0cbc8c1d4adbe6bde96d" "miniconda" verify_py39 + ;; +"MacOSX-arm64" ) + install_script "Miniconda3-py39_25.5.1-0-MacOSX-arm64" "https://repo.anaconda.com/miniconda/Miniconda3-py39_25.5.1-0-MacOSX-arm64.sh#562d7f0632a9f6ee636c6457b45daa200ca44890d920f6d671dfa44c6150b052" "miniconda" verify_py39 + ;; +"MacOSX-x86_64" ) + install_script "Miniconda3-py39_25.5.1-0-MacOSX-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py39_25.5.1-0-MacOSX-x86_64.sh#47b52b6c817c5cb41877e24f78d34add775065f8815e5205a9c3d7dada3306c7" "miniconda" verify_py39 + ;; +* ) + { echo + colorize 1 "ERROR" + echo ": The binary distribution of Miniconda is not available for $(anaconda_architecture 2>/dev/null || true)." + echo + } >&2 + exit 1 + ;; +esac diff --git a/plugins/python-build/share/python-build/miniconda3-3.9-25.5.1-1 b/plugins/python-build/share/python-build/miniconda3-3.9-25.5.1-1 new file mode 100644 index 00000000..f41114c5 --- /dev/null +++ b/plugins/python-build/share/python-build/miniconda3-3.9-25.5.1-1 @@ -0,0 +1,22 @@ +case "$(anaconda_architecture 2>/dev/null || true)" in +"Linux-aarch64" ) + install_script "Miniconda3-py39_25.5.1-1-Linux-aarch64" "https://repo.anaconda.com/miniconda/Miniconda3-py39_25.5.1-1-Linux-aarch64.sh#a1078a803928d8f8f1fd29a2bdad6689f19239844ee3beb84372f66f00cbfb19" "miniconda" verify_py39 + ;; +"Linux-x86_64" ) + install_script "Miniconda3-py39_25.5.1-1-Linux-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py39_25.5.1-1-Linux-x86_64.sh#33778762cb69381ce01de7f60bf4350935d3b31169e541fc9fbffbab5712670a" "miniconda" verify_py39 + ;; +"MacOSX-arm64" ) + install_script "Miniconda3-py39_25.5.1-1-MacOSX-arm64" "https://repo.anaconda.com/miniconda/Miniconda3-py39_25.5.1-1-MacOSX-arm64.sh#8c6968889bbebb3df94ce441775c1e7cfecae2fefb3955b09ac3b7b619bf7208" "miniconda" verify_py39 + ;; +"MacOSX-x86_64" ) + install_script "Miniconda3-py39_25.5.1-1-MacOSX-x86_64" "https://repo.anaconda.com/miniconda/Miniconda3-py39_25.5.1-1-MacOSX-x86_64.sh#83884da278a084d5d1923613af229aa4c384eebe197b48cb996a4c862d850e7f" "miniconda" verify_py39 + ;; +* ) + { echo + colorize 1 "ERROR" + echo ": The binary distribution of Miniconda is not available for $(anaconda_architecture 2>/dev/null || true)." + echo + } >&2 + exit 1 + ;; +esac From 4beacd2a9df2c4ec23ab532e3bedabc9fa133c31 Mon Sep 17 00:00:00 2001 From: Ivan Pozdeev Date: Wed, 3 Sep 2025 20:58:23 +0300 Subject: [PATCH 04/15] (refactor) --- plugins/python-build/scripts/add_miniforge.py | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/plugins/python-build/scripts/add_miniforge.py b/plugins/python-build/scripts/add_miniforge.py index d60c5036..89c3304c 100755 --- a/plugins/python-build/scripts/add_miniforge.py +++ b/plugins/python-build/scripts/add_miniforge.py @@ -123,20 +123,25 @@ def add_version(release, distributions): else: logger.info('Did not find specs for %(distribution)s', locals()) -for release in requests.get(f'https://api.github.com/repos/{MINIFORGE_REPO}/releases').json(): - version = release['tag_name'] - if version in SKIPPED_RELEASES: - continue +def main(): + for release in requests.get(f'https://api.github.com/repos/{MINIFORGE_REPO}/releases').json(): + version = release['tag_name'] - logger.info('Looking for %(version)s in %(out_dir)s', locals()) + if version in SKIPPED_RELEASES: + continue - # mambaforge is retired https://github.com/conda-forge/miniforge/releases/tag/24.11.2-0 - if version_tuple(version) >= (24,11,2): - distributions = DISTRIBUTIONS - else: - distributions = DISTRIBUTIONS_PRE25 + logger.info('Looking for %(version)s in %(out_dir)s', locals()) - if any(not list(out_dir.glob(f'{distribution}*-{version}')) for distribution in distributions): - logger.info('Downloading %(version)s', locals()) - add_version(release, distributions) + # mambaforge is retired https://github.com/conda-forge/miniforge/releases/tag/24.11.2-0 + if version_tuple(version) >= (24, 11, 2): + distributions = DISTRIBUTIONS + else: + distributions = DISTRIBUTIONS_PRE25 + + if any(not list(out_dir.glob(f'{distribution}*-{version}')) for distribution in distributions): + logger.info('Downloading %(version)s', locals()) + add_version(release, distributions) + +if __name__ == '__main__': + main() From b88f5df480ccfa21629fa0b3d72c483cd05c8096 Mon Sep 17 00:00:00 2001 From: Ivan Pozdeev Date: Sun, 14 Sep 2025 00:26:46 +0300 Subject: [PATCH 05/15] WIP --- plugins/python-build/scripts/add_cpython.py | 224 +++++--------------- 1 file changed, 58 insertions(+), 166 deletions(-) diff --git a/plugins/python-build/scripts/add_cpython.py b/plugins/python-build/scripts/add_cpython.py index aadf86ec..86e3ef8b 100644 --- a/plugins/python-build/scripts/add_cpython.py +++ b/plugins/python-build/scripts/add_cpython.py @@ -6,27 +6,28 @@ then writes a build script for any which do not exist locally, saving it to plugins/python-build/share/python-build. """ +import argparse +import collections +import functools import logging +import pathlib import re import string import sys import textwrap -from argparse import ArgumentParser -from collections import defaultdict -from dataclasses import dataclass +import typing from enum import Enum -from functools import total_ordering -from pathlib import Path from typing import NamedTuple, List, Optional, DefaultDict, Dict +import packaging.version import requests_html logger = logging.getLogger(__name__) REPO = "https://www.python.org/ftp/python/" -here = Path(__file__).resolve() -out_dir: Path = here.parent.parent / "share" / "python-build" +here = pathlib.Path(__file__).resolve() +out_dir: pathlib.Path = here.parent.parent / "share" / "python-build" class StrEnum(str, Enum): @@ -53,31 +54,6 @@ class SupportedOS(StrEnum): MACOSX = "MacOSX" -class SupportedArch(StrEnum): - AARCH64 = "aarch64" - ARM64 = "arm64" - PPC64LE = "ppc64le" - S390X = "s390x" - X86_64 = "x86_64" - X86 = "x86" - - -class Flavor(StrEnum): - ANACONDA = "anaconda" - MINICONDA = "miniconda" - - -class TFlavor(StrEnum): - ANACONDA = "Anaconda" - MINICONDA = "Miniconda" - - -class Suffix(StrEnum): - TWO = "2" - THREE = "3" - NONE = "" - - PyVersion = None class PyVersionMeta(type): def __getattr__(self, name): @@ -87,7 +63,7 @@ class PyVersionMeta(type): return super(PyVersionMeta,self).__getattr__(self, name) -@dataclass(frozen=True) +@collections.dataclass(frozen=True) class PyVersion(metaclass=PyVersionMeta): major: str minor: str @@ -111,7 +87,7 @@ class PyVersion(metaclass=PyVersionMeta): return self.value -@total_ordering +@functools.total_ordering class VersionStr(str): def info(self): return tuple(int(n) for n in self.replace("-", ".").split(".")) @@ -133,8 +109,6 @@ class VersionStr(str): class CondaVersion(NamedTuple): - flavor: Flavor - suffix: Suffix version_str: VersionStr py_version: Optional[PyVersion] @@ -174,8 +148,6 @@ class CondaVersion(NamedTuple): """ if self.py_version: return self.py_version - elif self.suffix == Suffix.TWO: - return PyVersion.PY27 v = self.version_str.info() if self.flavor == "miniconda": @@ -208,66 +180,15 @@ class CondaVersion(NamedTuple): raise ValueError(self.flavor) -class CondaSpec(NamedTuple): - tflavor: TFlavor - version: CondaVersion - os: SupportedOS - arch: SupportedArch - md5: str - repo: str - py_version: Optional[PyVersion] = None +class PyenvCPythonVersion(packaging.version.Version): + _free_threaded: bool = False - @classmethod - def from_filestem(cls, stem, md5, repo, py_version=None): - # The `*vers` captures the new trailing `-1` in some file names (a build number?) - # so they can be processed properly. - miniconda_n, *vers, os, arch = stem.split("-") - ver = "-".join(vers) - suffix = miniconda_n[-1] - if suffix in string.digits: - tflavor = miniconda_n[:-1] - else: - tflavor = miniconda_n - suffix = "" - flavor = tflavor.lower() - - if ver.startswith("py"): - py_ver, ver = ver.split("_", maxsplit=1) - py_ver = PyVersion(py_ver) - else: - py_ver = None - spec = CondaSpec( - TFlavor(tflavor), - CondaVersion(Flavor(flavor), Suffix(suffix), VersionStr(ver), py_ver), - SupportedOS(os), - SupportedArch(arch), - md5, - repo, - py_ver - ) - if py_version is None and py_ver is None and ver != "latest": - spec = spec.with_py_version(spec.version.default_py_version()) - return spec - - def to_install_lines(self): - """ - Installation command for this version of Miniconda for use in a Pyenv installation script - """ - return install_line_fmt.format( - tflavor=self.tflavor, - flavor=self.version.flavor, - repo=self.repo, - suffix=self.version.suffix, - version_str=self.version.version_str, - version_py_version=f"{self.version.py_version}_" if self.version.py_version else "", - os=self.os, - arch=self.arch, - md5=self.md5, - py_version=self.py_version, - ) - - def with_py_version(self, py_version: PyVersion): - return CondaSpec(*self[:-1], py_version=py_version) + def __init__(self, version_str): + m = re.match(r"^(.*[^a-zA-Z])t$", version_str) + if m: + self._free_threaded = True + version_str = m.group(1) + super().__init__(self, version_str) def make_script(specs: List[CondaSpec]): @@ -278,92 +199,46 @@ def make_script(specs: List[CondaSpec]): ) -def get_existing_condas(name): +def get_existing_scripts(name, pattern) -> typing.Generator[typing.Tuple[str, PyenvCPythonVersion]]: """ - Enumerate existing Miniconda installation scripts in share/python-build/ except rolling releases. - - :returns: A generator of :class:`CondaVersion` objects. + Enumerate existing installation scripts in share/python-build/ by pattern """ - logger.info("Getting known %(name)s versions",locals()) + logger.debug("Getting existing versions") for p in out_dir.iterdir(): entry_name = p.name - if not p.is_file() or not entry_name.startswith(name): + if not p.is_file() or not re.match(pattern,entry_name): continue try: - v = CondaVersion.from_str(entry_name) - if v.version_str != "latest": - logger.debug("Found existing %(name)s version %(v)s", locals()) - yield v + v = PyenvCPythonVersion(entry_name) + logger.debug("Existing %(name)s version %(v)s", locals()) + yield entry_name, v except ValueError as e: logger.error("Unable to parse existing version %s: %s", entry_name, e) -def get_available_condas(name, repo): +def get_available_versions(name, repo) -> typing.Generator[typing.Tuple[str, str]]: """ - Fetch remote miniconda versions. - - :returns: A generator of :class:`CondaSpec` objects for each release available for download - except rolling releases. + Fetch remote versions """ logger.info("Fetching remote %(name)s versions",locals()) session = requests_html.HTMLSession() response = session.get(repo) page: requests_html.HTML = response.html - table = page.find("table", first=True) - rows = table.find("tr")[1:] - for row in rows: - f, size, date, md5 = row.find("td") - fname = f.text - md5 = md5.text + table = page.find("pre", first=True) + # the first entry is ".." + links = table.find("a")[1:] + for link in links: + logger.debug('Available %(name)s version: %(link)s', locals()) + yield link.text, link.attrs['href'] - if not fname.endswith(".sh"): - continue - stem = fname[:-3] - - try: - s = CondaSpec.from_filestem(stem, md5, repo) - if s.version.version_str != "latest": - logger.debug("Found remote %(name)s version %(s)s", locals()) - yield s - except ValueError: - pass - - -def key_fn(spec: CondaSpec): - return ( - spec.tflavor, - spec.version.version_str.info(), - spec.version.suffix.value, - spec.os.value, - spec.arch.value, - ) - - -if __name__ == "__main__": - parser = ArgumentParser(description=__doc__) - parser.add_argument( - "-d", "--dry-run", action="store_true", - help="Do not write scripts, just report them to stdout", - ) - parser.add_argument( - "-v", "--verbose", action="store_true", default=0, - help="Increase verbosity of logging", - ) - parsed = parser.parse_args() - - logging.basicConfig(level=logging.DEBUG if parsed.verbose else logging.INFO) - - existing_versions = set() - available_specs = set() - for name,repo in ("miniconda",MINICONDA_REPO),("anaconda",ANACONDA_REPO): - existing_versions |= set(get_existing_condas(name)) - available_specs |= set(get_available_condas(name, repo)) +def main(): + args = parse_args() + logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO) + existing_versions = dict(get_existing_scripts("CPython", "^\d++\.\d++(?!-)")) + available_versions = dict(get_available_versions("CPython", REPO)) # version triple to triple-ified spec to raw spec - to_add: DefaultDict[ - CondaVersion, Dict[CondaSpec, CondaSpec] - ] = defaultdict(dict) - + versions_to_add = set(available_versions.keys()) - set(existing_versions.keys()) logger.info("Checking for new versions") for s in sorted(available_specs, key=key_fn): key = s.version @@ -382,15 +257,32 @@ if __name__ == "__main__": continue to_add[key][s] = s - logger.info("Writing %s scripts", len(to_add)) for ver, d in to_add.items(): specs = list(d.values()) fpath = out_dir / ver.to_filename() script_str = make_script(specs) logger.info("Writing script for %s", ver) - if parsed.dry_run: + if args.dry_run: print(f"Would write spec to {fpath}:\n" + textwrap.indent(script_str, " ")) else: with open(fpath, "w") as f: f.write(script_str) + + +def parse_args(): + parser = ArgumentParser(description=__doc__) + parser.add_argument( + "-d", "--dry-run", action="store_true", + help="Do not write scripts, just report them to stdout", + ) + parser.add_argument( + "-v", "--verbose", action="store_true", default=0, + help="Increase verbosity of logging", + ) + parsed = parser.parse_args() + return parsed + + +if __name__ == "__main__": + main() From bd7b425b66ebce16aeafdd7f6195ef4504c8218a Mon Sep 17 00:00:00 2001 From: Ivan Pozdeev Date: Fri, 14 Nov 2025 12:55:05 +0300 Subject: [PATCH 06/15] replace install_package line error when deleted prerelease is then tried as a base --- plugins/python-build/scripts/add_cpython.py | 447 +++++++++--------- plugins/python-build/scripts/requirements.txt | 4 + 2 files changed, 232 insertions(+), 219 deletions(-) diff --git a/plugins/python-build/scripts/add_cpython.py b/plugins/python-build/scripts/add_cpython.py index 86e3ef8b..544d94a2 100644 --- a/plugins/python-build/scripts/add_cpython.py +++ b/plugins/python-build/scripts/add_cpython.py @@ -7,271 +7,226 @@ saving it to plugins/python-build/share/python-build. """ import argparse -import collections -import functools +import dataclasses +import hashlib +import io +import itertools import logging +import operator import pathlib +import pprint import re -import string -import sys -import textwrap import typing -from enum import Enum -from typing import NamedTuple, List, Optional, DefaultDict, Dict +import urllib.parse +import more_itertools import packaging.version +import requests import requests_html +import sortedcontainers logger = logging.getLogger(__name__) REPO = "https://www.python.org/ftp/python/" +CUTOFF_VERSION=packaging.version.Version('3.9') +EXCLUDED_VERSIONS= { + packaging.version.Version("3.9.3") #recalled +} + here = pathlib.Path(__file__).resolve() out_dir: pathlib.Path = here.parent.parent / "share" / "python-build" - -class StrEnum(str, Enum): - """Enum subclass whose members are also instances of str - and directly comparable to strings. str type is forced at declaration. - - Adapted from https://github.com/kissgyorgy/enum34-custom/blob/dbc89596761c970398701d26c6a5bbcfcf70f548/enum_custom.py#L100 - (MIT license) - """ - - def __new__(cls, *args): - for arg in args: - if not isinstance(arg, str): - raise TypeError("Not text %s:" % arg) - - return super(StrEnum, cls).__new__(cls, *args) - - def __str__(self): - return str(self.value) +T_THUNK=\ +'''export PYTHON_BUILD_FREE_THREADING=1 +source "${BASH_SOURCE[0]%t}"''' -class SupportedOS(StrEnum): - LINUX = "Linux" - MACOSX = "MacOSX" - - -PyVersion = None -class PyVersionMeta(type): - def __getattr__(self, name): - """Generate PyVersion.PYXXX on demand to future-proof it""" - if PyVersion is not None: - return PyVersion(name.lower()) - return super(PyVersionMeta,self).__getattr__(self, name) - - -@collections.dataclass(frozen=True) -class PyVersion(metaclass=PyVersionMeta): - major: str - minor: str - - def __init__(self, value): - (major, minor) = re.match(r"py(\d)(\d+)", value).groups() - object.__setattr__(self, "major", major) - object.__setattr__(self, "minor", minor) - - @property - def value(self): - return f"py{self.major}{self.minor}" - - def version(self): - return f"{self.major}.{self.minor}" - - def version_info(self): - return (self.major, self.minor) - - def __str__(self): - return self.value - - -@functools.total_ordering -class VersionStr(str): - def info(self): - return tuple(int(n) for n in self.replace("-", ".").split(".")) - - def __eq__(self, other): - return str(self) == str(other) - - def __lt__(self, other): - if isinstance(other, VersionStr): - return self.info() < other.info() - raise ValueError("VersionStr can only be compared to other VersionStr") - - @classmethod - def from_info(cls, version_info): - return VersionStr(".".join(str(n) for n in version_info)) - - def __hash__(self): - return hash(str(self)) - - -class CondaVersion(NamedTuple): - version_str: VersionStr - py_version: Optional[PyVersion] - - @classmethod - def from_str(cls, s): - """ - Convert a string of the form "miniconda_n-ver" or "miniconda_n-py_ver-ver" to a :class:`CondaVersion` object. - """ - miniconda_n, _, remainder = s.partition("-") - suffix = miniconda_n[-1] - if suffix in string.digits: - flavor = miniconda_n[:-1] - else: - flavor = miniconda_n - suffix = "" - - components = remainder.split("-") - if flavor == Flavor.MINICONDA and len(components) >= 2: - py_ver, *ver_parts = components - py_ver = PyVersion(f"py{py_ver.replace('.', '')}") - ver = "-".join(ver_parts) - else: - ver = "-".join(components) - py_ver = None - - return CondaVersion(Flavor(flavor), Suffix(suffix), VersionStr(ver), py_ver) - - def to_filename(self): - if self.py_version: - return f"{self.flavor}{self.suffix}-{self.py_version.version()}-{self.version_str}" - else: - return f"{self.flavor}{self.suffix}-{self.version_str}" - - def default_py_version(self): - """ - :class:`PyVersion` of Python used with this Miniconda version - """ - if self.py_version: - return self.py_version - - v = self.version_str.info() - if self.flavor == "miniconda": - # https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-python.html - if v < (4, 7): - return PyVersion.PY36 - if v < (4, 8): - return PyVersion.PY37 - else: - # since 4.8, Miniconda specifies versions explicitly in the file name - raise ValueError("Miniconda 4.8+ is supposed to specify a Python version explicitly") - if self.flavor == "anaconda": - # https://docs.anaconda.com/free/anaconda/reference/release-notes/ - if v >= (2024,6): - return PyVersion.PY312 - if v >= (2023,7): - return PyVersion.PY311 - if v >= (2023,3): - return PyVersion.PY310 - if v >= (2021,11): - return PyVersion.PY39 - if v >= (2020,7): - return PyVersion.PY38 - if v >= (2020,2): - return PyVersion.PY37 - if v >= (5,3,0): - return PyVersion.PY37 - return PyVersion.PY36 - - raise ValueError(self.flavor) - - -class PyenvCPythonVersion(packaging.version.Version): - _free_threaded: bool = False - - def __init__(self, version_str): - m = re.match(r"^(.*[^a-zA-Z])t$", version_str) - if m: - self._free_threaded = True - version_str = m.group(1) - super().__init__(self, version_str) - - -def make_script(specs: List[CondaSpec]): - install_lines = [s.to_install_lines() for s in specs] - return install_script_fmt.format( - install_lines="\n".join(install_lines), - tflavor=specs[0].tflavor, - ) - - -def get_existing_scripts(name, pattern) -> typing.Generator[typing.Tuple[str, PyenvCPythonVersion]]: +def get_existing_scripts(name="CPython", pattern=r'^\d+\.\d+(?:(t?)(-\w+)|(.\d+((?:a|b|rc)\d)?(t?)))$'): """ Enumerate existing installation scripts in share/python-build/ by pattern """ logger.debug("Getting existing versions") for p in out_dir.iterdir(): entry_name = p.name - if not p.is_file() or not re.match(pattern,entry_name): + if not p.is_file() or not (m := re.match(pattern,entry_name)) or m.group(1)=='t' or m.group(5)=='t': continue try: - v = PyenvCPythonVersion(entry_name) + v = packaging.version.Version(entry_name) + # branch tip scrpts are different from release scripts and thus unusable as a pattern + if v.dev is not None: + continue logger.debug("Existing %(name)s version %(v)s", locals()) - yield entry_name, v + yield v,entry_name except ValueError as e: logger.error("Unable to parse existing version %s: %s", entry_name, e) -def get_available_versions(name, repo) -> typing.Generator[typing.Tuple[str, str]]: - """ - Fetch remote versions - """ - logger.info("Fetching remote %(name)s versions",locals()) - session = requests_html.HTMLSession() - response = session.get(repo) - page: requests_html.HTML = response.html +def _get_download_entries(url, pattern, session=None): + if session is None: + session = requests_html.HTMLSession() + response = session.get(url) + page = response.html table = page.find("pre", first=True) # the first entry is ".." links = table.find("a")[1:] for link in links: + name = link.text.rstrip('/') + if not re.match(pattern, name): + continue + yield name, urllib.parse.urljoin(response.url,link.attrs['href']) + + +def get_available_versions(name="CPython", url=REPO, pattern=r'^\d+', session=None): + """ + Fetch remote versions + """ + logger.info("Fetching remote %(name)s versions",locals()) + for name, url in _get_download_entries(url, pattern, session): + logger.debug('Available %(name)s version: %(link)s', locals()) - yield link.text, link.attrs['href'] + yield packaging.version.Version(name), url + + +def get_available_source_downloads(url, session) -> typing.Dict[ + packaging.version.Version, + typing.Dict[str,typing.Tuple[str,str]]]: + result: typing.Dict[packaging.version.Version, dict] = {} + for name, url in _get_download_entries(url, r'Python-.*\.(tar\.xz|tgz)$', session): + m=re.match(r'(?PPython-(?P.*))\.(?Ptar\.xz|tgz)$',name) + version = packaging.version.Version(m.group("version")) + extension = m.group("extension") + result.setdefault(version,{})[extension]=(m.group("package"),url) + return result + + +def pick_previous_version(version: packaging.version.Version, + available_versions: typing.Iterable[packaging.version.Version]): + return max(v for v in available_versions if v < version) + + +def adapt_script(version: packaging.version.Version, + extensions_urls: typing.Dict[str,typing.Tuple[str,str]], + previous_version: packaging.version.Version, + is_prerelease_upgrade: bool) -> None: + previous_version_path = out_dir.joinpath(str(previous_version)) + with previous_version_path.open("r", encoding='utf-8') as f: + script = f.readlines() + result = io.StringIO() + for line in script: + if m:=re.match(r'\s*install_package\s+"(?PPython-\S+)"\s+' + r'"(?P\S+)"\s+.*\s+verify_py(?P\d+)\s+.*$', + line): + existing_url_path = urllib.parse.urlparse(m.group('url')).path + try: + matched_extension = more_itertools.one(ext for ext in extensions_urls if existing_url_path.endswith(ext)) + except ValueError: + logger.error(f'Cannot match existing URL path\'s {existing_url_path} extension ' + f'to available packages {extensions_urls}') + return + new_package_name, new_package_url = extensions_urls[matched_extension] + new_package_hash = Url.sha256_url(new_package_url) + + verify_py_suffix = str(version.major)+str(version.minor) + + line = Re.subgroups(m, + package=new_package_name, + url=new_package_url+'#'+new_package_hash, + verify_py_suffix=verify_py_suffix) + + result.write(line) + result_path = out_dir.joinpath(str(version)) + logger.debug(f"Writing {result_path}") + result_path.write_text(result.getvalue(), encoding='utf-8') + result.close() + + if is_prerelease_upgrade: + logger.debug(f'Deleting {previous_version_path}') + previous_version_path.unlink() + + if (version.major, version.minor) >= (3, 13): + # an old thunk may have older version-specific code + # so it's safer to write a known version-independent template + thunk_path = out_dir.joinpath(str(version) + "t") + logger.debug(f"Writing {thunk_path}") + thunk_path.write_text(T_THUNK, encoding='utf-8') + if is_prerelease_upgrade: + previous_thunk_path = out_dir.joinpath(str(previous_version) + "t") + logger.debug(f"Deleting {previous_thunk_path}") + previous_thunk_path.unlink() + +def add_version(version: packaging.version.Version, + url: str, + existing_versions: typing.Collection[packaging.version.Version], + session: requests_html.BaseSession = None): + previous_version = pick_previous_version(version, existing_versions) + is_prerelease_upgrade = previous_version.major==version.major\ + and previous_version.minor==version.minor\ + and previous_version.micro==version.micro + if is_prerelease_upgrade: + logger.info(f"Checking for a (pre)release for {version} newer than {previous_version}") + else: + logger.info(f"Adding {version} based on {previous_version}") + + available_downloads = get_available_source_downloads(url, session) + latest_available_download_version = max(available_downloads.keys()) + if latest_available_download_version == previous_version: + logger.info("No newer download found") + return + + adapt_script(latest_available_download_version, + available_downloads[latest_available_download_version], + previous_version, + is_prerelease_upgrade) def main(): args = parse_args() logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO) + cached_session=None - existing_versions = dict(get_existing_scripts("CPython", "^\d++\.\d++(?!-)")) - available_versions = dict(get_available_versions("CPython", REPO)) + existing_versions = dict(get_existing_scripts()) + available_versions = dict(get_available_versions(session=cached_session)) # version triple to triple-ified spec to raw spec versions_to_add = set(available_versions.keys()) - set(existing_versions.keys()) + versions_to_add = {v for v in versions_to_add if v>=CUTOFF_VERSION and v not in EXCLUDED_VERSIONS} logger.info("Checking for new versions") - for s in sorted(available_specs, key=key_fn): - key = s.version - vv = key.version_str.info() - - reason = None - if key in existing_versions: - reason = "already exists" - elif key.version_str.info() <= (4, 3, 30): - reason = "too old" - elif len(key.version_str.info()) >= 4 and "-" not in key.version_str: - reason = "ignoring hotfix releases" - - if reason: - logger.debug("Ignoring version %(s)s (%(reason)s)", locals()) - continue - - to_add[key][s] = s - logger.info("Writing %s scripts", len(to_add)) - for ver, d in to_add.items(): - specs = list(d.values()) - fpath = out_dir / ver.to_filename() - script_str = make_script(specs) - logger.info("Writing script for %s", ver) - if args.dry_run: - print(f"Would write spec to {fpath}:\n" + textwrap.indent(script_str, " ")) - else: - with open(fpath, "w") as f: - f.write(script_str) + logger.debug("Existing_versions:\n"+pprint.pformat(existing_versions)) + logger.debug("Available_versions:\n"+pprint.pformat(available_versions)) + logger.debug("Versions to add:\n"+pprint.pformat(versions_to_add)) + for version_to_add in versions_to_add: + add_version(version_to_add, available_versions[version_to_add], existing_versions) + # for s in available_versions: + # key = s.version + # vv = key.version_str.info() + # + # reason = None + # if key in existing_versions: + # reason = "already exists" + # elif key.version_str.info() <= (4, 3, 30): + # reason = "too old" + # elif len(key.version_str.info()) >= 4 and "-" not in key.version_str: + # reason = "ignoring hotfix releases" + # + # if reason: + # logger.debug("Ignoring version %(s)s (%(reason)s)", locals()) + # continue + # + # to_add[key][s] = s + # logger.info("Writing %s scripts", len(to_add)) + # for ver, d in to_add.items(): + # specs = list(d.values()) + # fpath = out_dir / ver.to_filename() + # script_str = make_script(specs) + # logger.info("Writing script for %s", ver) + # if args.dry_run: + # print(f"Would write spec to {fpath}:\n" + textwrap.indent(script_str, " ")) + # else: + # with open(fpath, "w") as f: + # f.write(script_str) def parse_args(): - parser = ArgumentParser(description=__doc__) + parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( "-d", "--dry-run", action="store_true", help="Do not write scripts, just report them to stdout", @@ -283,6 +238,60 @@ def parse_args(): parsed = parser.parse_args() return parsed +class Re: + @dataclasses.dataclass + class _interval: + group: typing.Union[int, str, None] + start: int + end: int + @staticmethod + def subgroups(match: re.Match, + /, *args: [typing.AnyStr], + **kwargs: [typing.AnyStr])\ + -> typing.AnyStr: + repls={i:repl for i,repl in enumerate(args) if repl is not None} + repls.update({n:repl for n,repl in kwargs.items() if repl is not None}) + + intervals: sortedcontainers.SortedList[Re._interval]=\ + sortedcontainers.SortedKeyList(key=operator.attrgetter("start","end")) + + for group_id in itertools.chain(range(1,len(match.groups())), match.groupdict().keys()): + if group_id not in repls: + continue + if match.start(group_id) == -1: + continue + intervals.add(Re._interval(group_id,match.start(group_id),match.end(group_id))) + del group_id + + last_interval=Re._interval(None,0,0) + result="" + for interval in intervals: + if interval.start < last_interval.end: + raise ValueError(f"Cannot replace intersecting matches " + f"for groups {last_interval.group} and {interval.group} " + f"(position {interval.start})") + if interval.end == interval.start and \ + last_interval.start == last_interval.end == interval.start: + raise ValueError(f"Cannot replace consecutive zero-length matches " + f"for groups {last_interval.group} and {interval.group} " + f"(position {interval.start})") + + result+=match.string[last_interval.end:interval.start]+repls[interval.group] + last_interval = interval + result+=match.string[last_interval.end:] + + return result + +class Url: + @staticmethod + def sha256_url(url): + logger.info(f"Downloading and computing hash of {url}") + h=hashlib.sha256() + r=requests.get(url,stream=True) + for c in r.iter_content(None): + h.update(c) + return h.hexdigest() + if __name__ == "__main__": main() diff --git a/plugins/python-build/scripts/requirements.txt b/plugins/python-build/scripts/requirements.txt index a8fdb314..693e69d6 100644 --- a/plugins/python-build/scripts/requirements.txt +++ b/plugins/python-build/scripts/requirements.txt @@ -1 +1,5 @@ requests-html + +packaging~=20.4 +requests~=2.32.4 +sortedcontainers~=2.4.0 \ No newline at end of file From 047be557d42f95c28c36b0bbb31cff8afa3fe786 Mon Sep 17 00:00:00 2001 From: Ivan Pozdeev Date: Sat, 15 Nov 2025 18:36:22 +0300 Subject: [PATCH 07/15] fix message --- plugins/python-build/scripts/add_cpython.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/python-build/scripts/add_cpython.py b/plugins/python-build/scripts/add_cpython.py index 544d94a2..0d40de0d 100644 --- a/plugins/python-build/scripts/add_cpython.py +++ b/plugins/python-build/scripts/add_cpython.py @@ -84,7 +84,7 @@ def get_available_versions(name="CPython", url=REPO, pattern=r'^\d+', session=No logger.info("Fetching remote %(name)s versions",locals()) for name, url in _get_download_entries(url, pattern, session): - logger.debug('Available %(name)s version: %(link)s', locals()) + logger.debug(f'Available version: {name}, {url}') yield packaging.version.Version(name), url From f561ab17e213ec6c6bd244864a83a5b1151aee81 Mon Sep 17 00:00:00 2001 From: Ivan Pozdeev Date: Sat, 15 Nov 2025 18:40:28 +0300 Subject: [PATCH 08/15] Update existing_versions when deleting older prerelease --- plugins/python-build/scripts/add_cpython.py | 62 ++++++++++++++------- 1 file changed, 42 insertions(+), 20 deletions(-) diff --git a/plugins/python-build/scripts/add_cpython.py b/plugins/python-build/scripts/add_cpython.py index 0d40de0d..75d95c78 100644 --- a/plugins/python-build/scripts/add_cpython.py +++ b/plugins/python-build/scripts/add_cpython.py @@ -140,24 +140,9 @@ def adapt_script(version: packaging.version.Version, result_path.write_text(result.getvalue(), encoding='utf-8') result.close() - if is_prerelease_upgrade: - logger.debug(f'Deleting {previous_version_path}') - previous_version_path.unlink() - - if (version.major, version.minor) >= (3, 13): - # an old thunk may have older version-specific code - # so it's safer to write a known version-independent template - thunk_path = out_dir.joinpath(str(version) + "t") - logger.debug(f"Writing {thunk_path}") - thunk_path.write_text(T_THUNK, encoding='utf-8') - if is_prerelease_upgrade: - previous_thunk_path = out_dir.joinpath(str(previous_version) + "t") - logger.debug(f"Deleting {previous_thunk_path}") - previous_thunk_path.unlink() - def add_version(version: packaging.version.Version, url: str, - existing_versions: typing.Collection[packaging.version.Version], + existing_versions: typing.MutableMapping[packaging.version.Version,typing.Any], session: requests_html.BaseSession = None): previous_version = pick_previous_version(version, existing_versions) is_prerelease_upgrade = previous_version.major==version.major\ @@ -170,14 +155,50 @@ def add_version(version: packaging.version.Version, available_downloads = get_available_source_downloads(url, session) latest_available_download_version = max(available_downloads.keys()) - if latest_available_download_version == previous_version: - logger.info("No newer download found") - return + if is_prerelease_upgrade: + if latest_available_download_version == previous_version: + logger.info("No newer download found") + return False + else: + logger.info(f"Adding {version} replacing {previous_version}") adapt_script(latest_available_download_version, available_downloads[latest_available_download_version], previous_version, - is_prerelease_upgrade) + is_prerelease_upgrade, + session) + + cleanup_prerelease_upgrade(is_prerelease_upgrade, previous_version, existing_versions) + + handle_t_thunks(version, previous_version, is_prerelease_upgrade) + + return True + + +def cleanup_prerelease_upgrade( + is_prerelease_upgrade: bool, + previous_version: packaging.version.Version, + existing_versions: typing.MutableMapping[packaging.version.Version,typing.Any])\ + -> None: + if is_prerelease_upgrade: + previous_version_path = out_dir.joinpath(str(previous_version)) + logger.debug(f'Deleting {previous_version_path}') + previous_version_path.unlink() + del existing_versions[previous_version] + + +def handle_t_thunks(version, previous_version, is_prerelease_upgrade): + if (version.major, version.minor) >= (3, 13): + # an old thunk may have older version-specific code + # so it's safer to write a known version-independent template + thunk_path = out_dir.joinpath(str(version) + "t") + logger.debug(f"Writing {thunk_path}") + thunk_path.write_text(T_THUNK, encoding='utf-8') + if is_prerelease_upgrade: + previous_thunk_path = out_dir.joinpath(str(previous_version) + "t") + logger.debug(f"Deleting {previous_thunk_path}") + previous_thunk_path.unlink() + def main(): args = parse_args() @@ -238,6 +259,7 @@ def parse_args(): parsed = parser.parse_args() return parsed + class Re: @dataclasses.dataclass class _interval: From 602bb7a8e64192990fae83498bfcf34265f166f9 Mon Sep 17 00:00:00 2001 From: Ivan Pozdeev Date: Sat, 15 Nov 2025 18:41:57 +0300 Subject: [PATCH 09/15] reuse HTTP session --- plugins/python-build/scripts/add_cpython.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/plugins/python-build/scripts/add_cpython.py b/plugins/python-build/scripts/add_cpython.py index 75d95c78..be562e4f 100644 --- a/plugins/python-build/scripts/add_cpython.py +++ b/plugins/python-build/scripts/add_cpython.py @@ -108,7 +108,8 @@ def pick_previous_version(version: packaging.version.Version, def adapt_script(version: packaging.version.Version, extensions_urls: typing.Dict[str,typing.Tuple[str,str]], previous_version: packaging.version.Version, - is_prerelease_upgrade: bool) -> None: + is_prerelease_upgrade: bool, + session: requests_html.BaseSession = None) -> None: previous_version_path = out_dir.joinpath(str(previous_version)) with previous_version_path.open("r", encoding='utf-8') as f: script = f.readlines() @@ -125,7 +126,7 @@ def adapt_script(version: packaging.version.Version, f'to available packages {extensions_urls}') return new_package_name, new_package_url = extensions_urls[matched_extension] - new_package_hash = Url.sha256_url(new_package_url) + new_package_hash = Url.sha256_url(new_package_url, session) verify_py_suffix = str(version.major)+str(version.minor) @@ -203,7 +204,7 @@ def handle_t_thunks(version, previous_version, is_prerelease_upgrade): def main(): args = parse_args() logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO) - cached_session=None + cached_session=requests_html.HTMLSession() existing_versions = dict(get_existing_scripts()) available_versions = dict(get_available_versions(session=cached_session)) @@ -215,7 +216,7 @@ def main(): logger.debug("Available_versions:\n"+pprint.pformat(available_versions)) logger.debug("Versions to add:\n"+pprint.pformat(versions_to_add)) for version_to_add in versions_to_add: - add_version(version_to_add, available_versions[version_to_add], existing_versions) + add_version(version_to_add, available_versions[version_to_add], existing_versions, session=cached_session) # for s in available_versions: # key = s.version # vv = key.version_str.info() @@ -306,10 +307,12 @@ class Re: class Url: @staticmethod - def sha256_url(url): + def sha256_url(url, session=None): + if session is None: + session = requests_html.HTMLSession() logger.info(f"Downloading and computing hash of {url}") h=hashlib.sha256() - r=requests.get(url,stream=True) + r=session.get(url,stream=True) for c in r.iter_content(None): h.update(c) return h.hexdigest() From 00fd0d55957db1d8844c8516d0f1db8409c41dcd Mon Sep 17 00:00:00 2001 From: Ivan Pozdeev Date: Sat, 15 Nov 2025 18:42:34 +0300 Subject: [PATCH 10/15] add progress bar --- plugins/python-build/scripts/add_cpython.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/plugins/python-build/scripts/add_cpython.py b/plugins/python-build/scripts/add_cpython.py index be562e4f..1f939df0 100644 --- a/plugins/python-build/scripts/add_cpython.py +++ b/plugins/python-build/scripts/add_cpython.py @@ -24,6 +24,7 @@ import packaging.version import requests import requests_html import sortedcontainers +import tqdm logger = logging.getLogger(__name__) @@ -306,6 +307,7 @@ class Re: return result class Url: + logger = logging.getLogger("Url") @staticmethod def sha256_url(url, session=None): if session is None: @@ -313,8 +315,11 @@ class Url: logger.info(f"Downloading and computing hash of {url}") h=hashlib.sha256() r=session.get(url,stream=True) - for c in r.iter_content(None): - h.update(c) + total_bytes=int(r.headers.get('content-length',0)) or float('inf') + with tqdm.tqdm(total=total_bytes, unit='B', unit_scale=True, unit_divisor=1024) as t: + for c in r.iter_content(1024): + t.update(len(c)) + h.update(c) return h.hexdigest() From 0542763abb9fe2e698ba48e9d2ae69030136a377 Mon Sep 17 00:00:00 2001 From: Ivan Pozdeev Date: Sat, 15 Nov 2025 18:42:53 +0300 Subject: [PATCH 11/15] -dead code --- plugins/python-build/scripts/add_cpython.py | 28 --------------------- 1 file changed, 28 deletions(-) diff --git a/plugins/python-build/scripts/add_cpython.py b/plugins/python-build/scripts/add_cpython.py index 1f939df0..880457ca 100644 --- a/plugins/python-build/scripts/add_cpython.py +++ b/plugins/python-build/scripts/add_cpython.py @@ -218,34 +218,6 @@ def main(): logger.debug("Versions to add:\n"+pprint.pformat(versions_to_add)) for version_to_add in versions_to_add: add_version(version_to_add, available_versions[version_to_add], existing_versions, session=cached_session) - # for s in available_versions: - # key = s.version - # vv = key.version_str.info() - # - # reason = None - # if key in existing_versions: - # reason = "already exists" - # elif key.version_str.info() <= (4, 3, 30): - # reason = "too old" - # elif len(key.version_str.info()) >= 4 and "-" not in key.version_str: - # reason = "ignoring hotfix releases" - # - # if reason: - # logger.debug("Ignoring version %(s)s (%(reason)s)", locals()) - # continue - # - # to_add[key][s] = s - # logger.info("Writing %s scripts", len(to_add)) - # for ver, d in to_add.items(): - # specs = list(d.values()) - # fpath = out_dir / ver.to_filename() - # script_str = make_script(specs) - # logger.info("Writing script for %s", ver) - # if args.dry_run: - # print(f"Would write spec to {fpath}:\n" + textwrap.indent(script_str, " ")) - # else: - # with open(fpath, "w") as f: - # f.write(script_str) def parse_args(): From 2c16db9dfcf0e45cee355211646b4d4503c86c41 Mon Sep 17 00:00:00 2001 From: Ivan Pozdeev Date: Sat, 15 Nov 2025 18:43:30 +0300 Subject: [PATCH 12/15] Add in sorted order, report versions to add in info --- plugins/python-build/scripts/add_cpython.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/python-build/scripts/add_cpython.py b/plugins/python-build/scripts/add_cpython.py index 880457ca..bf5c0252 100644 --- a/plugins/python-build/scripts/add_cpython.py +++ b/plugins/python-build/scripts/add_cpython.py @@ -211,11 +211,11 @@ def main(): available_versions = dict(get_available_versions(session=cached_session)) # version triple to triple-ified spec to raw spec versions_to_add = set(available_versions.keys()) - set(existing_versions.keys()) - versions_to_add = {v for v in versions_to_add if v>=CUTOFF_VERSION and v not in EXCLUDED_VERSIONS} + versions_to_add = sorted({v for v in versions_to_add if v>=CUTOFF_VERSION and v not in EXCLUDED_VERSIONS}) logger.info("Checking for new versions") logger.debug("Existing_versions:\n"+pprint.pformat(existing_versions)) logger.debug("Available_versions:\n"+pprint.pformat(available_versions)) - logger.debug("Versions to add:\n"+pprint.pformat(versions_to_add)) + logger.info("Versions to add:\n"+pprint.pformat(versions_to_add)) for version_to_add in versions_to_add: add_version(version_to_add, available_versions[version_to_add], existing_versions, session=cached_session) From 949ff7f5daa3c0509f806d3f93b814f3e7c70cf8 Mon Sep 17 00:00:00 2001 From: Ivan Pozdeev Date: Sat, 15 Nov 2025 21:12:26 +0300 Subject: [PATCH 13/15] + progress bar --- plugins/python-build/scripts/requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/python-build/scripts/requirements.txt b/plugins/python-build/scripts/requirements.txt index 693e69d6..8f3d9b9c 100644 --- a/plugins/python-build/scripts/requirements.txt +++ b/plugins/python-build/scripts/requirements.txt @@ -2,4 +2,5 @@ requests-html packaging~=20.4 requests~=2.32.4 -sortedcontainers~=2.4.0 \ No newline at end of file +sortedcontainers~=2.4.0 +tqdm~=4.51.0 \ No newline at end of file From 7bb34890d1e18378d48ecbd592f00d67ec91ca9b Mon Sep 17 00:00:00 2001 From: Ivan Pozdeev Date: Sat, 15 Nov 2025 21:13:25 +0300 Subject: [PATCH 14/15] Clarify version for initial relesaes early and retain download file information --- plugins/python-build/scripts/add_cpython.py | 76 ++++++++++++++------- 1 file changed, 53 insertions(+), 23 deletions(-) diff --git a/plugins/python-build/scripts/add_cpython.py b/plugins/python-build/scripts/add_cpython.py index bf5c0252..fe381e57 100644 --- a/plugins/python-build/scripts/add_cpython.py +++ b/plugins/python-build/scripts/add_cpython.py @@ -21,7 +21,6 @@ import urllib.parse import more_itertools import packaging.version -import requests import requests_html import sortedcontainers import tqdm @@ -32,7 +31,7 @@ REPO = "https://www.python.org/ftp/python/" CUTOFF_VERSION=packaging.version.Version('3.9') EXCLUDED_VERSIONS= { - packaging.version.Version("3.9.3") #recalled + packaging.version.Version("3.9.3") #recalled upstream } here = pathlib.Path(__file__).resolve() @@ -63,7 +62,7 @@ def get_existing_scripts(name="CPython", pattern=r'^\d+\.\d+(?:(t?)(-\w+)|(.\d+( logger.error("Unable to parse existing version %s: %s", entry_name, e) -def _get_download_entries(url, pattern, session=None): +def _get_download_entries(url, pattern, session=None) -> typing.Generator[typing.Tuple[str,str],None,None]: if session is None: session = requests_html.HTMLSession() response = session.get(url) @@ -145,27 +144,27 @@ def adapt_script(version: packaging.version.Version, def add_version(version: packaging.version.Version, url: str, existing_versions: typing.MutableMapping[packaging.version.Version,typing.Any], + available_downloads_repo: typing.Mapping[packaging.version.Version,typing.Mapping[str,str]], session: requests_html.BaseSession = None): previous_version = pick_previous_version(version, existing_versions) is_prerelease_upgrade = previous_version.major==version.major\ and previous_version.minor==version.minor\ and previous_version.micro==version.micro - if is_prerelease_upgrade: - logger.info(f"Checking for a (pre)release for {version} newer than {previous_version}") - else: - logger.info(f"Adding {version} based on {previous_version}") - available_downloads = get_available_source_downloads(url, session) - latest_available_download_version = max(available_downloads.keys()) - if is_prerelease_upgrade: - if latest_available_download_version == previous_version: - logger.info("No newer download found") - return False - else: - logger.info(f"Adding {version} replacing {previous_version}") + logger.info(f"Adding {version} based on {previous_version}") - adapt_script(latest_available_download_version, - available_downloads[latest_available_download_version], + latest_available_download = available_downloads_repo.get(version, None) + if latest_available_download is None: + latest_available_download_version, latest_available_download = \ + get_latest_available_download(url, session) + # The only case when site dir is not equal to a download's version + # is for prereleases -- which we should have already clarified + # in clarify_available_versions_for_initial_releases + assert latest_available_download_version == version + del latest_available_download_version + + adapt_script(version, + latest_available_download, previous_version, is_prerelease_upgrade, session) @@ -177,6 +176,12 @@ def add_version(version: packaging.version.Version, return True +def get_latest_available_download(url, session): + available_downloads = get_available_source_downloads(url, session) + latest_available_download_version = max(available_downloads.keys()) + return (latest_available_download_version,available_downloads[latest_available_download_version]) + + def cleanup_prerelease_upgrade( is_prerelease_upgrade: bool, previous_version: packaging.version.Version, @@ -205,19 +210,44 @@ def handle_t_thunks(version, previous_version, is_prerelease_upgrade): def main(): args = parse_args() logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO) + cached_session=requests_html.HTMLSession() + available_downloads_repo={} existing_versions = dict(get_existing_scripts()) - available_versions = dict(get_available_versions(session=cached_session)) - # version triple to triple-ified spec to raw spec - versions_to_add = set(available_versions.keys()) - set(existing_versions.keys()) - versions_to_add = sorted({v for v in versions_to_add if v>=CUTOFF_VERSION and v not in EXCLUDED_VERSIONS}) - logger.info("Checking for new versions") + available_versions = {v:url for v,url in get_available_versions(session=cached_session) + if v>=CUTOFF_VERSION and v not in EXCLUDED_VERSIONS} + + clarify_available_versions_for_initial_releases( + available_versions, + existing_versions, + available_downloads_repo, + cached_session) + + + versions_to_add = sorted(set(available_versions.keys()) - set(existing_versions.keys())) + + logger.debug("Existing_versions:\n"+pprint.pformat(existing_versions)) logger.debug("Available_versions:\n"+pprint.pformat(available_versions)) logger.info("Versions to add:\n"+pprint.pformat(versions_to_add)) for version_to_add in versions_to_add: - add_version(version_to_add, available_versions[version_to_add], existing_versions, session=cached_session) + add_version(version_to_add, available_versions[version_to_add], existing_versions, available_downloads_repo, session=cached_session) + + +def clarify_available_versions_for_initial_releases(available_versions, existing_versions, available_downloads_repo, + session): + for v in [v for v in available_versions if v.micro == 0 and v not in existing_versions]: + logger.debug(f"Clarifying available version for {v}") + latest_available_download_version, latest_available_download = \ + get_latest_available_download(available_versions[v], session) + + available_downloads_repo[latest_available_download_version] = latest_available_download + + if latest_available_download_version != v: + logger.info(f"Adjusting available version {v} to {latest_available_download_version}") + available_versions[latest_available_download_version] = available_versions[v] + del available_versions[v] def parse_args(): From 55b43a55664d89695a975fea855e5eadc372079a Mon Sep 17 00:00:00 2001 From: Ivan Pozdeev Date: Sat, 15 Nov 2025 21:41:50 +0300 Subject: [PATCH 15/15] save newly generated scripts into existing_scripts --- plugins/python-build/scripts/add_cpython.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/python-build/scripts/add_cpython.py b/plugins/python-build/scripts/add_cpython.py index fe381e57..b662a2b9 100644 --- a/plugins/python-build/scripts/add_cpython.py +++ b/plugins/python-build/scripts/add_cpython.py @@ -108,8 +108,7 @@ def pick_previous_version(version: packaging.version.Version, def adapt_script(version: packaging.version.Version, extensions_urls: typing.Dict[str,typing.Tuple[str,str]], previous_version: packaging.version.Version, - is_prerelease_upgrade: bool, - session: requests_html.BaseSession = None) -> None: + session: requests_html.BaseSession = None) -> pathlib.Path: previous_version_path = out_dir.joinpath(str(previous_version)) with previous_version_path.open("r", encoding='utf-8') as f: script = f.readlines() @@ -141,6 +140,8 @@ def adapt_script(version: packaging.version.Version, result_path.write_text(result.getvalue(), encoding='utf-8') result.close() + return result_path + def add_version(version: packaging.version.Version, url: str, existing_versions: typing.MutableMapping[packaging.version.Version,typing.Any], @@ -163,11 +164,11 @@ def add_version(version: packaging.version.Version, assert latest_available_download_version == version del latest_available_download_version - adapt_script(version, + new_path = adapt_script(version, latest_available_download, previous_version, - is_prerelease_upgrade, session) + existing_versions[version]=new_path.name cleanup_prerelease_upgrade(is_prerelease_upgrade, previous_version, existing_versions)