From d963ec0143ef99c7fcc657f87494852c12f125f1 Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Mon, 3 Nov 2025 00:31:59 -0700 Subject: [PATCH 1/3] Extend pyversions to include dependencies Relenv has been tracking what python verions are available for some time now. It'll be great to track dependency versions too, this will reduce the burdon of maintaining things movnig forward. --- relenv/build/common.py | 72 ++- relenv/build/darwin.py | 112 ++++- relenv/build/linux.py | 284 ++++++++++-- relenv/build/windows.py | 51 ++- relenv/python-versions.json | 510 +++++++++++++-------- relenv/pyversions.py | 748 ++++++++++++++++++++++++++++++- tests/test_build.py | 64 ++- tests/test_pyversions_runtime.py | 78 ++++ 8 files changed, 1666 insertions(+), 253 deletions(-) diff --git a/relenv/build/common.py b/relenv/build/common.py index dde8e07b..e001eb73 100644 --- a/relenv/build/common.py +++ b/relenv/build/common.py @@ -204,11 +204,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 +222,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 @@ -541,6 +559,52 @@ def uuid_version(href: str) -> Optional[str]: return None +def get_dependency_version(name: str, platform: str) -> Optional[Dict[str, str]]: + """ + Get dependency version and metadata from python-versions.json. + + 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 + + import json + + data = json.loads(versions_file.read_text()) + dependencies = data.get("dependencies", {}) + + 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 + + return None + + def parse_links(text: str) -> List[str]: class HrefParser(HTMLParser): def __init__(self) -> None: 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..3d526219 100644 --- a/relenv/build/linux.py +++ b/relenv/build/linux.py @@ -20,6 +20,7 @@ build_sqlite, builds, finalize, + get_dependency_version, github_version, runcmd, sqlite_version, @@ -366,6 +367,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 +427,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,13 +554,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", + "url": openssl_url, + "version": openssl_version, + "checksum": openssl_checksum, "checkfunc": tarball_version, "checkurl": "https://www.openssl.org/source/", }, @@ -527,140 +592,281 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: ) +# 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", + "url": libxcrypt_url, + "version": libxcrypt_version, + "checksum": libxcrypt_checksum, "checkfunc": github_version, "checkurl": "https://github.com/besser82/libxcrypt/releases/", }, ) +# 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", + "url": xz_url, + "version": xz_version, + "checksum": xz_checksum, "checkfunc": tarball_version, }, ) +# 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", + "url": sqlite_url, + "version": sqlite_version_num, + "checksum": sqlite_checksum, "checkfunc": sqlite_version, "checkurl": "https://sqlite.org/", }, ) +# 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", + "url": bzip2_url, + "version": bzip2_version, + "checksum": bzip2_checksum, "checkfunc": tarball_version, }, ) +# 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", + "url": gdbm_url, + "version": gdbm_version, + "checksum": gdbm_checksum, "checkfunc": tarball_version, }, ) +# 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", + "url": ncurses_url, + "version": ncurses_version, + "checksum": ncurses_checksum, "checkfunc": tarball_version, }, ) +# 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", + "url": libffi_url, + "version": libffi_version, + "checksum": libffi_checksum, "checkfunc": github_version, "checkurl": "https://github.com/libffi/libffi/releases/", }, ) +# 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", + "url": zlib_url, + "version": zlib_version, + "checksum": zlib_checksum, "checkfunc": tarball_version, }, ) +# 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", + "url": uuid_url, + "version": uuid_ver, + "checksum": uuid_checksum, "checkfunc": uuid_version, }, ) +# 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", + "url": krb5_url, + "version": krb5_version, + "checksum": krb5_checksum, "checkfunc": krb_version, "checkurl": "https://kerberos.org/dist/krb5/", }, ) +# 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", + "url": readline_url, + "version": readline_version, + "checksum": readline_checksum, "checkfunc": tarball_version, }, ) +# 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", + "version": tirpc_version, + "checksum": tirpc_checksum, "checkfunc": tarball_version, }, ) 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..c280e2a2 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,107 @@ 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...") + + print("\nOpenSSL:") + openssl_versions = detect_openssl_versions() + if openssl_versions: + print(f" Latest: {openssl_versions[0]}") + + print("\nSQLite:") + sqlite_versions = detect_sqlite_versions() + if sqlite_versions: + latest_ver, latest_sql = sqlite_versions[0] + print(f" Latest: {latest_ver}") + + print("\nXZ:") + xz_versions = detect_xz_versions() + if xz_versions: + print(f" Latest: {xz_versions[0]}") + + print("\nlibffi:") + libffi_versions = detect_libffi_versions() + if libffi_versions: + print(f" Latest: {libffi_versions[0]}") + + print("\nzlib:") + zlib_versions = detect_zlib_versions() + if zlib_versions: + print(f" Latest: {zlib_versions[0]}") + + print("\nncurses:") + ncurses_versions = detect_ncurses_versions() + if ncurses_versions: + print(f" Latest: {ncurses_versions[0]}") + + print("\nreadline:") + readline_versions = detect_readline_versions() + if readline_versions: + print(f" Latest: {readline_versions[0]}") + + print("\ngdbm:") + gdbm_versions = detect_gdbm_versions() + if gdbm_versions: + print(f" Latest: {gdbm_versions[0]}") + + print("\nlibxcrypt:") + libxcrypt_versions = detect_libxcrypt_versions() + if libxcrypt_versions: + print(f" Latest: {libxcrypt_versions[0]}") + + print("\nkrb5:") + krb5_versions = detect_krb5_versions() + if krb5_versions: + print(f" Latest: {krb5_versions[0]}") + + print("\nbzip2:") + bzip2_versions = detect_bzip2_versions() + if bzip2_versions: + print(f" Latest: {bzip2_versions[0]}") + + print("\nuuid:") + uuid_versions = detect_uuid_versions() + if uuid_versions: + print(f" Latest: {uuid_versions[0]}") + + print("\ntirpc:") + tirpc_versions = detect_tirpc_versions() + if tirpc_versions: + print(f" Latest: {tirpc_versions[0]}") + + print("\nexpat:") + expat_versions = detect_expat_versions() + if expat_versions: + print(f" Latest: {expat_versions[0]}") + + 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" From 9dcba84c385ad7540b0e7c55151b5830c1f508ec Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Mon, 3 Nov 2025 02:13:06 -0700 Subject: [PATCH 2/3] Better check-versions ouptut --- relenv/pyversions.py | 161 +++++++++++++++++++++++++------------------ 1 file changed, 93 insertions(+), 68 deletions(-) diff --git a/relenv/pyversions.py b/relenv/pyversions.py index c280e2a2..e191afcc 100644 --- a/relenv/pyversions.py +++ b/relenv/pyversions.py @@ -1003,78 +1003,103 @@ def main(args: argparse.Namespace) -> None: # Handle dependency operations if args.check_deps: - print("Checking for new dependency versions...") - - print("\nOpenSSL:") - openssl_versions = detect_openssl_versions() - if openssl_versions: - print(f" Latest: {openssl_versions[0]}") - - print("\nSQLite:") - sqlite_versions = detect_sqlite_versions() - if sqlite_versions: - latest_ver, latest_sql = sqlite_versions[0] - print(f" Latest: {latest_ver}") - - print("\nXZ:") - xz_versions = detect_xz_versions() - if xz_versions: - print(f" Latest: {xz_versions[0]}") - - print("\nlibffi:") - libffi_versions = detect_libffi_versions() - if libffi_versions: - print(f" Latest: {libffi_versions[0]}") - - print("\nzlib:") - zlib_versions = detect_zlib_versions() - if zlib_versions: - print(f" Latest: {zlib_versions[0]}") - - print("\nncurses:") - ncurses_versions = detect_ncurses_versions() - if ncurses_versions: - print(f" Latest: {ncurses_versions[0]}") - - print("\nreadline:") - readline_versions = detect_readline_versions() - if readline_versions: - print(f" Latest: {readline_versions[0]}") - - print("\ngdbm:") - gdbm_versions = detect_gdbm_versions() - if gdbm_versions: - print(f" Latest: {gdbm_versions[0]}") - - print("\nlibxcrypt:") - libxcrypt_versions = detect_libxcrypt_versions() - if libxcrypt_versions: - print(f" Latest: {libxcrypt_versions[0]}") - - print("\nkrb5:") - krb5_versions = detect_krb5_versions() - if krb5_versions: - print(f" Latest: {krb5_versions[0]}") + 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), + ] - print("\nbzip2:") - bzip2_versions = detect_bzip2_versions() - if bzip2_versions: - print(f" Latest: {bzip2_versions[0]}") + for dep_key, dep_name, detect_func in checks: + detected = detect_func() + if not detected: + continue - print("\nuuid:") - uuid_versions = detect_uuid_versions() - if uuid_versions: - print(f" Latest: {uuid_versions[0]}") + # 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)) - print("\ntirpc:") - tirpc_versions = detect_tirpc_versions() - if tirpc_versions: - print(f" Latest: {tirpc_versions[0]}") + # Summary + print(f"\n{'=' * 60}") + print(f"Summary: {len(up_to_date)} up-to-date, ", end="") + print(f"{len(updates_available)} updates available") - print("\nexpat:") - expat_versions = detect_expat_versions() - if expat_versions: - print(f" Latest: {expat_versions[0]}") + if updates_available: + print("\nTo update dependencies, run:") + print(" python3 -m relenv versions --update-deps") sys.exit(0) From 1200f131da00b7ad28e75e184fb08b8406cbcbcb Mon Sep 17 00:00:00 2001 From: "Daniel A. Wozniak" Date: Mon, 3 Nov 2025 02:43:51 -0700 Subject: [PATCH 3/3] Drop relenv build --check-versions relenv build --check-versions is now superseeded by relenv versions --check-deps. The new dependency tracking via --check-deps and --update-deps will allow us to more easily maintain relenv moving forwared --- relenv/build/__init__.py | 20 +---- relenv/build/common.py | 157 +-------------------------------------- relenv/build/linux.py | 28 ------- 3 files changed, 2 insertions(+), 203 deletions(-) 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 e001eb73..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__) @@ -505,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 = "" @@ -517,48 +505,6 @@ 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 github_version(href: str) -> Optional[str]: - if "tag/" in href: - return href.split("/v")[-1] - return None - - -def krb_version(href: str) -> Optional[str]: - if re.match(r"\d\.\d\d/", href): - return href[:-1] - return None - - -def python_version(href: str) -> Optional[str]: - if re.match(r"(\d+\.)+\d/", href): - return href[:-1] - return None - - -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 get_dependency_version(name: str, platform: str) -> Optional[Dict[str, str]]: """ Get dependency version and metadata from python-versions.json. @@ -605,81 +551,6 @@ def get_dependency_version(name: str, platform: str) -> Optional[Dict[str, str]] 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. @@ -708,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 @@ -720,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( @@ -732,8 +599,6 @@ def copy(self) -> "Download": self.destination, self.version, self.checksum, - self.checkfunc, - self.checkurl, ) @property @@ -902,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.""" @@ -1567,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/linux.py b/relenv/build/linux.py index 3d526219..8dadb3d2 100644 --- a/relenv/build/linux.py +++ b/relenv/build/linux.py @@ -21,13 +21,7 @@ builds, finalize, get_dependency_version, - github_version, runcmd, - sqlite_version, - tarball_version, - krb_version, - python_version, - uuid_version, ) from ..common import LINUX, Version, arches @@ -573,8 +567,6 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: "url": openssl_url, "version": openssl_version, "checksum": openssl_checksum, - "checkfunc": tarball_version, - "checkurl": "https://www.openssl.org/source/", }, ) @@ -586,8 +578,6 @@ 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/", }, ) @@ -609,8 +599,6 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: "url": libxcrypt_url, "version": libxcrypt_version, "checksum": libxcrypt_checksum, - "checkfunc": github_version, - "checkurl": "https://github.com/besser82/libxcrypt/releases/", }, ) @@ -632,7 +620,6 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: "url": xz_url, "version": xz_version, "checksum": xz_checksum, - "checkfunc": tarball_version, }, ) @@ -656,8 +643,6 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: "url": sqlite_url, "version": sqlite_version_num, "checksum": sqlite_checksum, - "checkfunc": sqlite_version, - "checkurl": "https://sqlite.org/", }, ) @@ -679,7 +664,6 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: "url": bzip2_url, "version": bzip2_version, "checksum": bzip2_checksum, - "checkfunc": tarball_version, }, ) @@ -701,7 +685,6 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: "url": gdbm_url, "version": gdbm_version, "checksum": gdbm_checksum, - "checkfunc": tarball_version, }, ) @@ -725,7 +708,6 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: "url": ncurses_url, "version": ncurses_version, "checksum": ncurses_checksum, - "checkfunc": tarball_version, }, ) @@ -747,8 +729,6 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: "url": libffi_url, "version": libffi_version, "checksum": libffi_checksum, - "checkfunc": github_version, - "checkurl": "https://github.com/libffi/libffi/releases/", }, ) @@ -770,7 +750,6 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: "url": zlib_url, "version": zlib_version, "checksum": zlib_checksum, - "checkfunc": tarball_version, }, ) @@ -791,7 +770,6 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: "url": uuid_url, "version": uuid_ver, "checksum": uuid_checksum, - "checkfunc": uuid_version, }, ) @@ -814,8 +792,6 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: "url": krb5_url, "version": krb5_version, "checksum": krb5_checksum, - "checkfunc": krb_version, - "checkurl": "https://kerberos.org/dist/krb5/", }, ) @@ -840,7 +816,6 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: "url": readline_url, "version": readline_version, "checksum": readline_checksum, - "checkfunc": tarball_version, }, ) @@ -867,7 +842,6 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: # "url": "https://downloads.sourceforge.net/projects/libtirpc/files/libtirpc-{version}.tar.bz2", "version": tirpc_version, "checksum": tirpc_checksum, - "checkfunc": tarball_version, }, ) @@ -893,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/", }, )