From a336b0cf605f2c494015f90dc8d6a524b6a84545 Mon Sep 17 00:00:00 2001 From: mjallen18 Date: Wed, 21 Jan 2026 21:17:36 -0600 Subject: [PATCH] packages --- packages/proton-cachyos/version.json | 16 +- scripts/version_tui.py | 290 +++++++++++++++++++++++++-- 2 files changed, 280 insertions(+), 26 deletions(-) mode change 100644 => 100755 scripts/version_tui.py diff --git a/packages/proton-cachyos/version.json b/packages/proton-cachyos/version.json index 3a053e0..d6d8f0f 100644 --- a/packages/proton-cachyos/version.json +++ b/packages/proton-cachyos/version.json @@ -19,44 +19,44 @@ "cachyos": { "variables": { "base": "10.0", - "release": "20251222" + "release": "20260102" }, "sources": { "proton": { - "hash": "sha256-W7cC4pi8WED4rOEXYVXIio1tiUNvArzqsTl6xKwy/mY=" + "hash": "sha256-e2A9jrAGUHlD8KUuGjkLUhknclBxVZVYKJFElUEs0Us=" } } }, "cachyos-v2": { "variables": { "base": "10.0", - "release": "20251222" + "release": "20260102" }, "sources": { "proton": { - "hash": "sha256-S5i8RBbrPAlsYYavzzhTFanLU3uyLT3OQRpX9S6pPE0=" + "hash": "sha256-e2A9jrAGUHlD8KUuGjkLUhknclBxVZVYKJFElUEs0Us=" } } }, "cachyos-v3": { "variables": { "base": "10.0", - "release": "20251222" + "release": "20260102" }, "sources": { "proton": { - "hash": "sha256-tw1/uX4qZX3cQKyzsss8l+wHKLoJF2/8B+6RUIQt4oQ=" + "hash": "sha256-e2A9jrAGUHlD8KUuGjkLUhknclBxVZVYKJFElUEs0Us=" } } }, "cachyos-v4": { "variables": { "base": "10.0", - "release": "20251222" + "release": "20260102" }, "sources": { "proton": { - "hash": "sha256-1+6nCUc93vVZg3j4oSwuM7DYOZ2bNbLIjbH+8OUOSAQ=" + "hash": "sha256-e2A9jrAGUHlD8KUuGjkLUhknclBxVZVYKJFElUEs0Us=" } } }, diff --git a/scripts/version_tui.py b/scripts/version_tui.py old mode 100644 new mode 100755 index af4c21b..7042544 --- a/scripts/version_tui.py +++ b/scripts/version_tui.py @@ -184,6 +184,16 @@ def http_get_json(url: str, token: Optional[str] = None) -> Any: with urllib.request.urlopen(req) as resp: return json.loads(resp.read().decode("utf-8")) +def http_get_text(url: str) -> Optional[str]: + try: + # Provide a basic User-Agent to avoid some hosts rejecting the request + req = urllib.request.Request(url, headers={"User-Agent": "version-tui/1.0"}) + with urllib.request.urlopen(req) as resp: + return resp.read().decode("utf-8") + except Exception as e: + eprintln(f"http_get_text failed for {url}: {e}") + return None + def gh_latest_release(owner: str, repo: str, token: Optional[str]) -> Optional[str]: try: data = http_get_json(f"https://api.github.com/repos/{owner}/{repo}/releases/latest", token) @@ -201,6 +211,14 @@ def gh_latest_tag(owner: str, repo: str, token: Optional[str]) -> Optional[str]: eprintln(f"latest_tag failed for {owner}/{repo}: {e}") return None +def gh_list_tags(owner: str, repo: str, token: Optional[str]) -> List[str]: + try: + data = http_get_json(f"https://api.github.com/repos/{owner}/{repo}/tags?per_page=100", token) + return [t.get("name") for t in data if isinstance(t, dict) and "name" in t] + except Exception as e: + eprintln(f"list_tags failed for {owner}/{repo}: {e}") + return [] + def gh_head_commit(owner: str, repo: str) -> Optional[str]: out = run_get_stdout(["git", "ls-remote", f"https://github.com/{owner}/{repo}.git", "HEAD"]) if not out: @@ -358,11 +376,46 @@ class PackageDetailScreen(ScreenBase): repo = comp.get("repo") if owner and repo: r = gh_latest_release(owner, repo, self.gh_token) - if r: c["release"] = r + if r: + c["release"] = r t = gh_latest_tag(owner, repo, self.gh_token) - if t: c["tag"] = t + if t: + c["tag"] = t m = gh_head_commit(owner, repo) - if m: c["commit"] = m + if m: + c["commit"] = m + + # Special-case raspberrypi/linux: prefer latest stable_* tag or series-specific tags + try: + if owner == "raspberrypi" and repo == "linux": + tags_all = gh_list_tags(owner, repo, self.gh_token) + rendered = render_templates(comp, self.merged_vars) + cur_tag = str(rendered.get("tag") or "") + # If current tag uses stable_YYYYMMDD scheme, pick latest stable_* tag + if cur_tag.startswith("stable_"): + stable_tags = sorted( + [x for x in tags_all if re.match(r"^stable_\d{8}$", x)], + reverse=True, + ) + if stable_tags: + c["tag"] = stable_tags[0] + else: + # Try to pick a tag matching the current major.minor series if available + mm = str(self.merged_vars.get("modDirVersion") or "") + m2 = re.match(r"^(\d+)\.(\d+)", mm) + if m2: + base = f"rpi-{m2.group(1)}.{m2.group(2)}" + series_tags = [x for x in tags_all if ( + x == f"{base}.y" + or x.startswith(f"{base}.y") + or x.startswith(f"{base}.") + )] + series_tags.sort(reverse=True) + if series_tags: + c["tag"] = series_tags[0] + except Exception as _e: + # Fallback to previously computed values + pass elif fetcher == "git": url = comp.get("url") if url: @@ -414,6 +467,126 @@ class PackageDetailScreen(ScreenBase): return nix_prefetch_url(url) return None + def cachyos_suffix(self) -> str: + if self.vidx == 0: + return "" + v = self.variants[self.vidx] + mapping = {"rc": "-rc", "hardened": "-hardened", "lts": "-lts"} + return mapping.get(v, "") + + def fetch_cachyos_linux_latest(self, suffix: str) -> Optional[str]: + """ + Try to determine latest linux version from upstream: + - Prefer .SRCINFO (preprocessed) + - Fallback to PKGBUILD (parse pkgver= line) + Tries both 'CachyOS' and 'cachyos' org casing just in case. + """ + bases = [ + "https://raw.githubusercontent.com/CachyOS/linux-cachyos/master", + "https://raw.githubusercontent.com/cachyos/linux-cachyos/master", + ] + paths = [ + f"linux-cachyos{suffix}/.SRCINFO", + f"linux-cachyos{suffix}/PKGBUILD", + ] + + def parse_srcinfo(text: str) -> Optional[str]: + m = re.search(r"^\s*pkgver\s*=\s*([^\s#]+)\s*$", text, re.MULTILINE) + if not m: + return None + v = m.group(1).strip() + return v + + def parse_pkgbuild(text: str) -> Optional[str]: + # Parse assignments and expand variables in pkgver + # Build a simple env map from VAR=value lines + env: Dict[str, str] = {} + for line in text.splitlines(): + line = line.strip() + if not line or line.startswith("#"): + continue + m_assign = re.match(r'^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.+)$', line) + if m_assign: + key = m_assign.group(1) + val = m_assign.group(2).strip() + # Remove trailing comments + val = re.sub(r'\s+#.*$', '', val).strip() + # Strip surrounding quotes + if (val.startswith('"') and val.endswith('"')) or (val.startswith("'") and val.endswith("'")): + val = val[1:-1] + env[key] = val + + m = re.search(r"^\s*pkgver\s*=\s*(.+)$", text, re.MULTILINE) + if not m: + return None + raw = m.group(1).strip() + # Strip quotes + if (raw.startswith('"') and raw.endswith('"')) or (raw.startswith("'") and raw.endswith("'")): + raw = raw[1:-1] + + def expand_vars(s: str) -> str: + def repl_braced(mb): + key = mb.group(1) + return env.get(key, mb.group(0)) + def repl_unbraced(mu): + key = mu.group(1) + return env.get(key, mu.group(0)) + # Expand ${var} then $var + s = re.sub(r"\$\{([^}]+)\}", repl_braced, s) + s = re.sub(r"\$([A-Za-z_][A-Za-z0-9_]*)", repl_unbraced, s) + return s + + v = expand_vars(raw).strip() + # normalize rc form like 6.19.rc6 -> 6.19-rc6 + v = v.replace(".rc", "-rc") + return v + + # Try .SRCINFO first, then PKGBUILD + for base in bases: + # .SRCINFO + url = f"{base}/{paths[0]}" + text = http_get_text(url) + if text: + ver = parse_srcinfo(text) + if ver: + return ver.replace(".rc", "-rc") + # PKGBUILD fallback + url = f"{base}/{paths[1]}" + text = http_get_text(url) + if text: + ver = parse_pkgbuild(text) + if ver: + return ver.replace(".rc", "-rc") + + return None + + def linux_tarball_url_for_version(self, version: str) -> str: + # Use torvalds snapshot for -rc, stable releases from CDN + 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" + major_minor = ".".join(parts[:2]) if len(parts) >= 2 else version + ver_for_tar = major_minor if version.endswith(".0") else version + return f"https://cdn.kernel.org/pub/linux/kernel/v{major}.x/linux-{ver_for_tar}.tar.xz" + + def update_linux_from_pkgbuild(self, name: str): + suffix = self.cachyos_suffix() + latest = self.fetch_cachyos_linux_latest(suffix) + if not latest: + self.set_status("linux: failed to get version from PKGBUILD") + return + url = self.linux_tarball_url_for_version(latest) + sri = nix_prefetch_url(url) + if not sri: + self.set_status("linux: prefetch failed") + return + ts = self.target_dict.setdefault("sources", {}) + compw = ts.setdefault(name, {}) + compw["version"] = latest + compw["hash"] = sri + self.set_status(f"{name}: updated version to {latest} and refreshed hash") + def set_ref(self, name: str, kind: str, value: str): # Write to selected target dict (base or variant override) ts = self.target_dict.setdefault("sources", {}) @@ -511,15 +684,32 @@ class PackageDetailScreen(ScreenBase): elif ch in (ord('r'),): if self.snames: name = self.snames[self.sidx] - self.fetch_candidates_for(name) - cand = self.candidates.get(name, {}) - lines = [ - f"Candidates for {name}:", - f" latest release: {cand.get('release') or '-'}", - f" latest tag : {cand.get('tag') or '-'}", - f" latest commit : {cand.get('commit') or '-'}", - ] - show_popup(self.stdscr, lines) + comp = self.merged_srcs[name] + fetcher = comp.get("fetcher", "none") + if self.pkg_name == "linux-cachyos" and name == "linux": + # Show available linux version from upstream PKGBUILD (.SRCINFO) + suffix = self.cachyos_suffix() + latest = self.fetch_cachyos_linux_latest(suffix) + rendered = render_templates(comp, self.merged_vars) + cur_version = str(rendered.get("version") or "") + url_hint = self.linux_tarball_url_for_version(latest) if latest else "-" + lines = [ + f"linux-cachyos ({'base' if self.vidx == 0 else self.variants[self.vidx]}):", + f" current : {cur_version or '-'}", + f" available: {latest or '-'}", + f" tarball : {url_hint}", + ] + show_popup(self.stdscr, lines) + else: + self.fetch_candidates_for(name) + cand = self.candidates.get(name, {}) + lines = [ + f"Candidates for {name}:", + f" latest release: {cand.get('release') or '-'}", + f" latest tag : {cand.get('tag') or '-'}", + f" latest commit : {cand.get('commit') or '-'}", + ] + show_popup(self.stdscr, lines) elif ch in (ord('i'),): # Show full rendered URL for URL-based sources if self.snames: @@ -586,7 +776,24 @@ class PackageDetailScreen(ScreenBase): ("Recompute hash", ("hash", None)), ("Cancel", ("cancel", None)), ] - choice = select_menu(self.stdscr, f"Actions for {name}", [label for label, _ in items]) + # Build header with current and available refs + rendered = render_templates(comp, self.merged_vars) + cur_tag = rendered.get("tag") or "" + cur_rev = rendered.get("rev") or "" + cur_version = rendered.get("version") or "" + if cur_tag: + current_str = f"current: tag={cur_tag}" + elif cur_rev: + current_str = f"current: rev={cur_rev[:12]}" + elif cur_version: + current_str = f"current: version={cur_version}" + else: + current_str = "current: -" + header_lines = [ + current_str, + f"available: release={cand.get('release') or '-'} tag={cand.get('tag') or '-'} commit={(cand.get('commit') or '')[:12] or '-'}", + ] + choice = select_menu(self.stdscr, f"Actions for {name}", [label for label, _ in items], header=header_lines) if choice is not None: kind, val = items[choice][1] if kind in ("release", "tag", "commit"): @@ -621,7 +828,23 @@ class PackageDetailScreen(ScreenBase): menu_items.append(("Recompute hash (prefetch)", ("hash", None))) menu_items.append(("Cancel", ("cancel", None))) - choice = select_menu(self.stdscr, f"Actions for {name}", [label for label, _ in menu_items]) + # Build header with current and available release info + base = str(self.merged_vars.get("base") or "") + rel = str(self.merged_vars.get("release") or "") + rp = str(self.merged_vars.get("releasePrefix") or "") + rs = str(self.merged_vars.get("releaseSuffix") or "") + current_tag = f"{rp}{base}-{rel}{rs}" if (base and rel) else "" + if current_tag: + current_str = f"current: {current_tag}" + elif base or rel: + current_str = f"current: base={base or '-'} release={rel or '-'}" + else: + current_str = "current: -" + header_lines = [ + current_str, + f"available: tag={(cand.get('tag') or '-') if cand else '-'} base={(cand.get('base') or '-') if cand else '-'} release={(cand.get('release') or '-') if cand else '-'}", + ] + choice = select_menu(self.stdscr, f"Actions for {name}", [label for label, _ in menu_items], header=header_lines) if choice is not None: kind, payload = menu_items[choice][1] if kind == "update_vars" and isinstance(payload, dict): @@ -652,19 +875,50 @@ class PackageDetailScreen(ScreenBase): else: pass else: - show_popup(self.stdscr, [f"{name}: fetcher={fetcher}", "Use 'e' to edit fields manually."]) + if self.pkg_name == "linux-cachyos" and name == "linux": + # Offer update of linux version from upstream PKGBUILD (.SRCINFO) + suffix = self.cachyos_suffix() + latest = self.fetch_cachyos_linux_latest(suffix) + rendered = render_templates(comp, self.merged_vars) + cur_version = str(rendered.get("version") or "") + header_lines = [ + f"current: version={cur_version or '-'}", + f"available: version={latest or '-'}", + ] + opts = [] + if latest: + opts.append(f"Update linux version to {latest} from PKGBUILD (.SRCINFO)") + else: + opts.append("Update linux version from PKGBUILD (.SRCINFO)") + opts.append("Cancel") + choice = select_menu(self.stdscr, f"Actions for {name}", opts, header=header_lines) + if choice == 0 and latest: + self.update_linux_from_pkgbuild(name) + else: + pass + else: + show_popup(self.stdscr, [f"{name}: fetcher={fetcher}", "Use 'e' to edit fields manually."]) else: pass -def select_menu(stdscr, title: str, options: List[str]) -> Optional[int]: +def select_menu(stdscr, title: str, options: List[str], header: Optional[List[str]] = None) -> Optional[int]: idx = 0 while True: stdscr.clear() h, w = stdscr.getmaxyx() stdscr.addstr(0, 0, title[:w-1]) - for i, opt in enumerate(options[:h-3], start=0): + y = 1 + if header: + for line in header: + if y >= h-2: + break + stdscr.addstr(y, 0, str(line)[:w-1]) + y += 1 + start_y = y + 1 + max_opts = max(0, h - start_y - 1) + for i, opt in enumerate(options[:max_opts], start=0): sel = ">" if i == idx else " " - stdscr.addstr(2+i, 0, f"{sel} {opt}"[:w-1]) + stdscr.addstr(start_y + i, 0, f"{sel} {opt}"[:w-1]) stdscr.addstr(h-1, 0, "Enter: select | Backspace: cancel") stdscr.refresh() ch = stdscr.getch()