#!/usr/bin/env python3 """ Per-package hooks for version management. Each hook is a callable registered by package name (the relative path under packages/, e.g. 'raspberrypi/linux-rpi') and source component name. A hook can override: - fetch_candidates(comp, merged_vars) -> Candidates - prefetch_source(comp, merged_vars) -> Optional[str] (not yet needed) Hooks are invoked by both the CLI updater and the TUI. Adding a new hook: 1. Define a function or class with the required signature. 2. Register it via register_candidates_hook(pkg_name, src_name, fn) at module level below. """ from __future__ import annotations import re from typing import Callable, Dict, Optional, Tuple from lib import ( Candidates, Json, gh_head_commit, gh_list_tags, gh_ref_date, gh_release_date, http_get_text, ) # --------------------------------------------------------------------------- # Hook registry # --------------------------------------------------------------------------- # (pkg_name, src_name) -> fn(comp, merged_vars) -> Candidates _CANDIDATES_HOOKS: Dict[Tuple[str, str], Callable] = {} def register_candidates_hook(pkg: str, src: str, fn: Callable) -> None: _CANDIDATES_HOOKS[(pkg, src)] = fn def get_candidates_hook(pkg: str, src: str) -> Optional[Callable]: return _CANDIDATES_HOOKS.get((pkg, src)) # --------------------------------------------------------------------------- # Raspberry Pi linux — stable_YYYYMMDD tag selection # --------------------------------------------------------------------------- def _rpi_linux_stable_candidates(comp: Json, merged_vars: Json) -> Candidates: from lib import render, gh_latest_release, gh_latest_tag c = Candidates() owner = comp.get("owner", "raspberrypi") repo = comp.get("repo", "linux") branch: Optional[str] = comp.get("branch") or None tags_all = gh_list_tags(owner, repo) rendered = render(comp, merged_vars) cur_tag = str(rendered.get("tag") or "") if cur_tag.startswith("stable_") or not branch: # Pick the most recent stable_YYYYMMDD tag stable_tags = sorted( [t for t in tags_all if re.match(r"^stable_\d{8}$", t)], reverse=True, ) if stable_tags: c.tag = stable_tags[0] c.tag_date = gh_ref_date(owner, repo, c.tag) else: # Series-based tracking: pick latest rpi-X.Y.* tag mm = str(merged_vars.get("modDirVersion") or "") m = re.match(r"^(\d+)\.(\d+)", mm) if m: base = f"rpi-{m.group(1)}.{m.group(2)}" series = [ t for t in tags_all if t == f"{base}.y" or t.startswith(f"{base}.y") or t.startswith(f"{base}.") ] series.sort(reverse=True) if series: c.tag = series[0] c.tag_date = gh_ref_date(owner, repo, c.tag) if branch: commit = gh_head_commit(owner, repo, branch) if commit: c.commit = commit c.commit_date = gh_ref_date(owner, repo, commit) return c register_candidates_hook( "raspberrypi/linux-rpi", "stable", _rpi_linux_stable_candidates ) register_candidates_hook( "raspberrypi/linux-rpi", "unstable", _rpi_linux_stable_candidates ) # --------------------------------------------------------------------------- # CachyOS linux — version from upstream PKGBUILD / .SRCINFO # --------------------------------------------------------------------------- def _parse_cachyos_linux_version(text: str, is_srcinfo: bool) -> Optional[str]: if is_srcinfo: m = re.search(r"^\s*pkgver\s*=\s*([^\s#]+)\s*$", text, re.MULTILINE) if m: v = m.group(1).strip().replace(".rc", "-rc") return v return None # PKGBUILD env: Dict[str, str] = {} for line in text.splitlines(): line = line.strip() if not line or line.startswith("#"): continue ma = re.match(r"^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.+)$", line) if ma: key, val = ma.group(1), ma.group(2).strip() val = re.sub(r"\s+#.*$", "", val).strip() if (val.startswith('"') and val.endswith('"')) or ( val.startswith("'") and val.endswith("'") ): val = val[1:-1] env[key] = val m2 = re.search(r"^\s*pkgver\s*=\s*(.+)$", text, re.MULTILINE) if not m2: return None raw = m2.group(1).strip().strip("\"'") def expand(s: str) -> str: s = re.sub(r"\$\{([^}]+)\}", lambda mb: env.get(mb.group(1), mb.group(0)), s) s = re.sub( r"\$([A-Za-z_][A-Za-z0-9_]*)", lambda mu: env.get(mu.group(1), mu.group(0)), s, ) return s return expand(raw).strip().replace(".rc", "-rc") def _cachyos_linux_suffix(variant_name: Optional[str]) -> str: if not variant_name: return "" return {"rc": "-rc", "hardened": "-hardened", "lts": "-lts"}.get(variant_name, "") def fetch_cachyos_linux_version(suffix: str) -> Optional[str]: bases = [ "https://raw.githubusercontent.com/CachyOS/linux-cachyos/master", "https://raw.githubusercontent.com/cachyos/linux-cachyos/master", ] for base in bases: text = http_get_text(f"{base}/linux-cachyos{suffix}/.SRCINFO") if text: v = _parse_cachyos_linux_version(text, is_srcinfo=True) if v: return v text = http_get_text(f"{base}/linux-cachyos{suffix}/PKGBUILD") if text: v = _parse_cachyos_linux_version(text, is_srcinfo=False) if v: return v return None def linux_tarball_url(version: str) -> str: if "-rc" in version: return f"https://git.kernel.org/torvalds/t/linux-{version}.tar.gz" parts = version.split(".") major = parts[0] if parts else "6" ver_for_tar = ".".join(parts[:2]) if version.endswith(".0") else version return ( f"https://cdn.kernel.org/pub/linux/kernel/v{major}.x/linux-{ver_for_tar}.tar.xz" ) # Note: linux-cachyos is not yet in the repo, but the hook is defined here # so it can be activated when that package is added. def _cachyos_linux_candidates(comp: Json, merged_vars: Json) -> Candidates: c = Candidates() # The variant name is not available here; the TUI/CLI must pass it via merged_vars suffix = str(merged_vars.get("_cachyos_suffix") or "") latest = fetch_cachyos_linux_version(suffix) if latest: c.tag = latest # use tag slot for display consistency return c register_candidates_hook("linux-cachyos", "linux", _cachyos_linux_candidates) # --------------------------------------------------------------------------- # CachyOS ZFS — commit pinned in PKGBUILD # --------------------------------------------------------------------------- def fetch_cachyos_zfs_commit(suffix: str) -> Optional[str]: bases = [ "https://raw.githubusercontent.com/CachyOS/linux-cachyos/master", "https://raw.githubusercontent.com/cachyos/linux-cachyos/master", ] for base in bases: text = http_get_text(f"{base}/linux-cachyos{suffix}/PKGBUILD") if not text: continue m = re.search( r"git\+https://github\.com/cachyos/zfs\.git#commit=([0-9a-f]+)", text ) if m: return m.group(1) return None def _cachyos_zfs_candidates(comp: Json, merged_vars: Json) -> Candidates: c = Candidates() suffix = str(merged_vars.get("_cachyos_suffix") or "") sha = fetch_cachyos_zfs_commit(suffix) if sha: c.commit = sha url = comp.get("url") or "" c.commit_date = ( gh_ref_date("cachyos", "zfs", sha) if "github.com" in url else "" ) return c register_candidates_hook("linux-cachyos", "zfs", _cachyos_zfs_candidates)