Files
sergioandClaude Opus 4.8 3a64b603d7 catálogo objetivo P1: manifiesto de perfiles — el set de la distro deja de ser un string de shell
El destino de la distro vivía en 2 strings de product-userland-from-repo.sh, 1
de mirada-usb.sh y 55 tandas planas. Ahora en docs/state/targets.toml: un perfil
= una imagen enviable, listando sólo las RAÍCES (lo que se pide por nombre); la
clausura la calcula el grafo, no un humano.

4 perfiles: base (26), cli (46, hereda base), escritorio-mirada (14),
escritorio-kde (7 raíces, cola incoming-kde). scripts/targets.py expande hereda
(transitivo, con detección de ciclo) preservando el orden — mirada lo necesita.

Lift-and-shift verificado: las 3 listas expandidas son byte-idénticas a los
strings que reemplazan. Ninguna imagen cambia de contenido.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 17:41:01 -04:00

84 lines
3.2 KiB
Python
Executable File

#!/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())