|
|
@ -11,15 +11,18 @@ Also ignores sub-patch releases if that major.minor.patch already exists, |
|
|
but otherwise, takes the latest sub-patch release for given OS/arch. |
|
|
but otherwise, takes the latest sub-patch release for given OS/arch. |
|
|
Assumes all miniconda3 releases < 4.7 default to python 3.6, and anything else 3.7. |
|
|
Assumes all miniconda3 releases < 4.7 default to python 3.6, and anything else 3.7. |
|
|
""" |
|
|
""" |
|
|
|
|
|
import logging |
|
|
|
|
|
import re |
|
|
|
|
|
import string |
|
|
|
|
|
import sys |
|
|
import textwrap |
|
|
import textwrap |
|
|
from argparse import ArgumentParser |
|
|
from argparse import ArgumentParser |
|
|
from collections import defaultdict |
|
|
from collections import defaultdict |
|
|
|
|
|
from dataclasses import dataclass |
|
|
from enum import Enum |
|
|
from enum import Enum |
|
|
from functools import total_ordering |
|
|
from functools import total_ordering |
|
|
from pathlib import Path |
|
|
from pathlib import Path |
|
|
from typing import NamedTuple, List, Optional, DefaultDict, Dict |
|
|
from typing import NamedTuple, List, Optional, DefaultDict, Dict |
|
|
import logging |
|
|
|
|
|
import string |
|
|
|
|
|
|
|
|
|
|
|
import requests_html |
|
|
import requests_html |
|
|
|
|
|
|
|
|
@ -90,7 +93,7 @@ class Flavor(StrEnum): |
|
|
ANACONDA = "anaconda" |
|
|
ANACONDA = "anaconda" |
|
|
MINICONDA = "miniconda" |
|
|
MINICONDA = "miniconda" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TFlavor(StrEnum): |
|
|
class TFlavor(StrEnum): |
|
|
ANACONDA = "Anaconda" |
|
|
ANACONDA = "Anaconda" |
|
|
MINICONDA = "Miniconda" |
|
|
MINICONDA = "Miniconda" |
|
|
@ -102,21 +105,37 @@ class Suffix(StrEnum): |
|
|
NONE = "" |
|
|
NONE = "" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PyVersion(StrEnum): |
|
|
|
|
|
PY27 = "py27" |
|
|
|
|
|
PY36 = "py36" |
|
|
|
|
|
PY37 = "py37" |
|
|
|
|
|
PY38 = "py38" |
|
|
|
|
|
PY39 = "py39" |
|
|
|
|
|
PY310 = "py310" |
|
|
|
|
|
PY311 = "py311" |
|
|
|
|
|
|
|
|
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): |
|
|
def version(self): |
|
|
first, *others = self.value[2:] |
|
|
|
|
|
return f"{first}.{''.join(others)}" |
|
|
|
|
|
|
|
|
return f"{self.major}.{self.minor}" |
|
|
|
|
|
|
|
|
def version_info(self): |
|
|
def version_info(self): |
|
|
return tuple(int(n) for n in self.version().split(".")) |
|
|
|
|
|
|
|
|
return (self.major, self.minor) |
|
|
|
|
|
|
|
|
|
|
|
def __str__(self): |
|
|
|
|
|
return self.value |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@total_ordering |
|
|
@total_ordering |
|
|
@ -190,10 +209,15 @@ class CondaVersion(NamedTuple): |
|
|
# https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-python.html |
|
|
# https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-python.html |
|
|
if v < (4, 7): |
|
|
if v < (4, 7): |
|
|
return PyVersion.PY36 |
|
|
return PyVersion.PY36 |
|
|
else: |
|
|
|
|
|
|
|
|
if v < (4, 8): |
|
|
return PyVersion.PY37 |
|
|
return PyVersion.PY37 |
|
|
|
|
|
else: |
|
|
|
|
|
# since 4.8, Miniconda specifies versions explicitly in the file name |
|
|
|
|
|
return None |
|
|
if self.flavor == "anaconda": |
|
|
if self.flavor == "anaconda": |
|
|
# https://docs.anaconda.com/free/anaconda/reference/release-notes/ |
|
|
# https://docs.anaconda.com/free/anaconda/reference/release-notes/ |
|
|
|
|
|
if v >= (2024,6): |
|
|
|
|
|
return PyVersion.PY312 |
|
|
if v >= (2023,7): |
|
|
if v >= (2023,7): |
|
|
return PyVersion.PY311 |
|
|
return PyVersion.PY311 |
|
|
if v >= (2023,3): |
|
|
if v >= (2023,3): |
|
|
@ -247,7 +271,7 @@ class CondaSpec(NamedTuple): |
|
|
md5, |
|
|
md5, |
|
|
repo, |
|
|
repo, |
|
|
) |
|
|
) |
|
|
if py_version is None: |
|
|
|
|
|
|
|
|
if py_version is None and py_ver is None and ver != "latest": |
|
|
spec = spec.with_py_version(spec.version.default_py_version()) |
|
|
spec = spec.with_py_version(spec.version.default_py_version()) |
|
|
return spec |
|
|
return spec |
|
|
|
|
|
|
|
|
@ -296,8 +320,8 @@ def get_existing_condas(name): |
|
|
if v.version_str != "latest": |
|
|
if v.version_str != "latest": |
|
|
logger.debug("Found existing %(name)s version %(v)s", locals()) |
|
|
logger.debug("Found existing %(name)s version %(v)s", locals()) |
|
|
yield v |
|
|
yield v |
|
|
except ValueError: |
|
|
|
|
|
logger.error("Unable to parse existing version %s", entry_name) |
|
|
|
|
|
|
|
|
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_condas(name, repo): |
|
|
@ -348,19 +372,12 @@ if __name__ == "__main__": |
|
|
help="Do not write scripts, just report them to stdout", |
|
|
help="Do not write scripts, just report them to stdout", |
|
|
) |
|
|
) |
|
|
parser.add_argument( |
|
|
parser.add_argument( |
|
|
"-v", "--verbose", action="count", default=0, |
|
|
|
|
|
|
|
|
"-v", "--verbose", action="store_true", default=0, |
|
|
help="Increase verbosity of logging", |
|
|
help="Increase verbosity of logging", |
|
|
) |
|
|
) |
|
|
parsed = parser.parse_args() |
|
|
parsed = parser.parse_args() |
|
|
|
|
|
|
|
|
log_level = { |
|
|
|
|
|
0: logging.WARNING, |
|
|
|
|
|
1: logging.INFO, |
|
|
|
|
|
2: logging.DEBUG, |
|
|
|
|
|
}.get(parsed.verbose, logging.DEBUG) |
|
|
|
|
|
logging.basicConfig(level=log_level) |
|
|
|
|
|
if parsed.verbose < 3: |
|
|
|
|
|
logging.getLogger("requests").setLevel(logging.WARNING) |
|
|
|
|
|
|
|
|
logging.basicConfig(level=logging.DEBUG if parsed.verbose else logging.INFO) |
|
|
|
|
|
|
|
|
existing_versions = set() |
|
|
existing_versions = set() |
|
|
available_specs = set() |
|
|
available_specs = set() |
|
|
@ -377,7 +394,7 @@ if __name__ == "__main__": |
|
|
for s in sorted(available_specs, key=key_fn): |
|
|
for s in sorted(available_specs, key=key_fn): |
|
|
key = s.version |
|
|
key = s.version |
|
|
vv = key.version_str.info() |
|
|
vv = key.version_str.info() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
reason = None |
|
|
reason = None |
|
|
if key in existing_versions: |
|
|
if key in existing_versions: |
|
|
reason = "already exists" |
|
|
reason = "already exists" |
|
|
@ -385,7 +402,7 @@ if __name__ == "__main__": |
|
|
reason = "too old" |
|
|
reason = "too old" |
|
|
elif len(key.version_str.info()) >= 4 and "-" not in key.version_str: |
|
|
elif len(key.version_str.info()) >= 4 and "-" not in key.version_str: |
|
|
reason = "ignoring hotfix releases" |
|
|
reason = "ignoring hotfix releases" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if reason: |
|
|
if reason: |
|
|
logger.debug("Ignoring version %(s)s (%(reason)s)", locals()) |
|
|
logger.debug("Ignoring version %(s)s (%(reason)s)", locals()) |
|
|
continue |
|
|
continue |
|
|
|