#!/usr/bin/env python3 # targets.py — lee `docs/state/targets.toml` (el manifiesto de objetivo) y expande un perfil a su # lista de RAÍCES. Un solo parser para dos consumidores: los scripts de imagen (shell) y el grafo # de estado (`build-state.py`), para que el set de paquetes de la distro no vuelva a vivir en tres # strings de shell distintos. # # Uso: scripts/targets.py cli # raíces expandidas, una línea separada por espacios # scripts/targets.py --lista # perfiles disponibles, con su descripción # scripts/targets.py --json # todo el manifiesto expandido # Como módulo: from targets import load → {perfil: {descripcion, cola, hereda, paquetes, raices}} # # `hereda` es transitivo y el orden se PRESERVA (primero las raíces de lo heredado, en el orden # declarado, después las propias), porque hay imágenes donde el orden de hidratación importa # (mirada: libs antes que binarios). Duplicados: se queda la primera aparición. import json, sys, tomllib from pathlib import Path ROOT = Path(__file__).resolve().parent.parent DEFAULT = ROOT / "docs/state/targets.toml" def load(path=DEFAULT): """Devuelve {perfil: {descripcion, cola, hereda, paquetes, raices}} con `raices` ya expandido.""" doc = tomllib.load(open(path, "rb")) crudos = doc.get("perfil", {}) perfiles = {} for n, p in crudos.items(): perfiles[n] = dict( descripcion=p.get("descripcion", ""), cola=p.get("cola", "corpus"), hereda=list(p.get("hereda", [])), paquetes=list(p.get("paquetes", [])), ) # Expansión de `hereda` con detección de ciclo (un manifiesto con un ciclo es un error del # humano, no un caso a tolerar: sale ruidoso). def expandir(n, camino): if n in camino: raise ValueError(f"ciclo en `hereda`: {' → '.join(camino + [n])}") if n not in perfiles: raise KeyError(f"perfil desconocido en `hereda`: {n}") p = perfiles[n] if "raices" in p: return p["raices"] vistos, raices = set(), [] for h in p["hereda"] + [None]: for pkg in (expandir(h, camino + [n]) if h else p["paquetes"]): if pkg not in vistos: vistos.add(pkg); raices.append(pkg) p["raices"] = raices return raices for n in perfiles: expandir(n, []) return perfiles def main(): args = sys.argv[1:] try: perfiles = load() except FileNotFoundError: print(f"no existe {DEFAULT.relative_to(ROOT)}", file=sys.stderr); return 1 except (ValueError, KeyError) as e: print(f"manifiesto inválido: {e}", file=sys.stderr); return 1 if not args or args[0] in ("--lista", "-l"): for n, p in sorted(perfiles.items()): print(f"{n:20} {len(p['raices']):3} raíces [{p['cola']}] {p['descripcion']}") return 0 if args[0] == "--json": print(json.dumps(perfiles, indent=1, ensure_ascii=False)); return 0 n = args[0] if n not in perfiles: print(f"perfil desconocido: {n} (hay: {', '.join(sorted(perfiles))})", file=sys.stderr) return 1 print(" ".join(perfiles[n]["raices"])) return 0 if __name__ == "__main__": sys.exit(main())