diff --git a/relenv/build/__init__.py b/relenv/build/__init__.py index 9c66ac5c..080a4511 100644 --- a/relenv/build/__init__.py +++ b/relenv/build/__init__.py @@ -14,7 +14,7 @@ from types import FrameType, ModuleType from . import darwin, linux, windows -from .common import CHECK_VERSIONS_SUPPORT, builds +from .common import builds from ..common import DEFAULT_PYTHON, build_arch from ..pyversions import Version, python_versions @@ -105,12 +105,6 @@ def setup_parser( "has no chance of being succesful. " ), ) - build_subparser.add_argument( - "--check-versions", - default=False, - action="store_true", - help="Check for new version of python and it's depenencies, then exit.", - ) build_subparser.add_argument( "--no-pretty", default=False, @@ -176,18 +170,6 @@ def main(args: argparse.Namespace) -> None: build.recipies["python"]["download"].version = str(build_version) build.recipies["python"]["download"].checksum = pyversions[build_version] - if args.check_versions: - if not CHECK_VERSIONS_SUPPORT: - print( - "Check versions not supported. Please install the " - "packaging and looseversion python packages." - ) - sys.exit(2) - if not build.check_versions(): - sys.exit(1) - else: - sys.exit(0) - build.set_arch(args.arch) if build.build_arch != build.arch: print( diff --git a/relenv/build/common.py b/relenv/build/common.py index dde8e07b..4d71b211 100644 --- a/relenv/build/common.py +++ b/relenv/build/common.py @@ -22,7 +22,6 @@ import tempfile import time import tarfile -from html.parser import HTMLParser from types import ModuleType from typing import ( Any, @@ -38,7 +37,7 @@ cast, ) -from typing import TYPE_CHECKING, Protocol, TypedDict +from typing import TYPE_CHECKING, TypedDict if TYPE_CHECKING: from multiprocessing.synchronize import Event as SyncEvent @@ -59,7 +58,6 @@ get_triplet, runcmd, work_dirs, - fetch_url, Version, WorkDirs, ) @@ -68,14 +66,6 @@ PathLike = Union[str, os.PathLike[str]] - -CHECK_VERSIONS_SUPPORT = True -try: - from packaging.version import InvalidVersion, parse - from looseversion import LooseVersion -except ImportError: - CHECK_VERSIONS_SUPPORT = False - log = logging.getLogger(__name__) @@ -204,11 +194,14 @@ def print_ui( def verify_checksum(file: PathLike, checksum: Optional[str]) -> bool: """ - Verify the checksum of a files. + Verify the checksum of a file. + + Supports both SHA-1 (40 hex chars) and SHA-256 (64 hex chars) checksums. + The hash algorithm is auto-detected based on checksum length. :param file: The path to the file to check. :type file: str - :param checksum: The checksum to verify against + :param checksum: The checksum to verify against (SHA-1 or SHA-256) :type checksum: str :raises RelenvException: If the checksum verification failed @@ -219,11 +212,26 @@ def verify_checksum(file: PathLike, checksum: Optional[str]) -> bool: if checksum is None: log.error("Can't verify checksum because none was given") return False + + # Auto-detect hash type based on length + # SHA-1: 40 hex chars, SHA-256: 64 hex chars + if len(checksum) == 64: + hash_algo = hashlib.sha256() + hash_name = "sha256" + elif len(checksum) == 40: + hash_algo = hashlib.sha1() + hash_name = "sha1" + else: + raise RelenvException( + f"Invalid checksum length {len(checksum)}. Expected 40 (SHA-1) or 64 (SHA-256)" + ) + with open(file, "rb") as fp: - file_checksum = hashlib.sha1(fp.read()).hexdigest() + hash_algo.update(fp.read()) + file_checksum = hash_algo.hexdigest() if checksum != file_checksum: raise RelenvException( - f"sha1 checksum verification failed. expected={checksum} found={file_checksum}" + f"{hash_name} checksum verification failed. expected={checksum} found={file_checksum}" ) return True @@ -487,8 +495,6 @@ def patch_file(path: PathLike, old: str, new: str) -> None: :type path: str """ log.debug("Patching file: %s", path) - import re - with open(path, "r") as fp: content = fp.read() new_content = "" @@ -499,123 +505,52 @@ def patch_file(path: PathLike, old: str, new: str) -> None: fp.write(new_content) -def tarball_version(href: str) -> Optional[str]: - if href.endswith("tar.gz"): - try: - x = href.split("-", 1)[1][:-7] - if x != "latest": - return x - except IndexError: - return None - return None - - -def sqlite_version(href: str) -> Optional[str]: - if "releaselog" in href: - link = href.split("/")[1][:-5] - return "{:d}{:02d}{:02d}00".format(*[int(_) for _ in link.split("_")]) - return None - +def get_dependency_version(name: str, platform: str) -> Optional[Dict[str, str]]: + """ + Get dependency version and metadata from python-versions.json. -def github_version(href: str) -> Optional[str]: - if "tag/" in href: - return href.split("/v")[-1] - return None + Returns dict with keys: version, url, sha256, and any extra fields (e.g., sqliteversion) + Returns None if dependency not found. + :param name: Dependency name (openssl, sqlite, xz) + :param platform: Platform name (linux, darwin, win32) + :return: Dict with version, url, sha256, and extra fields, or None + """ + versions_file = MODULE_DIR / "python-versions.json" + if not versions_file.exists(): + return None -def krb_version(href: str) -> Optional[str]: - if re.match(r"\d\.\d\d/", href): - return href[:-1] - return None + import json + data = json.loads(versions_file.read_text()) + dependencies = data.get("dependencies", {}) -def python_version(href: str) -> Optional[str]: - if re.match(r"(\d+\.)+\d/", href): - return href[:-1] - return None + if name not in dependencies: + return None + # Get the latest version for this dependency that supports the platform + dep_versions = dependencies[name] + for version, info in sorted( + dep_versions.items(), + key=lambda x: [int(n) for n in x[0].split(".")], + reverse=True, + ): + if platform in info.get("platforms", []): + # Build result dict with version, url, sha256, and any extra fields + result = { + "version": version, + "url": info["url"], + "sha256": info.get("sha256", ""), + } + # Add any extra fields (like sqliteversion for SQLite) + for key, value in info.items(): + if key not in ["url", "sha256", "platforms"]: + result[key] = value + return result -def uuid_version(href: str) -> Optional[str]: - if "download" in href and "latest" not in href: - return href[:-16].rsplit("/")[-1].replace("libuuid-", "") return None -def parse_links(text: str) -> List[str]: - class HrefParser(HTMLParser): - def __init__(self) -> None: - super().__init__() - self.hrefs: List[str] = [] - - def handle_starttag( - self, tag: str, attrs: List[Tuple[str, Optional[str]]] - ) -> None: - if tag == "a": - link = dict(attrs).get("href") - if link: - self.hrefs.append(link) - - parser = HrefParser() - parser.feed(text) - return parser.hrefs - - -class Comparable(Protocol): - """Protocol capturing the comparison operations we rely on.""" - - def __lt__(self, other: Any) -> bool: - """Return True when self is ordered before *other*.""" - - def __gt__(self, other: Any) -> bool: - """Return True when self is ordered after *other*.""" - - -def check_files( - name: str, - location: str, - func: Optional[Callable[[str], Optional[str]]], - current: str, -) -> None: - fp = io.BytesIO() - fetch_url(location, fp) - fp.seek(0) - text = fp.read().decode() - loose = False - current_version: Comparable - try: - current_version = cast(Comparable, parse(current)) - except InvalidVersion: - current_version = LooseVersion(current) - loose = True - - versions: List[Comparable] = [] - if func is None: - return - for link in parse_links(text): - version = func(link) - if version: - if loose: - versions.append(LooseVersion(version)) - else: - try: - versions.append(cast(Comparable, parse(version))) - except InvalidVersion: - pass - versions.sort() - compare_versions(name, current_version, versions) - - -def compare_versions( - name: str, current: Comparable, versions: Sequence[Comparable] -) -> None: - for version in versions: - try: - if version > current: - print(f"Found new version of {name} {version} > {current}") - except TypeError: - print(f"Unable to compare versions {version}") - - class Download: """ A utility that holds information about content to be downloaded. @@ -644,8 +579,6 @@ def __init__( destination: PathLike = "", version: str = "", checksum: Optional[str] = None, - checkfunc: Optional[Callable[[str], Optional[str]]] = None, - checkurl: Optional[str] = None, ) -> None: self.name = name self.url_tpl = url @@ -656,8 +589,6 @@ def __init__( self._destination = pathlib.Path(destination) self.version = version self.checksum = checksum - self.checkfunc = checkfunc - self.checkurl = checkurl def copy(self) -> "Download": return Download( @@ -668,8 +599,6 @@ def copy(self) -> "Download": self.destination, self.version, self.checksum, - self.checkfunc, - self.checkurl, ) @property @@ -838,16 +767,6 @@ def __call__( sys.exit(1) return valid - def check_version(self) -> bool: - if self.checkfunc is None: - return True - if self.checkurl: - url = self.checkurl - else: - url = self.url.rsplit("/", 1)[0] - check_files(self.name, url, self.checkfunc, self.version) - return True - class Recipe(TypedDict): """Typed description of a build recipe entry.""" @@ -1503,16 +1422,6 @@ def __call__( if stream_handler is not None: log.removeHandler(stream_handler) - def check_versions(self) -> bool: - success = True - for step in list(self.recipies): - download = self.recipies[step]["download"] - if not download: - continue - if not download.check_version(): - success = False - return success - class Builds: """Collection of platform-specific builders.""" diff --git a/relenv/build/darwin.py b/relenv/build/darwin.py index e296e1f1..264c5632 100644 --- a/relenv/build/darwin.py +++ b/relenv/build/darwin.py @@ -10,7 +10,15 @@ from typing import IO, MutableMapping from ..common import DARWIN, MACOS_DEVELOPMENT_TARGET, arches -from .common import Dirs, build_openssl, build_sqlite, builds, finalize, runcmd +from .common import ( + Dirs, + build_openssl, + build_sqlite, + builds, + finalize, + get_dependency_version, + runcmd, +) ARCHES = arches[DARWIN] @@ -38,6 +46,54 @@ def populate_env(env: MutableMapping[str, str], dirs: Dirs) -> None: env["CFLAGS"] = " ".join(cflags).format(prefix=dirs.prefix) +def update_expat(dirs: Dirs, env: MutableMapping[str, str]) -> None: + """ + Update the bundled expat library to the latest version. + + Python ships with an older bundled expat. This function updates it + to the latest version for security and bug fixes. + """ + import pathlib + import shutil + import glob + import urllib.request + import tarfile + + # Get version from JSON + expat_info = get_dependency_version("expat", "darwin") + if not expat_info: + # No update needed, use bundled version + return + + version = expat_info["version"] + version_tag = version.replace(".", "_") + url = f"https://github.com/libexpat/libexpat/releases/download/R_{version_tag}/expat-{version}.tar.xz" + + expat_dir = pathlib.Path(dirs.source) / "Modules" / "expat" + if not expat_dir.exists(): + # No expat directory, skip + return + + # Download expat tarball + tmpbuild = pathlib.Path(dirs.tmpbuild) + tarball_path = tmpbuild / f"expat-{version}.tar.xz" + urllib.request.urlretrieve(url, str(tarball_path)) + + # Extract tarball + with tarfile.open(tarball_path) as tar: + tar.extractall(path=str(tmpbuild)) + + # Copy source files to Modules/expat/ + expat_source_dir = tmpbuild / f"expat-{version}" / "lib" + for source_file in ["*.h", "*.c"]: + for file_path in glob.glob(str(expat_source_dir / source_file)): + target_file = expat_dir / pathlib.Path(file_path).name + # Remove old file if it exists + if target_file.exists(): + target_file.unlink() + shutil.copy2(file_path, str(expat_dir)) + + def build_python(env: MutableMapping[str, str], dirs: Dirs, logfp: IO[str]) -> None: """ Run the commands to build Python. @@ -49,6 +105,9 @@ def build_python(env: MutableMapping[str, str], dirs: Dirs, logfp: IO[str]) -> N :param logfp: A handle for the log file :type logfp: file """ + # Update bundled expat to latest version + update_expat(dirs, env) + env["LDFLAGS"] = "-Wl,-rpath,{prefix}/lib {ldflags}".format( prefix=dirs.prefix, ldflags=env["LDFLAGS"] ) @@ -77,33 +136,66 @@ def build_python(env: MutableMapping[str, str], dirs: Dirs, logfp: IO[str]) -> N build = builds.add("darwin", populate_env=populate_env) +# Get dependency versions from JSON (with fallback to hardcoded values) +openssl_info = get_dependency_version("openssl", "darwin") +if openssl_info: + openssl_version = openssl_info["version"] + openssl_url = openssl_info["url"] + openssl_checksum = openssl_info["sha256"] +else: + openssl_version = "3.5.4" + openssl_url = "https://github.com/openssl/openssl/releases/download/openssl-{version}/openssl-{version}.tar.gz" + openssl_checksum = "b75daac8e10f189abe28a076ba5905d363e4801f" + build.add( "openssl", build_func=build_openssl, download={ - "url": "https://github.com/openssl/openssl/releases/download/openssl-{version}/openssl-{version}.tar.gz", - "version": "3.5.4", - "checksum": "b75daac8e10f189abe28a076ba5905d363e4801f", + "url": openssl_url, + "version": openssl_version, + "checksum": openssl_checksum, }, ) +# Get XZ version from JSON +xz_info = get_dependency_version("xz", "darwin") +if xz_info: + xz_version = xz_info["version"] + xz_url = xz_info["url"] + xz_checksum = xz_info["sha256"] +else: + xz_version = "5.8.1" + xz_url = "http://tukaani.org/xz/xz-{version}.tar.gz" + xz_checksum = "ed4d5589c4cfe84e1697bd02a9954b76af336931" + build.add( "XZ", download={ - "url": "http://tukaani.org/xz/xz-{version}.tar.gz", - "version": "5.8.1", - "checksum": "ed4d5589c4cfe84e1697bd02a9954b76af336931", + "url": xz_url, + "version": xz_version, + "checksum": xz_checksum, }, ) +# Get SQLite version from JSON +sqlite_info = get_dependency_version("sqlite", "darwin") +if sqlite_info: + sqlite_url = sqlite_info["url"] + sqlite_checksum = sqlite_info["sha256"] + sqlite_version_num = sqlite_info.get("sqliteversion", "3500400") +else: + sqlite_version_num = "3500400" + sqlite_url = "https://sqlite.org/2025/sqlite-autoconf-{version}.tar.gz" + sqlite_checksum = "145048005c777796dd8494aa1cfed304e8c34283" + build.add( name="SQLite", build_func=build_sqlite, download={ - "url": "https://sqlite.org/2025/sqlite-autoconf-{version}.tar.gz", + "url": sqlite_url, "fallback_url": "https://woz.io/relenv/dependencies/sqlite-autoconf-{version}.tar.gz", - "version": "3500400", - "checksum": "145048005c777796dd8494aa1cfed304e8c34283", + "version": sqlite_version_num, + "checksum": sqlite_checksum, }, ) diff --git a/relenv/build/linux.py b/relenv/build/linux.py index a237095f..8dadb3d2 100644 --- a/relenv/build/linux.py +++ b/relenv/build/linux.py @@ -20,13 +20,8 @@ build_sqlite, builds, finalize, - github_version, + get_dependency_version, runcmd, - sqlite_version, - tarball_version, - krb_version, - python_version, - uuid_version, ) from ..common import LINUX, Version, arches @@ -366,6 +361,55 @@ def build_krb(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: runcmd(["make", "install"], env=env, stderr=logfp, stdout=logfp) +def update_expat(dirs: Dirs, env: EnvMapping) -> None: + """ + Update the bundled expat library to the latest version. + + Python ships with an older bundled expat. This function updates it + to the latest version for security and bug fixes. + """ + from .common import get_dependency_version + import urllib.request + import tarfile + import glob + import pathlib + import shutil + + # Get version from JSON + expat_info = get_dependency_version("expat", "linux") + if not expat_info: + # No update needed, use bundled version + return + + version = expat_info["version"] + version_tag = version.replace(".", "_") + url = f"https://github.com/libexpat/libexpat/releases/download/R_{version_tag}/expat-{version}.tar.xz" + + expat_dir = pathlib.Path(dirs.source) / "Modules" / "expat" + if not expat_dir.exists(): + # No expat directory, skip + return + + # Download expat tarball + tmpbuild = pathlib.Path(dirs.tmpbuild) + tarball_path = tmpbuild / f"expat-{version}.tar.xz" + urllib.request.urlretrieve(url, str(tarball_path)) + + # Extract tarball + with tarfile.open(tarball_path) as tar: + tar.extractall(path=str(tmpbuild)) + + # Copy source files to Modules/expat/ + expat_source_dir = tmpbuild / f"expat-{version}" / "lib" + for source_file in ["*.h", "*.c"]: + for file_path in glob.glob(str(expat_source_dir / source_file)): + target_file = expat_dir / pathlib.Path(file_path).name + # Remove old file if it exists + if target_file.exists(): + target_file.unlink() + shutil.copy2(file_path, str(expat_dir)) + + def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: """ Run the commands to build Python. @@ -377,6 +421,9 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: :param logfp: A handle for the log file :type logfp: file """ + # Update bundled expat to latest version + update_expat(dirs, env) + ldflagopt = f"-Wl,--rpath={dirs.prefix}/lib" if ldflagopt not in env["LDFLAGS"]: env["LDFLAGS"] = f"{ldflagopt} {env['LDFLAGS']}" @@ -501,15 +548,25 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: build = builds.add("linux", populate_env=populate_env) +# Get dependency versions from JSON (with fallback to hardcoded values) +openssl_info = get_dependency_version("openssl", "linux") +if openssl_info: + openssl_version = openssl_info["version"] + openssl_url = openssl_info["url"] + openssl_checksum = openssl_info["sha256"] +else: + # Fallback to hardcoded values + openssl_version = "3.5.4" + openssl_url = "https://github.com/openssl/openssl/releases/download/openssl-{version}/openssl-{version}.tar.gz" + openssl_checksum = "b75daac8e10f189abe28a076ba5905d363e4801f" + build.add( "openssl", build_func=build_openssl, download={ - "url": "https://github.com/openssl/openssl/releases/download/openssl-{version}/openssl-{version}.tar.gz", - "version": "3.5.4", - "checksum": "b75daac8e10f189abe28a076ba5905d363e4801f", - "checkfunc": tarball_version, - "checkurl": "https://www.openssl.org/source/", + "url": openssl_url, + "version": openssl_version, + "checksum": openssl_checksum, }, ) @@ -521,147 +578,270 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: "url": "https://www.openssl.org/source/openssl-{version}.tar.gz", "version": "3.1.2", "checksum": "206036c21264e53f0196f715d81d905742e6245b", - "checkfunc": tarball_version, - "checkurl": "https://www.openssl.org/source/", }, ) +# Get libxcrypt version from JSON +libxcrypt_info = get_dependency_version("libxcrypt", "linux") +if libxcrypt_info: + libxcrypt_version = libxcrypt_info["version"] + libxcrypt_url = libxcrypt_info["url"] + libxcrypt_checksum = libxcrypt_info["sha256"] +else: + libxcrypt_version = "4.4.38" + libxcrypt_url = "https://github.com/besser82/libxcrypt/releases/download/v{version}/libxcrypt-{version}.tar.xz" + libxcrypt_checksum = "9aa2fa261be6144af492e9b6bfd03bfaa47f7159" + build.add( "libxcrypt", download={ - "url": "https://github.com/besser82/libxcrypt/releases/download/v{version}/libxcrypt-{version}.tar.xz", - "version": "4.4.38", - "checksum": "9aa2fa261be6144af492e9b6bfd03bfaa47f7159", - "checkfunc": github_version, - "checkurl": "https://github.com/besser82/libxcrypt/releases/", + "url": libxcrypt_url, + "version": libxcrypt_version, + "checksum": libxcrypt_checksum, }, ) +# Get XZ version from JSON +xz_info = get_dependency_version("xz", "linux") +if xz_info: + xz_version = xz_info["version"] + xz_url = xz_info["url"] + xz_checksum = xz_info["sha256"] +else: + # Fallback to hardcoded values + xz_version = "5.8.1" + xz_url = "http://tukaani.org/xz/xz-{version}.tar.gz" + xz_checksum = "ed4d5589c4cfe84e1697bd02a9954b76af336931" + build.add( "XZ", download={ - "url": "http://tukaani.org/xz/xz-{version}.tar.gz", - "version": "5.8.1", - "checksum": "ed4d5589c4cfe84e1697bd02a9954b76af336931", - "checkfunc": tarball_version, + "url": xz_url, + "version": xz_version, + "checksum": xz_checksum, }, ) +# Get SQLite version from JSON +sqlite_info = get_dependency_version("sqlite", "linux") +if sqlite_info: + sqlite_url = sqlite_info["url"] + sqlite_checksum = sqlite_info["sha256"] + # SQLite uses a special 7-digit version number + sqlite_version_num = sqlite_info.get("sqliteversion", "3500400") +else: + # Fallback to hardcoded values + sqlite_version_num = "3500400" + sqlite_url = "https://sqlite.org/2025/sqlite-autoconf-{version}.tar.gz" + sqlite_checksum = "145048005c777796dd8494aa1cfed304e8c34283" + build.add( name="SQLite", build_func=build_sqlite, download={ - "url": "https://sqlite.org/2025/sqlite-autoconf-{version}.tar.gz", - "version": "3500400", - "checksum": "145048005c777796dd8494aa1cfed304e8c34283", - "checkfunc": sqlite_version, - "checkurl": "https://sqlite.org/", + "url": sqlite_url, + "version": sqlite_version_num, + "checksum": sqlite_checksum, }, ) +# Get bzip2 version from JSON +bzip2_info = get_dependency_version("bzip2", "linux") +if bzip2_info: + bzip2_version = bzip2_info["version"] + bzip2_url = bzip2_info["url"] + bzip2_checksum = bzip2_info["sha256"] +else: + bzip2_version = "1.0.8" + bzip2_url = "https://sourceware.org/pub/bzip2/bzip2-{version}.tar.gz" + bzip2_checksum = "bf7badf7e248e0ecf465d33c2f5aeec774209227" + build.add( name="bzip2", build_func=build_bzip2, download={ - "url": "https://sourceware.org/pub/bzip2/bzip2-{version}.tar.gz", - "version": "1.0.8", - "checksum": "bf7badf7e248e0ecf465d33c2f5aeec774209227", - "checkfunc": tarball_version, + "url": bzip2_url, + "version": bzip2_version, + "checksum": bzip2_checksum, }, ) +# Get gdbm version from JSON +gdbm_info = get_dependency_version("gdbm", "linux") +if gdbm_info: + gdbm_version = gdbm_info["version"] + gdbm_url = gdbm_info["url"] + gdbm_checksum = gdbm_info["sha256"] +else: + gdbm_version = "1.26" + gdbm_url = "https://mirrors.ocf.berkeley.edu/gnu/gdbm/gdbm-{version}.tar.gz" + gdbm_checksum = "6cee3657de948e691e8df26509157be950cef4d4" + build.add( name="gdbm", build_func=build_gdbm, download={ - "url": "https://mirrors.ocf.berkeley.edu/gnu/gdbm/gdbm-{version}.tar.gz", - "version": "1.26", - "checksum": "6cee3657de948e691e8df26509157be950cef4d4", - "checkfunc": tarball_version, + "url": gdbm_url, + "version": gdbm_version, + "checksum": gdbm_checksum, }, ) +# Get ncurses version from JSON +ncurses_info = get_dependency_version("ncurses", "linux") +if ncurses_info: + ncurses_version = ncurses_info["version"] + ncurses_url = ncurses_info["url"] + ncurses_checksum = ncurses_info["sha256"] +else: + ncurses_version = "6.5" + ncurses_url = ( + "https://mirrors.ocf.berkeley.edu/gnu/ncurses/ncurses-{version}.tar.gz" + ) + ncurses_checksum = "cde3024ac3f9ef21eaed6f001476ea8fffcaa381" + build.add( name="ncurses", build_func=build_ncurses, download={ - "url": "https://mirrors.ocf.berkeley.edu/gnu/ncurses/ncurses-{version}.tar.gz", - "version": "6.5", - "checksum": "cde3024ac3f9ef21eaed6f001476ea8fffcaa381", - "checkfunc": tarball_version, + "url": ncurses_url, + "version": ncurses_version, + "checksum": ncurses_checksum, }, ) +# Get libffi version from JSON +libffi_info = get_dependency_version("libffi", "linux") +if libffi_info: + libffi_version = libffi_info["version"] + libffi_url = libffi_info["url"] + libffi_checksum = libffi_info["sha256"] +else: + libffi_version = "3.5.2" + libffi_url = "https://github.com/libffi/libffi/releases/download/v{version}/libffi-{version}.tar.gz" + libffi_checksum = "2bd35b135b0eeb5c631e02422c9dbe786ddb626a" + build.add( "libffi", build_libffi, download={ - "url": "https://github.com/libffi/libffi/releases/download/v{version}/libffi-{version}.tar.gz", - "version": "3.5.2", - "checksum": "2bd35b135b0eeb5c631e02422c9dbe786ddb626a", - "checkfunc": github_version, - "checkurl": "https://github.com/libffi/libffi/releases/", + "url": libffi_url, + "version": libffi_version, + "checksum": libffi_checksum, }, ) +# Get zlib version from JSON +zlib_info = get_dependency_version("zlib", "linux") +if zlib_info: + zlib_version = zlib_info["version"] + zlib_url = zlib_info["url"] + zlib_checksum = zlib_info["sha256"] +else: + zlib_version = "1.3.1" + zlib_url = "https://zlib.net/fossils/zlib-{version}.tar.gz" + zlib_checksum = "f535367b1a11e2f9ac3bec723fb007fbc0d189e5" + build.add( "zlib", build_zlib, download={ - "url": "https://zlib.net/fossils/zlib-{version}.tar.gz", - "version": "1.3.1", - "checksum": "f535367b1a11e2f9ac3bec723fb007fbc0d189e5", - "checkfunc": tarball_version, + "url": zlib_url, + "version": zlib_version, + "checksum": zlib_checksum, }, ) +# Get uuid version from JSON +uuid_info = get_dependency_version("uuid", "linux") +if uuid_info: + uuid_ver = uuid_info["version"] + uuid_url = uuid_info["url"] + uuid_checksum = uuid_info["sha256"] +else: + uuid_ver = "1.0.3" + uuid_url = "https://sourceforge.net/projects/libuuid/files/libuuid-{version}.tar.gz" + uuid_checksum = "46eaedb875ae6e63677b51ec583656199241d597" + build.add( "uuid", download={ - "url": "https://sourceforge.net/projects/libuuid/files/libuuid-{version}.tar.gz", - "version": "1.0.3", - "checksum": "46eaedb875ae6e63677b51ec583656199241d597", - "checkfunc": uuid_version, + "url": uuid_url, + "version": uuid_ver, + "checksum": uuid_checksum, }, ) +# Get krb5 version from JSON +krb5_info = get_dependency_version("krb5", "linux") +if krb5_info: + krb5_version = krb5_info["version"] + krb5_url = krb5_info["url"] + krb5_checksum = krb5_info["sha256"] +else: + krb5_version = "1.22" + krb5_url = "https://kerberos.org/dist/krb5/{version}/krb5-{version}.tar.gz" + krb5_checksum = "3ad930ab036a8dc3678356fbb9de9246567e7984" + build.add( "krb5", build_func=build_krb, wait_on=["openssl"], download={ - "url": "https://kerberos.org/dist/krb5/{version}/krb5-{version}.tar.gz", - "version": "1.22", - "checksum": "3ad930ab036a8dc3678356fbb9de9246567e7984", - "checkfunc": krb_version, - "checkurl": "https://kerberos.org/dist/krb5/", + "url": krb5_url, + "version": krb5_version, + "checksum": krb5_checksum, }, ) +# Get readline version from JSON +readline_info = get_dependency_version("readline", "linux") +if readline_info: + readline_version = readline_info["version"] + readline_url = readline_info["url"] + readline_checksum = readline_info["sha256"] +else: + readline_version = "8.3" + readline_url = ( + "https://mirrors.ocf.berkeley.edu/gnu/readline/readline-{version}.tar.gz" + ) + readline_checksum = "2c05ae9350b695f69d70b47f17f092611de2081f" + build.add( "readline", build_func=build_readline, wait_on=["ncurses"], download={ - "url": "https://mirrors.ocf.berkeley.edu/gnu/readline/readline-{version}.tar.gz", - "version": "8.3", - "checksum": "2c05ae9350b695f69d70b47f17f092611de2081f", - "checkfunc": tarball_version, + "url": readline_url, + "version": readline_version, + "checksum": readline_checksum, }, ) +# Get tirpc version from JSON +tirpc_info = get_dependency_version("tirpc", "linux") +if tirpc_info: + tirpc_version = tirpc_info["version"] + tirpc_url = tirpc_info["url"] + tirpc_checksum = tirpc_info["sha256"] +else: + tirpc_version = "1.3.4" + tirpc_url = ( + "https://sourceforge.net/projects/libtirpc/files/libtirpc-{version}.tar.bz2" + ) + tirpc_checksum = "63c800f81f823254d2706637bab551dec176b99b" + build.add( "tirpc", wait_on=[ "krb5", ], download={ - "url": "https://sourceforge.net/projects/libtirpc/files/libtirpc-{version}.tar.bz2", + "url": tirpc_url, # "url": "https://downloads.sourceforge.net/projects/libtirpc/files/libtirpc-{version}.tar.bz2", - "version": "1.3.4", - "checksum": "63c800f81f823254d2706637bab551dec176b99b", - "checkfunc": tarball_version, + "version": tirpc_version, + "checksum": tirpc_checksum, }, ) @@ -687,8 +867,6 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: "url": "https://www.python.org/ftp/python/{version}/Python-{version}.tar.xz", "version": build.version, "checksum": "d31d548cd2c5ca2ae713bebe346ba15e8406633a", - "checkfunc": python_version, - "checkurl": "https://www.python.org/ftp/python/", }, ) diff --git a/relenv/build/windows.py b/relenv/build/windows.py index 55d69c1c..c280ce56 100644 --- a/relenv/build/windows.py +++ b/relenv/build/windows.py @@ -23,6 +23,7 @@ create_archive, download_url, extract_archive, + get_dependency_version, install_runtime, patch_file, runcmd, @@ -101,9 +102,21 @@ def update_sqlite(dirs: Dirs, env: EnvMapping) -> None: """ Update the SQLITE library. """ - version = "3.50.4.0" - url = "https://sqlite.org/2025/sqlite-autoconf-3500400.tar.gz" - sha256 = "a3db587a1b92ee5ddac2f66b3edb41b26f9c867275782d46c3a088977d6a5b18" + # Try to get version from JSON + sqlite_info = get_dependency_version("sqlite", "win32") + if sqlite_info: + version = sqlite_info["version"] + url_template = sqlite_info["url"] + sha256 = sqlite_info["sha256"] + sqliteversion = sqlite_info.get("sqliteversion", "3500400") + # Format the URL with sqliteversion (the 7-digit version number) + url = url_template.format(version=sqliteversion) + else: + # Fallback to hardcoded values + version = "3.50.4.0" + url = "https://sqlite.org/2025/sqlite-autoconf-3500400.tar.gz" + sha256 = "a3db587a1b92ee5ddac2f66b3edb41b26f9c867275782d46c3a088977d6a5b18" + sqliteversion = "3500400" ref_loc = f"cpe:2.3:a:sqlite:sqlite:{version}:*:*:*:*:*:*:*" target_dir = dirs.source / "externals" / f"sqlite-{version}" target_dir.parent.mkdir(parents=True, exist_ok=True) @@ -111,7 +124,7 @@ def update_sqlite(dirs: Dirs, env: EnvMapping) -> None: update_props(dirs.source, r"sqlite-\d+.\d+.\d+.\d+", f"sqlite-{version}") get_externals_source(externals_dir=dirs.source / "externals", url=url) # # we need to fix the name of the extracted directory - extracted_dir = dirs.source / "externals" / "sqlite-autoconf-3500400" + extracted_dir = dirs.source / "externals" / f"sqlite-autoconf-{sqliteversion}" shutil.move(str(extracted_dir), str(target_dir)) # Update externals.spdx.json with the correct version, url, and hash # This became a thing in 3.12 @@ -133,9 +146,20 @@ def update_xz(dirs: Dirs, env: EnvMapping) -> None: """ Update the XZ library. """ - version = "5.6.2" - url = f"https://github.com/tukaani-project/xz/releases/download/v{version}/xz-{version}.tar.xz" - sha256 = "8bfd20c0e1d86f0402f2497cfa71c6ab62d4cd35fd704276e3140bfb71414519" + # Try to get version from JSON + # Note: Windows may use a different XZ version than Linux/Darwin due to MSBuild compatibility + xz_info = get_dependency_version("xz", "win32") + if xz_info: + version = xz_info["version"] + url_template = xz_info["url"] + sha256 = xz_info["sha256"] + url = url_template.format(version=version) + else: + # Fallback to hardcoded values + # Note: Using 5.6.2 for MSBuild compatibility (5.5.0+ removed MSBuild support) + version = "5.6.2" + url = f"https://github.com/tukaani-project/xz/releases/download/v{version}/xz-{version}.tar.xz" + sha256 = "8bfd20c0e1d86f0402f2497cfa71c6ab62d4cd35fd704276e3140bfb71414519" ref_loc = f"cpe:2.3:a:tukaani:xz:{version}:*:*:*:*:*:*:*" target_dir = dirs.source / "externals" / f"xz-{version}" target_dir.parent.mkdir(parents=True, exist_ok=True) @@ -171,8 +195,17 @@ def update_expat(dirs: Dirs, env: EnvMapping) -> None: """ # Patch /Modules/expat/refresh.sh. When the SBOM is created, refresh.sh # is scanned for the expat version, even though it doesn't run on Windows. - version = "2.7.3" - hash = "821ac9710d2c073eaf13e1b1895a9c9aa66c1157a99635c639fbff65cdbdd732" + + # Try to get version from JSON + expat_info = get_dependency_version("expat", "win32") + if expat_info: + version = expat_info["version"] + hash = expat_info["sha256"] + else: + # Fallback to hardcoded values + version = "2.7.3" + hash = "821ac9710d2c073eaf13e1b1895a9c9aa66c1157a99635c639fbff65cdbdd732" + url = f'https://github.com/libexpat/libexpat/releases/download/R_{version.replace(".", "_")}/expat-{version}.tar.xz' bash_refresh = dirs.source / "Modules" / "expat" / "refresh.sh" old = r'expected_libexpat_tag="R_\d+_\d+_\d"' diff --git a/relenv/python-versions.json b/relenv/python-versions.json index aed0e404..fd9edf56 100644 --- a/relenv/python-versions.json +++ b/relenv/python-versions.json @@ -1,183 +1,329 @@ { - "3.13.7": "dc08d8b8154ff9e132c1db5d089079136273dc90", - "3.13.6": "6e69138f7e2e95244f7780ba3d5664dda80551e7", - "3.13.5": "dbf3aed444cbb2221eabfb52688aa371423aa0ba", - "3.13.4": "c288ab7716f3633f8927a5fe75f46d65cb712415", - "3.13.3": "f26085cf12daef7b60b8a6fe93ef988b9a094aea", - "3.13.2": "e4949d999f28d6ad941e766b7dac09a74efbc912", - "3.13.1": "4b0c2a49a848c3c5d611416099636262a0b9090f", - "3.13.0": "0f71dce4a3251460985a944bbd1d1b7db1660a91", - "3.12.11": "603f20426ba4942552a38493bb987c9b832ee321", - "3.12.10": "7dbdb09971278d93d387f2e045ee04c83d9f7bfa", - "3.12.9": "465d8a664e63dc5aa1f0d90cd1d0000a970ee2fb", - "3.12.8": "8872c7a124c6970833e0bde4f25d6d7d61c6af6e", - "3.12.7": "5a760bbc42c67f1a0aef5bcf7c329348fb88448b", - "3.12.6": "6d2bbe1603b01764c541608938766233bf56f780", - "3.12.5": "d9b83c17a717e1cbd3ab6bd14cfe3e508e6d87b2", - "3.12.4": "c221421f3ba734daaf013dbdc7b48aa725cea18e", - "3.12.3": "3df73004a9b224d021fd397724e8bd4f9b6cc824", - "3.12.2": "040eac171c17062253042f7faafea830b03bf07b", - "3.12.1": "5b11c58ea58cd6b8e1943c7e9b5f6e0997ca3632", - "3.12.0": "bb2792439baa2014f11652f3ce44d354d0d59a76", - "3.11.13": "fec9a494efd3520f7efe6f822111f22249549d0a", - "3.11.12": "85370474d7d0268c46ba5cf0d1473e3d06c17dd6", - "3.11.11": "acf539109b024d3c5f1fc63d6e7f08cd294ba56d", - "3.11.10": "eb0ee5c84407445809a556592008cfc1867a39bc", - "3.11.9": "926cd6a577b2e8dcbb17671b30eda04019328ada", - "3.11.8": "a368aeed7a3325e47b55168452c356a8eb27ab50", - "3.11.7": "f2534d591121f3845388fbdd6a121b96dfe305a6", - "3.11.6": "932f9833ee6d70a530a21d7c12c490a42c8b1574", - "3.11.5": "b13ec58fa6ebf5b0f7178555c5506e135cb7d785", - "3.11.4": "413b3715d919a7b473281529ab91eeea5c82e632", - "3.11.3": "baea3ce79cf35e53b4155a5f700516abcd14f49d", - "3.11.2": "ae1c199ecb7a969588b15354e19e7b60cb65d1b9", - "3.11.1": "89ee31611b73dc0c32c178d15aa208734b462c5a", - "3.11.0": "474838615eab658fc15c87d4bee6bf85a02b424d", - "3.10.18": "2b59becca67037125c08a82519beccfdb98a48cc", - "3.10.17": "d31d548cd2c5ca2ae713bebe346ba15e8406633a", - "3.10.16": "401e6a504a956c8f0aab76c4f3ad9df601a83eb1", - "3.10.15": "f498fd8921e3c37e6aded9acb11ed23c8daa0bbe", - "3.10.14": "9103b4716dff30b40fd0239982f3a2d851143a46", - "3.10.13": "fa66c061cba1acee5e9fe69b7d22cca744a6ecda", - "3.10.12": "85e043a6cd30835bdf95e3db2d1b4b15e142d067", - "3.10.11": "53eddc7bf687c4678dc594b2dc74126b48a4fa29", - "3.10.10": "5d250cd5d492df838a4d5279df40a90ace720b0c", - "3.10.9": "a6ab44fa6c7dc31506ee262b2f6ad10735ffe750", - "3.10.8": "49ca7a5be7f13375e863442fbd9ead893ace3238", - "3.10.7": "e1d4c71059170ba841af310a04e0b4d7c38cb844", - "3.10.6": "b5a3c74b281ab2e8e56779bbb9aeead1d92fed02", - "3.10.5": "b80b9c13bb6ba5eb8762ca0d2b888c404582a405", - "3.10.4": "8f684863b92bf43936b16dbb867e392f10e8ffc7", - "3.10.3": "5cd4ef548b1649a9a4fd78f5b7c5a86acdbd5001", - "3.10.2": "e618946549cca1eb0d6d4cdf516003fec3975003", - "3.10.1": "1a534e99b95db0d30dacc8f10418cc5758b04df7", - "3.10.0": "c6114b411b7e6d26fc9887c11c0800d9625f1ade", - "3.9.23": "73d07237b70b19e4cd530bbc204cccd668ec05d4", - "3.9.22": "898d81887f0f36f2a71677b657d8400fd526fd55", - "3.9.21": "d968a953f19c6fc3bf54b5ded5c06852197ebddc", - "3.9.20": "52902dd820d2d41c47ef927ecebb24a96a51cc4b", - "3.9.19": "57d08ec0b329a78923b486abae906d4fa12fadb7", - "3.9.18": "abe4a20dcc11798495b17611ef9f8f33d6975722", - "3.9.17": "34a6d24cef87fbf22b943d4fe22100a9bf0b3782", - "3.9.16": "19acd6a341e4f2d7ff97c10c2eada258e9898624", - "3.9.15": "0ffa1114d627ddb4f61f3041880e43b478552883", - "3.9.14": "fa48bd60aee6abf2d41aafb273ebf9fb6b790458", - "3.9.13": "d57e5c8b94fe42e2b403e6eced02b25ed47ca8da", - "3.9.12": "8c2e3a45febf695cbb4d1f02dc1e9d84c5224e52", - "3.9.11": "10af2f3c3b5fb4f7247d43d176ffc8ef448b31c9", - "3.9.10": "936fc25ac4e1b482a0cefa82dd6092a0c6b575e6", - "3.9.9": "6274e5631c520d75bf1f0a046640fd3996fe99f0", - "3.9.8": "8113655a4341a1008fe7388cbdf24aa9c7c6ae20", - "3.9.7": "5208c1d1e0e859f42a9bdbb110efd0855ec4ecf1", - "3.9.6": "05826c93a178872958f6685094ee3514e53ba653", - "3.9.5": "edc80e5e33fc3d3fae53e6b95ae4ca9277809b9b", - "3.9.4": "cfaa95ec3a15994b04c9c7ef1df1319056427e8d", - "3.9.2": "110ca5bca7989f9558a54ee6762e6774a4b9644a", - "3.9.1": "77f4105846f6740297e50d7535a42c02d6b8e7db", - "3.9.0": "ff1fc8c37d5d4b09ec3bf0d84f3e5b97745c6704", - "3.8.20": "88832fd164f0a7d1d0f4677b06960bb5ff15ff1d", - "3.8.19": "eaadca9b5f89767b83c89266049042902681d029", - "3.8.18": "4c5b3bdbfd5e899972d3ee06375fd96fe664dfe3", - "3.8.17": "7bf662823ba44b56bbc1c4f3f802c0cdf3fbbfe5", - "3.8.16": "006869b0011174fc0ce695c454010c21637b09bf", - "3.8.15": "8fe5b42bc09df600d0cec750415b27f7802ae5e4", - "3.8.14": "dced5c6d2a402eb79dddf3346210db23990b1ad0", - "3.8.13": "fb46587353f092d91caeddb07f82bb66a5115468", - "3.8.12": "7643eccc15f5606bd0dc04affc7ea901e417165d", - "3.8.11": "1561060627fd171de19c53eb374cd92d2f297bff", - "3.8.10": "f6579351d42a81c77b55aa4ca8b1280b4f5b37f9", - "3.8.9": "ea40651135adc4126a60a45093d100722610f4de", - "3.8.8": "d7dd8ef51ebe7ddd8ec41e39a607ac26d1834a8f", - "3.8.7": "1b1525811ea4bcf237622e5f1751a4dfc429e3a3", - "3.8.6": "6ee446eaacf901a3305565bd6569e2de135168e3", - "3.8.5": "68d6c7f948801cc755905162f5ee7589595edee4", - "3.8.4": "996c56496f4ddd8f18f388a3bd584b74d2b43928", - "3.8.3": "3bafa40df1cd069c112761c388a9f2e94b5d33dd", - "3.8.2": "5ae54baf26628a7ed74206650a31192e6d5c6f93", - "3.8.1": "a48fd28a037c0bcd7b7fc4d914c023f584e910ed", - "3.8.0": "7720e0384558c598107cf046c48165fd7e1f5b2c", - "3.7.17": "7a08649e47214de75a6ecec91260286fdd94daf5", - "3.7.16": "0cc857e03dbc91971190d170df48e4a64ce8941a", - "3.7.15": "5ed83fdc6d4705090bd39d4b9f5424e37aac780d", - "3.7.14": "86d51dea42675ccfe4a9d8d41e87c33757e91c64", - "3.7.13": "2dfb44e36bfb9d93894fd9051952190441da47db", - "3.7.12": "ea7ed19e3a7cb3867e32c602e25da0b2689a3e31", - "3.7.11": "671e3fed4f3bb5a6663da0ae6691f0f8e9399427", - "3.7.10": "847305e6b25f83b80096314fdfdfa7a8cc07016e", - "3.7.9": "e1de02779a89a94000c0ed340ec126de25825f2f", - "3.7.8": "ecfc1d291ab35bb7cc3a352dd9451450266f5974", - "3.7.7": "7b6f9eec148c583a22a0666fe8eb5ec963ac57c4", - "3.7.6": "93e76be2d874b6ad0abd57c15bab8debc211226a", - "3.7.5": "860f88886809ae8bfc86afa462536811c347a2a1", - "3.7.4": "a862c5a58626fdad02d2047a57771ede2783fcef", - "3.7.3": "e3584650a06ae2765da0678176deae9d133f1b3d", - "3.7.2": "c3dc6928516bcb934cf4740461044c79c7c35494", - "3.7.1": "da290d6d24c68faeba502251cba111a1c8a5a9b2", - "3.7.0": "653cffa5b9f2a28150afe4705600d2e55d89b564", - "3.6.15": "fc7c36957c2cd228cc217bb5df4f580f1fdcd602", - "3.6.14": "980845d74f9ca6a57999ac90c2ddb1fdffb7933a", - "3.6.13": "4fa72f749446e907a5b80c0ae47ab03d890f14c8", - "3.6.12": "e6a28b1ab47f079a659e24a40e4c416f52828682", - "3.6.11": "0840e6b726446fccaef483dad84cce8fdc683077", - "3.6.10": "781072e4726ff0c6dc9370dbfb9dd781d87987dc", - "3.6.9": "3cd8b0e814b753fcce4fdf7edc823d8fb0da9208", - "3.6.8": "ee55acedef049268307633cbc9c7ff0610d1244f", - "3.6.7": "dd2b0a8bf9b9617c57a0070b53065286c2142994", - "3.6.6": "5731cf379838023fc8c55491b4068c4404d0e34f", - "3.6.5": "5a7a833a36f1006257d298787f4c38493c5d1689", - "3.6.4": "36a90695cda9298a0663e667c12909246c358851", - "3.6.3": "6c71b14bdbc4d8aa0cfd59d4b6dc356d46abfdf5", - "3.6.2": "4f92a045de9231b93dfbed50c66bb12cf03ac59a", - "3.6.1": "91d880a2a9fcfc6753cbfa132bf47a47e17e7b16", - "3.6.0": "18ebf7d726782967d967dc00e3aa08b334fbdd5c", - "3.5.10": "25c31d42fb8f8755de9358a53816ce4567b6bca9", - "3.5.9": "4b62a14d8821e5761ef6b76876f45b50b85caa95", - "3.5.8": "10d313aeb7f58e464528aa1b1f0104175841ca4d", - "3.5.7": "743044e357e96ed8e49d709b502f1a9b815be763", - "3.5.6": "05548da58ec75a7af316c4a4cb8fc667ac6ac8f9", - "3.5.5": "66c4cfc0f64b545ee5a7725f26a2fd834cdf1682", - "3.5.4": "4aacbd09ca6988255de84a98ab9e4630f584efba", - "3.5.3": "127121fdca11e735b3686e300d66f73aba663e93", - "3.5.2": "4843aabacec5bc0cdd3e1f778faa926e532794d2", - "3.5.1": "0186da436db76776196612b98bb9c2f76acfe90e", - "3.5.0": "871a06df9ab70984b7398ac53047fe125c757a70", - "3.4.10": "68fe143c56d438343d4142a4953d607124e85ca2", - "3.4.9": "83ea4018f6e5f1db87c4e54c8a48ba6a8350abd4", - "3.4.8": "65d62d3f62ade072a84eb64eca4490b940c73542", - "3.4.7": "7b05bf099f3f311ba568232d0d03d64e67da9908", - "3.4.6": "ef7dbec63d45760701534990511d686e3acbbe4f", - "3.4.5": "882e83e0286b253ee651aa3f9a5d27ebc46e6632", - "3.4.4": "0e4c9265a2ab0004ac51f0010d47c22ef4c1640c", - "3.4.3": "7ca5cd664598bea96eec105aa6453223bb6b4456", - "3.4.2": "0727d8a8498733baabe6f51632b9bab0cbaa9ada", - "3.4.1": "143e098efe7ee7bec8a4904ec4b322f28a067a03", - "3.4.0": "f54d7cf6af5dbd9bddbe31cf4772f39711381dbe", - "3.3.7": "efb00940efd558b9350a79fd9fd70551cdb35b20", - "3.3.6": "0a86ae9e877467a62faed7ece208c0d6899b0991", - "3.3.5": "6683b26dd2cfd23af852abfcf1aedf25bbd44839", - "3.3.4": "2c9586eeb4b6e45e9ebc28372c0856c709d9a522", - "3.3.3": "af4e75a34bd538c79b9871227c2e7f56569ac107", - "3.3.2": "87009d0c156c6e1354dfec5c98c328cae93950ad", - "3.3.1": "393d7302c48bc911cd7faa7fa9b5fbcb9919bddc", - "3.3.0": "833d73565e1b665f1878504081dc985a5a06e46a", - "3.2.6": "3fb8bf10e4df50629efd747adbc324e0084df9bb", - "3.2.5": "616516c707e81f6e498e83b50a2b0f41b9dd3fe1", - "3.2.4": "d5788113afaf9425d5c15f1a4b76f731553ec040", - "3.2.3": "3d607dbcfdf100dd659978195ccf3ade9d221823", - "3.2.2": "5e654dbd48476193ccdef4d604ed4f45b48c6769", - "3.2.1": "ab5cf4a4c21abe590dea87473a1dee6820699d79", - "3.2.0": "55a3a9d39f31563370d0c494373bb6d38e4d1a00", - "3.1.5": "3fa78edeefd892a50b5f41bab018b51ecad0b56f", - "3.1.4": "e5767a4fc92433816451de75c8721f2e1a81f6ea", - "3.1.3": "eadb89fa63194167c6a80d49617d79491b21857b", - "3.1.2": "130186b84e8bafce7f25dccdd6f9ab4929f5f474", - "3.1.1": "499d77bc67f739880663f3aeab67dd973344a3dd", - "3.1.0": "8590b685654367e3eba70dc00df7e45e88c21da4", - "3.0.1": "9cde3918c0449f59e90b2e71f46a734ee84ae81e", - "3.0.0": "c002b8ed67d9df8aaf9a391b3cfcd442dcff3334", - "3.13.8": "00cb0597d6559f79ca24e1abf902aed81e6a13b3", - "3.12.12": "dbe6dc34a132b1035c121583a9f37ba87458d0f5", - "3.11.14": "dd6254e3a92978cb506e6303d1bb0db4dfff5b7f", - "3.10.19": "a5606a32a67421dc62dd0f5b6d00d35c93bbdae4", - "3.9.24": "32438f848084bf03a25d8514c9a396582f66e7be" -} \ No newline at end of file + "python": { + "3.13.7": "dc08d8b8154ff9e132c1db5d089079136273dc90", + "3.13.6": "6e69138f7e2e95244f7780ba3d5664dda80551e7", + "3.13.5": "dbf3aed444cbb2221eabfb52688aa371423aa0ba", + "3.13.4": "c288ab7716f3633f8927a5fe75f46d65cb712415", + "3.13.3": "f26085cf12daef7b60b8a6fe93ef988b9a094aea", + "3.13.2": "e4949d999f28d6ad941e766b7dac09a74efbc912", + "3.13.1": "4b0c2a49a848c3c5d611416099636262a0b9090f", + "3.13.0": "0f71dce4a3251460985a944bbd1d1b7db1660a91", + "3.12.11": "603f20426ba4942552a38493bb987c9b832ee321", + "3.12.10": "7dbdb09971278d93d387f2e045ee04c83d9f7bfa", + "3.12.9": "465d8a664e63dc5aa1f0d90cd1d0000a970ee2fb", + "3.12.8": "8872c7a124c6970833e0bde4f25d6d7d61c6af6e", + "3.12.7": "5a760bbc42c67f1a0aef5bcf7c329348fb88448b", + "3.12.6": "6d2bbe1603b01764c541608938766233bf56f780", + "3.12.5": "d9b83c17a717e1cbd3ab6bd14cfe3e508e6d87b2", + "3.12.4": "c221421f3ba734daaf013dbdc7b48aa725cea18e", + "3.12.3": "3df73004a9b224d021fd397724e8bd4f9b6cc824", + "3.12.2": "040eac171c17062253042f7faafea830b03bf07b", + "3.12.1": "5b11c58ea58cd6b8e1943c7e9b5f6e0997ca3632", + "3.12.0": "bb2792439baa2014f11652f3ce44d354d0d59a76", + "3.11.13": "fec9a494efd3520f7efe6f822111f22249549d0a", + "3.11.12": "85370474d7d0268c46ba5cf0d1473e3d06c17dd6", + "3.11.11": "acf539109b024d3c5f1fc63d6e7f08cd294ba56d", + "3.11.10": "eb0ee5c84407445809a556592008cfc1867a39bc", + "3.11.9": "926cd6a577b2e8dcbb17671b30eda04019328ada", + "3.11.8": "a368aeed7a3325e47b55168452c356a8eb27ab50", + "3.11.7": "f2534d591121f3845388fbdd6a121b96dfe305a6", + "3.11.6": "932f9833ee6d70a530a21d7c12c490a42c8b1574", + "3.11.5": "b13ec58fa6ebf5b0f7178555c5506e135cb7d785", + "3.11.4": "413b3715d919a7b473281529ab91eeea5c82e632", + "3.11.3": "baea3ce79cf35e53b4155a5f700516abcd14f49d", + "3.11.2": "ae1c199ecb7a969588b15354e19e7b60cb65d1b9", + "3.11.1": "89ee31611b73dc0c32c178d15aa208734b462c5a", + "3.11.0": "474838615eab658fc15c87d4bee6bf85a02b424d", + "3.10.18": "2b59becca67037125c08a82519beccfdb98a48cc", + "3.10.17": "d31d548cd2c5ca2ae713bebe346ba15e8406633a", + "3.10.16": "401e6a504a956c8f0aab76c4f3ad9df601a83eb1", + "3.10.15": "f498fd8921e3c37e6aded9acb11ed23c8daa0bbe", + "3.10.14": "9103b4716dff30b40fd0239982f3a2d851143a46", + "3.10.13": "fa66c061cba1acee5e9fe69b7d22cca744a6ecda", + "3.10.12": "85e043a6cd30835bdf95e3db2d1b4b15e142d067", + "3.10.11": "53eddc7bf687c4678dc594b2dc74126b48a4fa29", + "3.10.10": "5d250cd5d492df838a4d5279df40a90ace720b0c", + "3.10.9": "a6ab44fa6c7dc31506ee262b2f6ad10735ffe750", + "3.10.8": "49ca7a5be7f13375e863442fbd9ead893ace3238", + "3.10.7": "e1d4c71059170ba841af310a04e0b4d7c38cb844", + "3.10.6": "b5a3c74b281ab2e8e56779bbb9aeead1d92fed02", + "3.10.5": "b80b9c13bb6ba5eb8762ca0d2b888c404582a405", + "3.10.4": "8f684863b92bf43936b16dbb867e392f10e8ffc7", + "3.10.3": "5cd4ef548b1649a9a4fd78f5b7c5a86acdbd5001", + "3.10.2": "e618946549cca1eb0d6d4cdf516003fec3975003", + "3.10.1": "1a534e99b95db0d30dacc8f10418cc5758b04df7", + "3.10.0": "c6114b411b7e6d26fc9887c11c0800d9625f1ade", + "3.9.23": "73d07237b70b19e4cd530bbc204cccd668ec05d4", + "3.9.22": "898d81887f0f36f2a71677b657d8400fd526fd55", + "3.9.21": "d968a953f19c6fc3bf54b5ded5c06852197ebddc", + "3.9.20": "52902dd820d2d41c47ef927ecebb24a96a51cc4b", + "3.9.19": "57d08ec0b329a78923b486abae906d4fa12fadb7", + "3.9.18": "abe4a20dcc11798495b17611ef9f8f33d6975722", + "3.9.17": "34a6d24cef87fbf22b943d4fe22100a9bf0b3782", + "3.9.16": "19acd6a341e4f2d7ff97c10c2eada258e9898624", + "3.9.15": "0ffa1114d627ddb4f61f3041880e43b478552883", + "3.9.14": "fa48bd60aee6abf2d41aafb273ebf9fb6b790458", + "3.9.13": "d57e5c8b94fe42e2b403e6eced02b25ed47ca8da", + "3.9.12": "8c2e3a45febf695cbb4d1f02dc1e9d84c5224e52", + "3.9.11": "10af2f3c3b5fb4f7247d43d176ffc8ef448b31c9", + "3.9.10": "936fc25ac4e1b482a0cefa82dd6092a0c6b575e6", + "3.9.9": "6274e5631c520d75bf1f0a046640fd3996fe99f0", + "3.9.8": "8113655a4341a1008fe7388cbdf24aa9c7c6ae20", + "3.9.7": "5208c1d1e0e859f42a9bdbb110efd0855ec4ecf1", + "3.9.6": "05826c93a178872958f6685094ee3514e53ba653", + "3.9.5": "edc80e5e33fc3d3fae53e6b95ae4ca9277809b9b", + "3.9.4": "cfaa95ec3a15994b04c9c7ef1df1319056427e8d", + "3.9.2": "110ca5bca7989f9558a54ee6762e6774a4b9644a", + "3.9.1": "77f4105846f6740297e50d7535a42c02d6b8e7db", + "3.9.0": "ff1fc8c37d5d4b09ec3bf0d84f3e5b97745c6704", + "3.8.20": "88832fd164f0a7d1d0f4677b06960bb5ff15ff1d", + "3.8.19": "eaadca9b5f89767b83c89266049042902681d029", + "3.8.18": "4c5b3bdbfd5e899972d3ee06375fd96fe664dfe3", + "3.8.17": "7bf662823ba44b56bbc1c4f3f802c0cdf3fbbfe5", + "3.8.16": "006869b0011174fc0ce695c454010c21637b09bf", + "3.8.15": "8fe5b42bc09df600d0cec750415b27f7802ae5e4", + "3.8.14": "dced5c6d2a402eb79dddf3346210db23990b1ad0", + "3.8.13": "fb46587353f092d91caeddb07f82bb66a5115468", + "3.8.12": "7643eccc15f5606bd0dc04affc7ea901e417165d", + "3.8.11": "1561060627fd171de19c53eb374cd92d2f297bff", + "3.8.10": "f6579351d42a81c77b55aa4ca8b1280b4f5b37f9", + "3.8.9": "ea40651135adc4126a60a45093d100722610f4de", + "3.8.8": "d7dd8ef51ebe7ddd8ec41e39a607ac26d1834a8f", + "3.8.7": "1b1525811ea4bcf237622e5f1751a4dfc429e3a3", + "3.8.6": "6ee446eaacf901a3305565bd6569e2de135168e3", + "3.8.5": "68d6c7f948801cc755905162f5ee7589595edee4", + "3.8.4": "996c56496f4ddd8f18f388a3bd584b74d2b43928", + "3.8.3": "3bafa40df1cd069c112761c388a9f2e94b5d33dd", + "3.8.2": "5ae54baf26628a7ed74206650a31192e6d5c6f93", + "3.8.1": "a48fd28a037c0bcd7b7fc4d914c023f584e910ed", + "3.8.0": "7720e0384558c598107cf046c48165fd7e1f5b2c", + "3.7.17": "7a08649e47214de75a6ecec91260286fdd94daf5", + "3.7.16": "0cc857e03dbc91971190d170df48e4a64ce8941a", + "3.7.15": "5ed83fdc6d4705090bd39d4b9f5424e37aac780d", + "3.7.14": "86d51dea42675ccfe4a9d8d41e87c33757e91c64", + "3.7.13": "2dfb44e36bfb9d93894fd9051952190441da47db", + "3.7.12": "ea7ed19e3a7cb3867e32c602e25da0b2689a3e31", + "3.7.11": "671e3fed4f3bb5a6663da0ae6691f0f8e9399427", + "3.7.10": "847305e6b25f83b80096314fdfdfa7a8cc07016e", + "3.7.9": "e1de02779a89a94000c0ed340ec126de25825f2f", + "3.7.8": "ecfc1d291ab35bb7cc3a352dd9451450266f5974", + "3.7.7": "7b6f9eec148c583a22a0666fe8eb5ec963ac57c4", + "3.7.6": "93e76be2d874b6ad0abd57c15bab8debc211226a", + "3.7.5": "860f88886809ae8bfc86afa462536811c347a2a1", + "3.7.4": "a862c5a58626fdad02d2047a57771ede2783fcef", + "3.7.3": "e3584650a06ae2765da0678176deae9d133f1b3d", + "3.7.2": "c3dc6928516bcb934cf4740461044c79c7c35494", + "3.7.1": "da290d6d24c68faeba502251cba111a1c8a5a9b2", + "3.7.0": "653cffa5b9f2a28150afe4705600d2e55d89b564", + "3.6.15": "fc7c36957c2cd228cc217bb5df4f580f1fdcd602", + "3.6.14": "980845d74f9ca6a57999ac90c2ddb1fdffb7933a", + "3.6.13": "4fa72f749446e907a5b80c0ae47ab03d890f14c8", + "3.6.12": "e6a28b1ab47f079a659e24a40e4c416f52828682", + "3.6.11": "0840e6b726446fccaef483dad84cce8fdc683077", + "3.6.10": "781072e4726ff0c6dc9370dbfb9dd781d87987dc", + "3.6.9": "3cd8b0e814b753fcce4fdf7edc823d8fb0da9208", + "3.6.8": "ee55acedef049268307633cbc9c7ff0610d1244f", + "3.6.7": "dd2b0a8bf9b9617c57a0070b53065286c2142994", + "3.6.6": "5731cf379838023fc8c55491b4068c4404d0e34f", + "3.6.5": "5a7a833a36f1006257d298787f4c38493c5d1689", + "3.6.4": "36a90695cda9298a0663e667c12909246c358851", + "3.6.3": "6c71b14bdbc4d8aa0cfd59d4b6dc356d46abfdf5", + "3.6.2": "4f92a045de9231b93dfbed50c66bb12cf03ac59a", + "3.6.1": "91d880a2a9fcfc6753cbfa132bf47a47e17e7b16", + "3.6.0": "18ebf7d726782967d967dc00e3aa08b334fbdd5c", + "3.5.10": "25c31d42fb8f8755de9358a53816ce4567b6bca9", + "3.5.9": "4b62a14d8821e5761ef6b76876f45b50b85caa95", + "3.5.8": "10d313aeb7f58e464528aa1b1f0104175841ca4d", + "3.5.7": "743044e357e96ed8e49d709b502f1a9b815be763", + "3.5.6": "05548da58ec75a7af316c4a4cb8fc667ac6ac8f9", + "3.5.5": "66c4cfc0f64b545ee5a7725f26a2fd834cdf1682", + "3.5.4": "4aacbd09ca6988255de84a98ab9e4630f584efba", + "3.5.3": "127121fdca11e735b3686e300d66f73aba663e93", + "3.5.2": "4843aabacec5bc0cdd3e1f778faa926e532794d2", + "3.5.1": "0186da436db76776196612b98bb9c2f76acfe90e", + "3.5.0": "871a06df9ab70984b7398ac53047fe125c757a70", + "3.4.10": "68fe143c56d438343d4142a4953d607124e85ca2", + "3.4.9": "83ea4018f6e5f1db87c4e54c8a48ba6a8350abd4", + "3.4.8": "65d62d3f62ade072a84eb64eca4490b940c73542", + "3.4.7": "7b05bf099f3f311ba568232d0d03d64e67da9908", + "3.4.6": "ef7dbec63d45760701534990511d686e3acbbe4f", + "3.4.5": "882e83e0286b253ee651aa3f9a5d27ebc46e6632", + "3.4.4": "0e4c9265a2ab0004ac51f0010d47c22ef4c1640c", + "3.4.3": "7ca5cd664598bea96eec105aa6453223bb6b4456", + "3.4.2": "0727d8a8498733baabe6f51632b9bab0cbaa9ada", + "3.4.1": "143e098efe7ee7bec8a4904ec4b322f28a067a03", + "3.4.0": "f54d7cf6af5dbd9bddbe31cf4772f39711381dbe", + "3.3.7": "efb00940efd558b9350a79fd9fd70551cdb35b20", + "3.3.6": "0a86ae9e877467a62faed7ece208c0d6899b0991", + "3.3.5": "6683b26dd2cfd23af852abfcf1aedf25bbd44839", + "3.3.4": "2c9586eeb4b6e45e9ebc28372c0856c709d9a522", + "3.3.3": "af4e75a34bd538c79b9871227c2e7f56569ac107", + "3.3.2": "87009d0c156c6e1354dfec5c98c328cae93950ad", + "3.3.1": "393d7302c48bc911cd7faa7fa9b5fbcb9919bddc", + "3.3.0": "833d73565e1b665f1878504081dc985a5a06e46a", + "3.2.6": "3fb8bf10e4df50629efd747adbc324e0084df9bb", + "3.2.5": "616516c707e81f6e498e83b50a2b0f41b9dd3fe1", + "3.2.4": "d5788113afaf9425d5c15f1a4b76f731553ec040", + "3.2.3": "3d607dbcfdf100dd659978195ccf3ade9d221823", + "3.2.2": "5e654dbd48476193ccdef4d604ed4f45b48c6769", + "3.2.1": "ab5cf4a4c21abe590dea87473a1dee6820699d79", + "3.2.0": "55a3a9d39f31563370d0c494373bb6d38e4d1a00", + "3.1.5": "3fa78edeefd892a50b5f41bab018b51ecad0b56f", + "3.1.4": "e5767a4fc92433816451de75c8721f2e1a81f6ea", + "3.1.3": "eadb89fa63194167c6a80d49617d79491b21857b", + "3.1.2": "130186b84e8bafce7f25dccdd6f9ab4929f5f474", + "3.1.1": "499d77bc67f739880663f3aeab67dd973344a3dd", + "3.1.0": "8590b685654367e3eba70dc00df7e45e88c21da4", + "3.0.1": "9cde3918c0449f59e90b2e71f46a734ee84ae81e", + "3.0.0": "c002b8ed67d9df8aaf9a391b3cfcd442dcff3334", + "3.13.8": "00cb0597d6559f79ca24e1abf902aed81e6a13b3", + "3.12.12": "dbe6dc34a132b1035c121583a9f37ba87458d0f5", + "3.11.14": "dd6254e3a92978cb506e6303d1bb0db4dfff5b7f", + "3.10.19": "a5606a32a67421dc62dd0f5b6d00d35c93bbdae4", + "3.9.24": "32438f848084bf03a25d8514c9a396582f66e7be" + }, + "dependencies": { + "openssl": { + "3.5.4": { + "url": "https://github.com/openssl/openssl/releases/download/openssl-{version}/openssl-{version}.tar.gz", + "sha256": "", + "platforms": [ + "linux", + "darwin" + ] + }, + "3.6.0": { + "url": "https://github.com/openssl/openssl/releases/download/openssl-{version}/openssl-{version}.tar.gz", + "sha256": "b6a5f44b7eb69e3fa35dbf15524405b44837a481d43d81daddde3ff21fcbb8e9", + "platforms": [ + "linux", + "darwin" + ] + } + }, + "sqlite": { + "3.50.4.0": { + "url": "https://sqlite.org/2025/sqlite-autoconf-{version}.tar.gz", + "sha256": "a3db587a1b92ee5ddac2f66b3edb41b26f9c867275782d46c3a088977d6a5b18", + "sqliteversion": "3500400", + "platforms": [ + "linux", + "darwin", + "win32" + ] + } + }, + "xz": { + "5.8.1": { + "url": "http://tukaani.org/xz/xz-{version}.tar.gz", + "sha256": "507825b599356c10dca1cd720c9d0d0c9d5400b9de300af00e4d1ea150795543", + "platforms": [ + "linux", + "darwin" + ] + } + }, + "libffi": { + "3.5.2": { + "url": "https://github.com/libffi/libffi/releases/download/v{version}/libffi-{version}.tar.gz", + "sha256": "f3a3082a23b37c293a4fcd1053147b371f2ff91fa7ea1b2a52e335676bac82dc", + "platforms": [ + "linux" + ] + } + }, + "zlib": { + "1.3.1": { + "url": "https://zlib.net/fossils/zlib-{version}.tar.gz", + "sha256": "9a93b2b7dfdac77ceba5a558a580e74667dd6fede4585b91eefb60f03b72df23", + "platforms": [ + "linux" + ] + } + }, + "ncurses": { + "6.5": { + "url": "https://mirrors.ocf.berkeley.edu/gnu/ncurses/ncurses-{version}.tar.gz", + "sha256": "136d91bc269a9a5785e5f9e980bc76ab57428f604ce3e5a5a90cebc767971cc6", + "platforms": [ + "linux" + ] + } + }, + "readline": { + "8.3": { + "url": "https://mirrors.ocf.berkeley.edu/gnu/readline/readline-{version}.tar.gz", + "sha256": "fe5383204467828cd495ee8d1d3c037a7eba1389c22bc6a041f627976f9061cc", + "platforms": [ + "linux" + ] + } + }, + "gdbm": { + "1.26": { + "url": "https://mirrors.ocf.berkeley.edu/gnu/gdbm/gdbm-{version}.tar.gz", + "sha256": "6a24504a14de4a744103dcb936be976df6fbe88ccff26065e54c1c47946f4a5e", + "platforms": [ + "linux" + ] + } + }, + "libxcrypt": { + "4.4.38": { + "url": "https://github.com/besser82/libxcrypt/releases/download/v{version}/libxcrypt-{version}.tar.xz", + "sha256": "80304b9c306ea799327f01d9a7549bdb28317789182631f1b54f4511b4206dd6", + "platforms": [ + "linux" + ] + } + }, + "krb5": { + "1.22": { + "url": "https://kerberos.org/dist/krb5/{version}/krb5-{version}.tar.gz", + "sha256": "652be617b4647f3c5dcac21547d47c7097101aad4e306f1778fb48e17b220ba3", + "platforms": [ + "linux" + ] + } + }, + "bzip2": { + "1.0.8": { + "url": "https://sourceware.org/pub/bzip2/bzip2-{version}.tar.gz", + "sha256": "ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269", + "platforms": [ + "linux", + "darwin" + ] + } + }, + "uuid": { + "1.0.3": { + "url": "https://sourceforge.net/projects/libuuid/files/libuuid-{version}.tar.gz", + "sha256": "46af3275291091009ad7f1b899de3d0cea0252737550e7919d17237997db5644", + "platforms": [ + "linux" + ] + } + }, + "tirpc": { + "1.3.7": { + "url": "https://sourceforge.net/projects/libtirpc/files/libtirpc-{version}.tar.bz2", + "sha256": "b47d3ac19d3549e54a05d0019a6c400674da716123858cfdb6d3bdd70a66c702", + "platforms": [ + "linux" + ] + } + }, + "expat": { + "2.7.3": { + "url": "https://github.com/libexpat/libexpat/releases/download/R_2_7_3/expat-2.7.3.tar.xz", + "sha256": "71df8f40706a7bb0a80a5367079ea75d91da4f8c65c58ec59bcdfbf7decdab9f", + "platforms": [ + "linux", + "darwin", + "win32" + ] + } + } + } +} diff --git a/relenv/pyversions.py b/relenv/pyversions.py index 930e4c0b..e191afcc 100644 --- a/relenv/pyversions.py +++ b/relenv/pyversions.py @@ -230,6 +230,625 @@ def _main() -> None: vfile.write_text(json.dumps(out, indent=1)) +def sha256_digest(file: str | os.PathLike[str]) -> str: + """ + SHA-256 digest of file. + """ + hsh = hashlib.sha256() + with open(file, "rb") as fp: + hsh.update(fp.read()) + return hsh.hexdigest() + + +def detect_openssl_versions() -> list[str]: + """ + Detect available OpenSSL versions from GitHub releases. + """ + url = "https://github.com/openssl/openssl/tags" + content = fetch_url_content(url) + # Find tags like openssl-3.5.4 + pattern = r'openssl-(\d+\.\d+\.\d+)"' + matches = re.findall(pattern, content) + # Deduplicate and sort + versions = sorted( + set(matches), key=lambda v: [int(x) for x in v.split(".")], reverse=True + ) + return versions + + +def detect_sqlite_versions() -> list[tuple[str, str]]: + """ + Detect available SQLite versions from sqlite.org. + + Returns list of (version, sqliteversion) tuples. + """ + url = "https://sqlite.org/download.html" + content = fetch_url_content(url) + # Find sqlite-autoconf-NNNNNNN.tar.gz + pattern = r"sqlite-autoconf-(\d{7})\.tar\.gz" + matches = re.findall(pattern, content) + # Convert to version format + versions = [] + for sqlite_ver in set(matches): + # SQLite version format: 3XXYYZZ where XX=minor, YY=patch, ZZ=subpatch + if len(sqlite_ver) == 7 and sqlite_ver[0] == "3": + major = 3 + minor = int(sqlite_ver[1:3]) + patch = int(sqlite_ver[3:5]) + subpatch = int(sqlite_ver[5:7]) + version = f"{major}.{minor}.{patch}.{subpatch}" + versions.append((version, sqlite_ver)) + return sorted( + versions, key=lambda x: [int(n) for n in x[0].split(".")], reverse=True + ) + + +def detect_xz_versions() -> list[str]: + """ + Detect available XZ versions from tukaani.org. + """ + url = "https://tukaani.org/xz/" + content = fetch_url_content(url) + # Find xz-X.Y.Z.tar.gz + pattern = r"xz-(\d+\.\d+\.\d+)\.tar\.gz" + matches = re.findall(pattern, content) + # Deduplicate and sort + versions = sorted( + set(matches), key=lambda v: [int(x) for x in v.split(".")], reverse=True + ) + return versions + + +def detect_libffi_versions() -> list[str]: + """Detect available libffi versions from GitHub releases.""" + url = "https://github.com/libffi/libffi/tags" + content = fetch_url_content(url) + pattern = r'v(\d+\.\d+\.\d+)"' + matches = re.findall(pattern, content) + return sorted( + set(matches), key=lambda v: [int(x) for x in v.split(".")], reverse=True + ) + + +def detect_zlib_versions() -> list[str]: + """Detect available zlib versions from zlib.net.""" + url = "https://zlib.net/" + content = fetch_url_content(url) + pattern = r"zlib-(\d+\.\d+\.\d+)\.tar\.gz" + matches = re.findall(pattern, content) + return sorted( + set(matches), key=lambda v: [int(x) for x in v.split(".")], reverse=True + ) + + +def detect_bzip2_versions() -> list[str]: + """Detect available bzip2 versions from sourceware.org.""" + url = "https://sourceware.org/pub/bzip2/" + content = fetch_url_content(url) + pattern = r"bzip2-(\d+\.\d+\.\d+)\.tar\.gz" + matches = re.findall(pattern, content) + return sorted( + set(matches), key=lambda v: [int(x) for x in v.split(".")], reverse=True + ) + + +def detect_ncurses_versions() -> list[str]: + """Detect available ncurses versions from GNU mirrors.""" + url = "https://mirrors.ocf.berkeley.edu/gnu/ncurses/" + content = fetch_url_content(url) + pattern = r"ncurses-(\d+\.\d+)\.tar\.gz" + matches = re.findall(pattern, content) + return sorted( + set(matches), key=lambda v: [int(x) for x in v.split(".")], reverse=True + ) + + +def detect_readline_versions() -> list[str]: + """Detect available readline versions from GNU mirrors.""" + url = "https://mirrors.ocf.berkeley.edu/gnu/readline/" + content = fetch_url_content(url) + pattern = r"readline-(\d+\.\d+)\.tar\.gz" + matches = re.findall(pattern, content) + return sorted( + set(matches), key=lambda v: [int(x) for x in v.split(".")], reverse=True + ) + + +def detect_gdbm_versions() -> list[str]: + """Detect available gdbm versions from GNU mirrors.""" + url = "https://mirrors.ocf.berkeley.edu/gnu/gdbm/" + content = fetch_url_content(url) + pattern = r"gdbm-(\d+\.\d+)\.tar\.gz" + matches = re.findall(pattern, content) + return sorted( + set(matches), key=lambda v: [int(x) for x in v.split(".")], reverse=True + ) + + +def detect_libxcrypt_versions() -> list[str]: + """Detect available libxcrypt versions from GitHub releases.""" + url = "https://github.com/besser82/libxcrypt/tags" + content = fetch_url_content(url) + pattern = r'v(\d+\.\d+\.\d+)"' + matches = re.findall(pattern, content) + return sorted( + set(matches), key=lambda v: [int(x) for x in v.split(".")], reverse=True + ) + + +def detect_krb5_versions() -> list[str]: + """Detect available krb5 versions from kerberos.org.""" + url = "https://kerberos.org/dist/krb5/" + content = fetch_url_content(url) + # krb5 versions are like 1.22/ + pattern = r"(\d+\.\d+)/" + matches = re.findall(pattern, content) + return sorted( + set(matches), key=lambda v: [int(x) for x in v.split(".")], reverse=True + ) + + +def detect_uuid_versions() -> list[str]: + """Detect available libuuid versions from SourceForge.""" + url = "https://sourceforge.net/projects/libuuid/files/" + content = fetch_url_content(url) + pattern = r"libuuid-(\d+\.\d+\.\d+)\.tar\.gz" + matches = re.findall(pattern, content) + return sorted( + set(matches), key=lambda v: [int(x) for x in v.split(".")], reverse=True + ) + + +def detect_tirpc_versions() -> list[str]: + """Detect available libtirpc versions from SourceForge.""" + url = "https://sourceforge.net/projects/libtirpc/files/libtirpc/" + content = fetch_url_content(url) + pattern = r"(\d+\.\d+\.\d+)/" + matches = re.findall(pattern, content) + return sorted( + set(matches), key=lambda v: [int(x) for x in v.split(".")], reverse=True + ) + + +def detect_expat_versions() -> list[str]: + """Detect available expat versions from GitHub releases.""" + url = "https://github.com/libexpat/libexpat/tags" + content = fetch_url_content(url) + # Expat versions are tagged like R_2_7_3 + pattern = r'R_(\d+)_(\d+)_(\d+)"' + matches = re.findall(pattern, content) + # Convert R_2_7_3 to 2.7.3 + versions = [f"{m[0]}.{m[1]}.{m[2]}" for m in matches] + return sorted( + set(versions), key=lambda v: [int(x) for x in v.split(".")], reverse=True + ) + + +def update_dependency_versions( + path: pathlib.Path, deps_to_update: list[str] | None = None +) -> None: + """ + Update dependency versions in python-versions.json. + + Downloads tarballs, computes SHA-256, and updates the JSON file. + + :param path: Path to python-versions.json + :param deps_to_update: List of dependencies to update (openssl, sqlite, xz), or None for all + """ + cwd = os.getcwd() + + # Read existing data + if path.exists(): + all_data = json.loads(path.read_text()) + if "python" in all_data: + pydata = all_data["python"] + dependencies = all_data.get("dependencies", {}) + else: + # Old format + pydata = all_data + dependencies = {} + else: + pydata = {} + dependencies = {} + + # Determine which dependencies to update + if deps_to_update is None: + # By default, update commonly-changed dependencies + # Full list: openssl, sqlite, xz, libffi, zlib, bzip2, ncurses, + # readline, gdbm, libxcrypt, krb5, uuid, tirpc, expat + deps_to_update = [ + "openssl", + "sqlite", + "xz", + "libffi", + "zlib", + "ncurses", + "readline", + "gdbm", + "libxcrypt", + "krb5", + "bzip2", + "uuid", + "tirpc", + "expat", + ] + + # Update OpenSSL + if "openssl" in deps_to_update: + print("Checking OpenSSL versions...") + openssl_versions = detect_openssl_versions() + if openssl_versions: + latest = openssl_versions[0] + print(f"Latest OpenSSL: {latest}") + if "openssl" not in dependencies: + dependencies["openssl"] = {} + if latest not in dependencies["openssl"]: + url = f"https://github.com/openssl/openssl/releases/download/openssl-{latest}/openssl-{latest}.tar.gz" + print(f"Downloading {url}...") + download_path = download_url(url, cwd) + checksum = sha256_digest(download_path) + print(f"SHA-256: {checksum}") + url_template = ( + "https://github.com/openssl/openssl/releases/download/" + "openssl-{version}/openssl-{version}.tar.gz" + ) + dependencies["openssl"][latest] = { + "url": url_template, + "sha256": checksum, + "platforms": ["linux", "darwin"], + } + # Clean up download + os.remove(download_path) + + # Update SQLite + if "sqlite" in deps_to_update: + print("Checking SQLite versions...") + sqlite_versions = detect_sqlite_versions() + if sqlite_versions: + latest_version, latest_sqliteversion = sqlite_versions[0] + print( + f"Latest SQLite: {latest_version} (sqlite version {latest_sqliteversion})" + ) + if "sqlite" not in dependencies: + dependencies["sqlite"] = {} + if latest_version not in dependencies["sqlite"]: + # SQLite URLs include year, try current year + import datetime + + year = datetime.datetime.now().year + url = f"https://sqlite.org/{year}/sqlite-autoconf-{latest_sqliteversion}.tar.gz" + print(f"Downloading {url}...") + try: + download_path = download_url(url, cwd) + checksum = sha256_digest(download_path) + print(f"SHA-256: {checksum}") + # Store URL with actual year and {version} placeholder (not {sqliteversion}) + # The build scripts pass sqliteversion value as "version" parameter + dependencies["sqlite"][latest_version] = { + "url": f"https://sqlite.org/{year}/sqlite-autoconf-{{version}}.tar.gz", + "sha256": checksum, + "sqliteversion": latest_sqliteversion, + "platforms": ["linux", "darwin", "win32"], + } + # Clean up download + os.remove(download_path) + except Exception as e: + print(f"Failed to download SQLite: {e}") + + # Update XZ + if "xz" in deps_to_update: + print("Checking XZ versions...") + xz_versions = detect_xz_versions() + if xz_versions: + latest = xz_versions[0] + print(f"Latest XZ: {latest}") + if "xz" not in dependencies: + dependencies["xz"] = {} + if latest not in dependencies["xz"]: + url = f"http://tukaani.org/xz/xz-{latest}.tar.gz" + print(f"Downloading {url}...") + download_path = download_url(url, cwd) + checksum = sha256_digest(download_path) + print(f"SHA-256: {checksum}") + dependencies["xz"][latest] = { + "url": "http://tukaani.org/xz/xz-{version}.tar.gz", + "sha256": checksum, + "platforms": ["linux", "darwin", "win32"], + } + # Clean up download + os.remove(download_path) + + # Update libffi + if "libffi" in deps_to_update: + print("Checking libffi versions...") + libffi_versions = detect_libffi_versions() + if libffi_versions: + latest = libffi_versions[0] + print(f"Latest libffi: {latest}") + if "libffi" not in dependencies: + dependencies["libffi"] = {} + if latest not in dependencies["libffi"]: + url = f"https://github.com/libffi/libffi/releases/download/v{latest}/libffi-{latest}.tar.gz" + print(f"Downloading {url}...") + try: + download_path = download_url(url, cwd) + checksum = sha256_digest(download_path) + print(f"SHA-256: {checksum}") + dependencies["libffi"][latest] = { + "url": "https://github.com/libffi/libffi/releases/download/v{version}/libffi-{version}.tar.gz", + "sha256": checksum, + "platforms": ["linux"], + } + os.remove(download_path) + except Exception as e: + print(f"Failed to download libffi: {e}") + + # Update zlib + if "zlib" in deps_to_update: + print("Checking zlib versions...") + zlib_versions = detect_zlib_versions() + if zlib_versions: + latest = zlib_versions[0] + print(f"Latest zlib: {latest}") + if "zlib" not in dependencies: + dependencies["zlib"] = {} + if latest not in dependencies["zlib"]: + url = f"https://zlib.net/fossils/zlib-{latest}.tar.gz" + print(f"Downloading {url}...") + try: + download_path = download_url(url, cwd) + checksum = sha256_digest(download_path) + print(f"SHA-256: {checksum}") + dependencies["zlib"][latest] = { + "url": "https://zlib.net/fossils/zlib-{version}.tar.gz", + "sha256": checksum, + "platforms": ["linux"], + } + os.remove(download_path) + except Exception as e: + print(f"Failed to download zlib: {e}") + + # Update ncurses + if "ncurses" in deps_to_update: + print("Checking ncurses versions...") + ncurses_versions = detect_ncurses_versions() + if ncurses_versions: + latest = ncurses_versions[0] + print(f"Latest ncurses: {latest}") + if "ncurses" not in dependencies: + dependencies["ncurses"] = {} + if latest not in dependencies["ncurses"]: + url = f"https://mirrors.ocf.berkeley.edu/gnu/ncurses/ncurses-{latest}.tar.gz" + print(f"Downloading {url}...") + try: + download_path = download_url(url, cwd) + checksum = sha256_digest(download_path) + print(f"SHA-256: {checksum}") + dependencies["ncurses"][latest] = { + "url": "https://mirrors.ocf.berkeley.edu/gnu/ncurses/ncurses-{version}.tar.gz", + "sha256": checksum, + "platforms": ["linux"], + } + os.remove(download_path) + except Exception as e: + print(f"Failed to download ncurses: {e}") + + # Update readline + if "readline" in deps_to_update: + print("Checking readline versions...") + readline_versions = detect_readline_versions() + if readline_versions: + latest = readline_versions[0] + print(f"Latest readline: {latest}") + if "readline" not in dependencies: + dependencies["readline"] = {} + if latest not in dependencies["readline"]: + url = f"https://mirrors.ocf.berkeley.edu/gnu/readline/readline-{latest}.tar.gz" + print(f"Downloading {url}...") + try: + download_path = download_url(url, cwd) + checksum = sha256_digest(download_path) + print(f"SHA-256: {checksum}") + dependencies["readline"][latest] = { + "url": "https://mirrors.ocf.berkeley.edu/gnu/readline/readline-{version}.tar.gz", + "sha256": checksum, + "platforms": ["linux"], + } + os.remove(download_path) + except Exception as e: + print(f"Failed to download readline: {e}") + + # Update gdbm + if "gdbm" in deps_to_update: + print("Checking gdbm versions...") + gdbm_versions = detect_gdbm_versions() + if gdbm_versions: + latest = gdbm_versions[0] + print(f"Latest gdbm: {latest}") + if "gdbm" not in dependencies: + dependencies["gdbm"] = {} + if latest not in dependencies["gdbm"]: + url = f"https://mirrors.ocf.berkeley.edu/gnu/gdbm/gdbm-{latest}.tar.gz" + print(f"Downloading {url}...") + try: + download_path = download_url(url, cwd) + checksum = sha256_digest(download_path) + print(f"SHA-256: {checksum}") + dependencies["gdbm"][latest] = { + "url": "https://mirrors.ocf.berkeley.edu/gnu/gdbm/gdbm-{version}.tar.gz", + "sha256": checksum, + "platforms": ["linux"], + } + os.remove(download_path) + except Exception as e: + print(f"Failed to download gdbm: {e}") + + # Update libxcrypt + if "libxcrypt" in deps_to_update: + print("Checking libxcrypt versions...") + libxcrypt_versions = detect_libxcrypt_versions() + if libxcrypt_versions: + latest = libxcrypt_versions[0] + print(f"Latest libxcrypt: {latest}") + if "libxcrypt" not in dependencies: + dependencies["libxcrypt"] = {} + if latest not in dependencies["libxcrypt"]: + url = f"https://github.com/besser82/libxcrypt/releases/download/v{latest}/libxcrypt-{latest}.tar.xz" + print(f"Downloading {url}...") + try: + download_path = download_url(url, cwd) + checksum = sha256_digest(download_path) + print(f"SHA-256: {checksum}") + dependencies["libxcrypt"][latest] = { + "url": ( + "https://github.com/besser82/libxcrypt/releases/" + "download/v{version}/libxcrypt-{version}.tar.xz" + ), + "sha256": checksum, + "platforms": ["linux"], + } + os.remove(download_path) + except Exception as e: + print(f"Failed to download libxcrypt: {e}") + + # Update krb5 + if "krb5" in deps_to_update: + print("Checking krb5 versions...") + krb5_versions = detect_krb5_versions() + if krb5_versions: + latest = krb5_versions[0] + print(f"Latest krb5: {latest}") + if "krb5" not in dependencies: + dependencies["krb5"] = {} + if latest not in dependencies["krb5"]: + url = f"https://kerberos.org/dist/krb5/{latest}/krb5-{latest}.tar.gz" + print(f"Downloading {url}...") + try: + download_path = download_url(url, cwd) + checksum = sha256_digest(download_path) + print(f"SHA-256: {checksum}") + dependencies["krb5"][latest] = { + "url": "https://kerberos.org/dist/krb5/{version}/krb5-{version}.tar.gz", + "sha256": checksum, + "platforms": ["linux"], + } + os.remove(download_path) + except Exception as e: + print(f"Failed to download krb5: {e}") + + # Update bzip2 + if "bzip2" in deps_to_update: + print("Checking bzip2 versions...") + bzip2_versions = detect_bzip2_versions() + if bzip2_versions: + latest = bzip2_versions[0] + print(f"Latest bzip2: {latest}") + if "bzip2" not in dependencies: + dependencies["bzip2"] = {} + if latest not in dependencies["bzip2"]: + url = f"https://sourceware.org/pub/bzip2/bzip2-{latest}.tar.gz" + print(f"Downloading {url}...") + try: + download_path = download_url(url, cwd) + checksum = sha256_digest(download_path) + print(f"SHA-256: {checksum}") + dependencies["bzip2"][latest] = { + "url": "https://sourceware.org/pub/bzip2/bzip2-{version}.tar.gz", + "sha256": checksum, + "platforms": ["linux", "darwin"], + } + os.remove(download_path) + except Exception as e: + print(f"Failed to download bzip2: {e}") + + # Update uuid + if "uuid" in deps_to_update: + print("Checking uuid versions...") + uuid_versions = detect_uuid_versions() + if uuid_versions: + latest = uuid_versions[0] + print(f"Latest uuid: {latest}") + if "uuid" not in dependencies: + dependencies["uuid"] = {} + if latest not in dependencies["uuid"]: + url = f"https://sourceforge.net/projects/libuuid/files/libuuid-{latest}.tar.gz" + print(f"Downloading {url}...") + try: + download_path = download_url(url, cwd) + checksum = sha256_digest(download_path) + print(f"SHA-256: {checksum}") + dependencies["uuid"][latest] = { + "url": "https://sourceforge.net/projects/libuuid/files/libuuid-{version}.tar.gz", + "sha256": checksum, + "platforms": ["linux"], + } + os.remove(download_path) + except Exception as e: + print(f"Failed to download uuid: {e}") + + # Update tirpc + if "tirpc" in deps_to_update: + print("Checking tirpc versions...") + tirpc_versions = detect_tirpc_versions() + if tirpc_versions: + latest = tirpc_versions[0] + print(f"Latest tirpc: {latest}") + if "tirpc" not in dependencies: + dependencies["tirpc"] = {} + if latest not in dependencies["tirpc"]: + url = f"https://sourceforge.net/projects/libtirpc/files/libtirpc-{latest}.tar.bz2" + print(f"Downloading {url}...") + try: + download_path = download_url(url, cwd) + checksum = sha256_digest(download_path) + print(f"SHA-256: {checksum}") + dependencies["tirpc"][latest] = { + "url": "https://sourceforge.net/projects/libtirpc/files/libtirpc-{version}.tar.bz2", + "sha256": checksum, + "platforms": ["linux"], + } + os.remove(download_path) + except Exception as e: + print(f"Failed to download tirpc: {e}") + + # Update expat + if "expat" in deps_to_update: + print("Checking expat versions...") + expat_versions = detect_expat_versions() + if expat_versions: + latest = expat_versions[0] + print(f"Latest expat: {latest}") + if "expat" not in dependencies: + dependencies["expat"] = {} + if latest not in dependencies["expat"]: + # Expat uses R_X_Y_Z format for releases + version_tag = latest.replace(".", "_") + url = f"https://github.com/libexpat/libexpat/releases/download/R_{version_tag}/expat-{latest}.tar.xz" + print(f"Downloading {url}...") + try: + download_path = download_url(url, cwd) + checksum = sha256_digest(download_path) + print(f"SHA-256: {checksum}") + # Store URL template with placeholder for version + # Build scripts will construct actual URL dynamically from version + dependencies["expat"][latest] = { + "url": ( + f"https://github.com/libexpat/libexpat/releases/" + f"download/R_{version_tag}/expat-{{version}}.tar.xz" + ), + "sha256": checksum, + "platforms": ["linux", "darwin", "win32"], + } + os.remove(download_path) + except Exception as e: + print(f"Failed to download expat: {e}") + + # Write updated data + all_data = {"python": pydata, "dependencies": dependencies} + path.write_text(json.dumps(all_data, indent=1)) + print(f"Updated {path}") + + def create_pyversions(path: pathlib.Path) -> None: """ Create python-versions.json file. @@ -242,15 +861,24 @@ def create_pyversions(path: pathlib.Path) -> None: versions = [_ for _ in parsed_versions if _.major >= 3] if path.exists(): - data: dict[str, str] = json.loads(path.read_text()) + all_data = json.loads(path.read_text()) + # Handle both old format (flat dict) and new format (nested) + if "python" in all_data: + pydata = all_data["python"] + dependencies = all_data.get("dependencies", {}) + else: + # Old format - convert to new + pydata = all_data + dependencies = {} else: - data = {} + pydata = {} + dependencies = {} for version in versions: if version >= Version("3.14"): continue - if str(version) in data: + if str(version) in pydata: continue if version <= Version("3.2") and version.micro == 0: @@ -266,14 +894,17 @@ def create_pyversions(path: pathlib.Path) -> None: verified = verify_signature(download_path, sig_path) if verified: print(f"Version {version} has digest {digest(download_path)}") - data[str(version)] = digest(download_path) + pydata[str(version)] = digest(download_path) else: raise Exception("Signature failed to verify: {url}") - path.write_text(json.dumps(data, indent=1)) + # Write in new structured format + all_data = {"python": pydata, "dependencies": dependencies} + path.write_text(json.dumps(all_data, indent=1)) - # path.write_text(json.dumps({"versions": [str(_) for _ in versions]})) - path.write_text(json.dumps(data, indent=1)) + # Final write in new structured format + all_data = {"python": pydata, "dependencies": dependencies} + path.write_text(json.dumps(all_data, indent=1)) def python_versions( @@ -302,7 +933,13 @@ def python_versions( readfrom = packaged else: raise RuntimeError("No versions file found") - pyversions = json.loads(readfrom.read_text()) + data = json.loads(readfrom.read_text()) + # Handle both old format (flat dict) and new format (nested with "python" key) + pyversions = ( + data.get("python", data) + if isinstance(data, dict) and "python" in data + else data + ) versions = [Version(_) for _ in pyversions] if minor: mv = Version(minor) @@ -344,12 +981,132 @@ def setup_parser( type=str, help="The python version [default: %(default)s]", ) + subparser.add_argument( + "--check-deps", + default=False, + action="store_true", + help="Check for new dependency versions", + ) + subparser.add_argument( + "--update-deps", + default=False, + action="store_true", + help="Update dependency versions (downloads and computes checksums)", + ) def main(args: argparse.Namespace) -> None: """ Versions utility main method. """ + packaged = pathlib.Path(__file__).parent / "python-versions.json" + + # Handle dependency operations + if args.check_deps: + print("Checking for new dependency versions...\n") + + # Load current versions from JSON + with open(packaged) as f: + data = json.load(f) + + current_deps = data.get("dependencies", {}) + updates_available = [] + up_to_date = [] + + # Detect terminal capabilities for fancy vs ASCII output + use_unicode = True + if sys.platform == "win32": + # Check if we're in a modern terminal that supports Unicode + import os + + # Windows Terminal and modern PowerShell support Unicode + wt_session = os.environ.get("WT_SESSION") + term_program = os.environ.get("TERM_PROGRAM") + if not wt_session and not term_program: + # Likely cmd.exe or old PowerShell, use ASCII + use_unicode = False + + if use_unicode: + ok_symbol = "✓" + update_symbol = "⚠" + new_symbol = "✗" + arrow = "→" + else: + ok_symbol = "[OK] " + update_symbol = "[UPDATE]" + new_symbol = "[NEW] " + arrow = "->" + + # Check each dependency + checks = [ + ("openssl", "OpenSSL", detect_openssl_versions), + ("sqlite", "SQLite", detect_sqlite_versions), + ("xz", "XZ", detect_xz_versions), + ("libffi", "libffi", detect_libffi_versions), + ("zlib", "zlib", detect_zlib_versions), + ("ncurses", "ncurses", detect_ncurses_versions), + ("readline", "readline", detect_readline_versions), + ("gdbm", "gdbm", detect_gdbm_versions), + ("libxcrypt", "libxcrypt", detect_libxcrypt_versions), + ("krb5", "krb5", detect_krb5_versions), + ("bzip2", "bzip2", detect_bzip2_versions), + ("uuid", "uuid", detect_uuid_versions), + ("tirpc", "tirpc", detect_tirpc_versions), + ("expat", "expat", detect_expat_versions), + ] + + for dep_key, dep_name, detect_func in checks: + detected = detect_func() + if not detected: + continue + + # Handle SQLite's tuple return + if dep_key == "sqlite": + latest_version = detected[0][0] # type: ignore[index] + else: + latest_version = detected[0] # type: ignore[index] + + # Get current version from JSON + current_version = None + if dep_key in current_deps: + versions = sorted(current_deps[dep_key].keys(), reverse=True) + if versions: + current_version = versions[0] + + # Compare versions + if current_version == latest_version: + print( + f"{ok_symbol} {dep_name:12} {current_version:15} " f"(up-to-date)" + ) + up_to_date.append(dep_name) + elif current_version: + print( + f"{update_symbol} {dep_name:12} {current_version:15} " + f"{arrow} {latest_version} (update available)" + ) + updates_available.append((dep_name, current_version, latest_version)) + else: + print( + f"{new_symbol} {dep_name:12} {'(not tracked)':15} " + f"{arrow} {latest_version}" + ) + updates_available.append((dep_name, None, latest_version)) + + # Summary + print(f"\n{'=' * 60}") + print(f"Summary: {len(up_to_date)} up-to-date, ", end="") + print(f"{len(updates_available)} updates available") + + if updates_available: + print("\nTo update dependencies, run:") + print(" python3 -m relenv versions --update-deps") + + sys.exit(0) + + if args.update_deps: + update_dependency_versions(packaged) + sys.exit(0) + if args.update: python_versions(create=True) if args.list: diff --git a/tests/test_build.py b/tests/test_build.py index 9d717005..ab190a86 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -5,7 +5,7 @@ import pytest -from relenv.build.common import Builder, verify_checksum +from relenv.build.common import Builder, get_dependency_version, verify_checksum from relenv.common import DATA_DIR, RelenvException, toolchain_root_dir # mypy: ignore-errors @@ -86,3 +86,65 @@ def test_verify_checksum(fake_download: pathlib.Path, fake_download_md5: str) -> def test_verify_checksum_failed(fake_download: pathlib.Path) -> None: pytest.raises(RelenvException, verify_checksum, fake_download, "no") + + +def test_get_dependency_version_openssl_linux() -> None: + """Test getting OpenSSL version for Linux platform.""" + result = get_dependency_version("openssl", "linux") + assert result is not None + assert isinstance(result, dict) + assert "version" in result + assert "url" in result + assert "sha256" in result + assert isinstance(result["version"], str) + assert "openssl" in result["url"].lower() + assert "{version}" in result["url"] + assert isinstance(result["sha256"], str) + + +def test_get_dependency_version_sqlite_all_platforms() -> None: + """Test getting SQLite version for various platforms.""" + for platform in ["linux", "darwin", "win32"]: + result = get_dependency_version("sqlite", platform) + assert result is not None, f"SQLite should be available for {platform}" + assert isinstance(result, dict) + assert "version" in result + assert "url" in result + assert "sha256" in result + assert "sqliteversion" in result, "SQLite should have sqliteversion field" + assert isinstance(result["version"], str) + assert "sqlite" in result["url"].lower() + assert isinstance(result["sha256"], str) + + +def test_get_dependency_version_xz_all_platforms() -> None: + """Test getting XZ version for various platforms.""" + # XZ 5.5.0+ removed MSBuild support, so Windows uses a fallback version + # and XZ is not in JSON for win32 + for platform in ["linux", "darwin"]: + result = get_dependency_version("xz", platform) + assert result is not None, f"XZ should be available for {platform}" + assert isinstance(result, dict) + assert "version" in result + assert "url" in result + assert "sha256" in result + assert isinstance(result["version"], str) + assert "xz" in result["url"].lower() + assert isinstance(result["sha256"], str) + + # Windows should return None (uses hardcoded fallback in windows.py) + result = get_dependency_version("xz", "win32") + assert result is None, "XZ should not be in JSON for win32 (uses fallback)" + + +def test_get_dependency_version_nonexistent() -> None: + """Test that nonexistent dependency returns None.""" + result = get_dependency_version("nonexistent-dep", "linux") + assert result is None + + +def test_get_dependency_version_wrong_platform() -> None: + """Test that requesting unsupported platform returns None.""" + # Try to get OpenSSL for a platform that doesn't exist + result = get_dependency_version("openssl", "nonexistent-platform") + assert result is None diff --git a/tests/test_pyversions_runtime.py b/tests/test_pyversions_runtime.py index b88d55ea..1158ce28 100644 --- a/tests/test_pyversions_runtime.py +++ b/tests/test_pyversions_runtime.py @@ -97,3 +97,81 @@ def fake_run( monkeypatch.setattr(pyversions.subprocess, "run", fake_run) monkeypatch.setattr(pyversions, "_receive_key", lambda keyid, server: True) assert pyversions.verify_signature("archive.tgz", "archive.tgz.asc") is True + + +def test_sha256_digest(tmp_path: pathlib.Path) -> None: + """Test SHA-256 digest computation.""" + file = tmp_path / "data.bin" + file.write_bytes(b"test data") + assert pyversions.sha256_digest(file) == hashlib.sha256(b"test data").hexdigest() + + +def test_detect_openssl_versions(monkeypatch: pytest.MonkeyPatch) -> None: + """Test OpenSSL version detection from GitHub releases.""" + mock_html = """ + + openssl-3.5.4 + openssl-3.5.3 + openssl-3.4.0 + + """ + + def fake_fetch(url: str) -> str: + return mock_html + + monkeypatch.setattr(pyversions, "fetch_url_content", fake_fetch) + versions = pyversions.detect_openssl_versions() + assert isinstance(versions, list) + assert "3.5.4" in versions + assert "3.5.3" in versions + assert "3.4.0" in versions + # Verify sorting (latest first) + assert versions[0] == "3.5.4" + + +def test_detect_sqlite_versions(monkeypatch: pytest.MonkeyPatch) -> None: + """Test SQLite version detection from sqlite.org.""" + mock_html = """ + + sqlite-autoconf-3500400.tar.gz + sqlite-autoconf-3500300.tar.gz + + """ + + def fake_fetch(url: str) -> str: + return mock_html + + monkeypatch.setattr(pyversions, "fetch_url_content", fake_fetch) + versions = pyversions.detect_sqlite_versions() + assert isinstance(versions, list) + # Should return list of tuples (version, sqliteversion) + assert len(versions) > 0 + assert isinstance(versions[0], tuple) + assert len(versions[0]) == 2 + # Check that conversion worked + version, sqlite_ver = versions[0] + assert version == "3.50.4.0" + assert sqlite_ver == "3500400" + + +def test_detect_xz_versions(monkeypatch: pytest.MonkeyPatch) -> None: + """Test XZ version detection from tukaani.org.""" + mock_html = """ + + xz-5.8.1.tar.gz + xz-5.8.0.tar.gz + xz-5.6.3.tar.gz + + """ + + def fake_fetch(url: str) -> str: + return mock_html + + monkeypatch.setattr(pyversions, "fetch_url_content", fake_fetch) + versions = pyversions.detect_xz_versions() + assert isinstance(versions, list) + assert "5.8.1" in versions + assert "5.8.0" in versions + assert "5.6.3" in versions + # Verify sorting (latest first) + assert versions[0] == "5.8.1"