250 líneas de comentario en 151 scripts. Control verificado: el diff no toca NI UNA línea que no empiece por #, y la sintaxis de los 151 pasa. El barrido saltea heredocs y cadenas triples, y el guardián DISPARÓ 3 veces: las tres eran el MOTD que el script escribe DENTRO de la imagen construida — texto del producto, no comentario del script. Se cambiaron aparte y a propósito, que es rebranding, no limpieza. Y el hallazgo caro: casaba contra , que es el TARGET de tracing — o sea el module_path!, o sea el nombre del crate. La etapa 4 lo movió a y el script quedó casando NADA. No fallaba: imprimía cero atribuciones, indistinguible de un log sin problemas. Comprobado con el binario (RUST_LOG=info sobre zlib), no deducido. Ahora acepta las dos, y tiene que seguir aceptándolas porque los logs viejos en disco dicen la vieja. Además 14 rutas de módulo en docs, que el barrido anterior no tocó porque no es frontera de palabra.
189 lines
12 KiB
Bash
Executable File
189 lines
12 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# licencias.sh — mide y puebla el campo `license` de las recetas. SDD 19 §2.1.
|
|
#
|
|
# ── EL PROBLEMA ─────────────────────────────────────────────────────────────────────────────────
|
|
# Medido el 2026-08-07: **0 de 1141 recetas** declaraban licencia. (Un informe anterior dijo «5 de
|
|
# 771»; los dos números estaban mal. Los «5» eran falsos positivos de `grep license`: el paquete
|
|
# `addlicense`, el paquete `cargo-bundle-licenses`, una línea `install ... /usr/share/licenses/` y un
|
|
# comentario. Y las recetas son 1141, no 771. Lección: contar con `grep -l <palabra>` sobre TOML
|
|
# cuenta comentarios y nombres, no campos.)
|
|
#
|
|
# Sin ese dato la distro no puede cumplir la GPL cuando redistribuye binarios: la obligación es
|
|
# acompañar el binario del texto de la licencia y poder entregar la fuente correspondiente. Es
|
|
# BLOQUEANTE para publicar, y es la deuda que más barato sale pagar temprano: a 1141 recetas es un
|
|
# rato de trabajo, a 2600 es un proyecto.
|
|
#
|
|
# ── POR QUÉ ESTO NO RE-HASHEA NADA (lo que hace la deuda pagable) ───────────────────────────────
|
|
# `Recipe::hash_inputs` es una LISTA BLANCA: sólo entran source, compiler, target, link, patches,
|
|
# flags, phases y deps. `license` no entra, igual que `evidence` y `slots`. O sea que se puede poblar
|
|
# en las 1141 recetas YA SELLADAS sin mover un solo ArtifactHash ni invalidar el baseline de
|
|
# reproducibilidad. Está clavado con un test: `licencia_round_trip_y_no_afecta_el_hash`.
|
|
#
|
|
# ── DÓNDE VA LA LÍNEA (trampa de TOML, cuesta una tarde si se ignora) ───────────────────────────
|
|
# En TOML una clave suelta después de un `[table]` pertenece a ESA tabla. `license = "..."` puesto al
|
|
# final del fichero acaba dentro de `[deps]` y **se pierde en silencio**, porque serde ignora los
|
|
# campos que no conoce: no hay error, simplemente no está. Va ARRIBA, junto a `name` y `version`.
|
|
#
|
|
# ── LA REGLA DE HONESTIDAD: NO SE ADIVINA ───────────────────────────────────────────────────────
|
|
# Declarar mal una licencia es PEOR que no declararla — convierte un hueco visible en una afirmación
|
|
# falsa sobre la que alguien va a construir. Por eso este script no infiere de la URL ni del nombre.
|
|
# Sólo puebla desde `docs/licencias-conocidas.tsv`, una tabla curada y auditable a mano, y desde los
|
|
# ficheros COPYING/LICENSE de árboles de fuente realmente extraídos. Lo que no tiene evidencia se
|
|
# queda vacío y SE CUENTA. Un hueco contado es deuda; un hueco rellenado a ojo es una mentira.
|
|
#
|
|
# ── EL ARREGLO ESTRUCTURAL, QUE ES OTRO TICKET ──────────────────────────────────────────────────
|
|
# Poblar a mano tapa el agujero de hoy pero no impide que vuelva a abrirse: cada receta nueva nace
|
|
# sin licencia. El cierre de verdad es capturarla donde ya pasa el dato gratis — takana YA descarga y
|
|
# extrae cada tarball al construir, así que la fase de fetch puede guardar el COPYING/LICENSE del
|
|
# árbol junto al artefacto, sin descargas extra. Y el texto de la licencia dentro del paquete conviene
|
|
# inyectarlo en `takana pack`, NO en la fase install de la receta: pack es aguas abajo del
|
|
# ArtifactHash, así que cumplir la GPL no cuesta reconstruir el corpus. Ver SDD 19.
|
|
#
|
|
# Uso: scripts/licencias.sh informe de cobertura (default, no toca nada)
|
|
# scripts/licencias.sh --sembrar escribe las licencias de la tabla curada
|
|
# scripts/licencias.sh --faltan lista las recetas sin licencia, para trabajarlas
|
|
set -uo pipefail
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$ROOT"
|
|
TABLA="${TABLA:-docs/licencias-conocidas.tsv}"
|
|
MODO="${1:-informe}"
|
|
|
|
recetas() { ls recipes/*.toml recipes/incoming-*/*.toml 2>/dev/null; }
|
|
|
|
# Un campo `license` de verdad: clave a principio de línea, ANTES del primer `[table]`.
|
|
# (Ver arriba: después de un `[table]` la clave pertenece a la tabla y no la lee el parser.)
|
|
tiene_licencia() {
|
|
awk '/^\[/{exit} /^[[:space:]]*license[[:space:]]*=/{print "si"; exit}' "$1" | grep -q si
|
|
}
|
|
|
|
licencia_de() {
|
|
awk -F'"' '/^\[/{exit} /^[[:space:]]*license[[:space:]]*=/{print $2; exit}' "$1"
|
|
}
|
|
|
|
case "$MODO" in
|
|
# ── sembrar: escribir las licencias de la tabla curada ──────────────────────────────────────────
|
|
--sembrar)
|
|
[ -s "$TABLA" ] || { echo "!! falta la tabla curada: $TABLA"; exit 1; }
|
|
puestas=0; ya=0; sin_receta=0; corregidas=0
|
|
# TRES campos, no dos: `licencias-detectadas.tsv` añade una tercera columna con el `owner/repo`
|
|
# consultado (la procedencia, para poder auditar). Leyendo sólo dos variables, `lic` se tragaría
|
|
# también el slug y se escribiría `license = "MIT<TAB>owner/repo"` en la receta — basura silenciosa,
|
|
# porque nada valida el contenido. `_resto` la absorbe.
|
|
while IFS=$'\t' read -r pkg lic _resto; do
|
|
case "$pkg" in ''|'#'*) continue ;; esac
|
|
[ -n "${lic:-}" ] || continue
|
|
# ÚLTIMA BARRERA antes de tocar una receta: la licencia tiene que TENER FORMA de expresión SPDX.
|
|
# Sin esto, cualquier fallo de una fuente aguas arriba se escribe como si fuera un dato — y el
|
|
# 2026-08-07 pasó: un cuerpo de error JSON de la API de GitHub acabó dentro de 5 recetas, con
|
|
# comillas, rompiendo el TOML y dejando de calcularse su ArtifactHash. Validar en el sitio que
|
|
# ESCRIBE es lo que corta la clase entera de fallo, no sólo el caso conocido.
|
|
case "$lic" in
|
|
*[!A-Za-z0-9.+\ -]*) echo " ?? $pkg: descarto licencia con forma inválida: $(printf '%.40s' "$lic")"; continue ;;
|
|
esac
|
|
hallada=0
|
|
for f in recipes/"$pkg".toml recipes/incoming-*/"$pkg".toml; do
|
|
[ -f "$f" ] || continue
|
|
hallada=1
|
|
if tiene_licencia "$f"; then
|
|
# FORZAR=1: reemplazar lo ya declarado. Sólo tiene sentido con una fuente MÁS autoritativa
|
|
# que la que puso el valor actual — el caso real es `licencias-cargo.tsv`, la declaración
|
|
# del propio autor, que corrige los dos defectos de la detección por API (SPDX ambiguo y
|
|
# licencias dobles colapsadas). Con la tabla curada NO se usa: pisaría a mano lo verificado.
|
|
if [ "${FORZAR:-0}" = 1 ] && [ "$(licencia_de "$f")" != "$lic" ]; then
|
|
esc=$(printf '%s' "$lic" | sed 's/[&/\]/\\&/g')
|
|
sed -i "0,/^[[:space:]]*license[[:space:]]*=.*/s//license = \"$esc\"/" "$f"
|
|
corregidas=$((corregidas+1))
|
|
else
|
|
ya=$((ya+1))
|
|
fi
|
|
continue
|
|
fi
|
|
# Insertar DESPUÉS de la línea `version = ...`, que está en la raíz del TOML y antes de
|
|
# cualquier `[table]`. Es el único sitio seguro (ver la trampa de TOML en la cabecera).
|
|
if grep -qE '^version[[:space:]]*=' "$f"; then
|
|
awk -v L="license = \"$lic\"" '
|
|
{print}
|
|
!done && /^version[[:space:]]*=/ {print L; done=1}
|
|
' "$f" > "$f.tmp" && mv "$f.tmp" "$f"
|
|
puestas=$((puestas+1))
|
|
else
|
|
echo " ?? $pkg: sin línea `version` en la raíz, no toco el fichero"
|
|
fi
|
|
done
|
|
[ "$hallada" = 0 ] && sin_receta=$((sin_receta+1))
|
|
done < "$TABLA"
|
|
echo "==> sembradas $puestas · corregidas $corregidas · ya tenían $ya · sin receta $sin_receta"
|
|
echo "==> verificá que el hash NO se movió: ./scripts/store-gc.sh (debe dar los mismos vigentes)"
|
|
;;
|
|
|
|
# ── faltan: la lista de trabajo ─────────────────────────────────────────────────────────────────
|
|
--faltan)
|
|
recetas() { ls recipes/*.toml recipes/incoming-*/*.toml 2>/dev/null; }
|
|
for f in $(recetas); do tiene_licencia "$f" || basename "$f" .toml; done
|
|
;;
|
|
|
|
# ── revisar: las que están declaradas pero con un SPDX AMBIGUO ─────────────────────────────────
|
|
# La detección por API de GitHub deja dos imprecisiones conocidas, y esto las hace contables en vez
|
|
# de esconderlas:
|
|
#
|
|
# 1. SPDX OBSOLETOS Y AMBIGUOS. GitHub devuelve `GPL-3.0`, `GPL-2.0`, `LGPL-2.1`, `LGPL-3.0`,
|
|
# `AGPL-3.0` — identificadores que SPDX declaró obsoletos **precisamente porque son ambiguos**:
|
|
# no distinguen «sólo esta versión» (`-only`) de «esta o posterior» (`-or-later`), y esa
|
|
# diferencia tiene efectos legales reales sobre con qué se puede combinar el paquete.
|
|
# **NO se normalizan a ciegas**: mapear `GPL-3.0` → `GPL-3.0-or-later` sería inventar el dato que
|
|
# falta, que es justo lo que este fichero prohíbe. Se marcan para que un humano las resuelva
|
|
# mirando la cabecera del fuente.
|
|
#
|
|
# 2. LICENCIAS DOBLES COLAPSADAS. La API devuelve UNA licencia; muchos proyectos Rust son
|
|
# «MIT OR Apache-2.0» y sólo se recoge una de las dos (p.ej. `fd` quedó como Apache-2.0). No es
|
|
# falso —Apache-2.0 sí es una de las opciones concedidas, y cumplir una basta— pero es
|
|
# INCOMPLETO: pierde una opción que el autor otorgó. Se resuelve con el cierre estructural
|
|
# (leer el `license` del Cargo.toml en la fase de fetch), no a mano.
|
|
--revisar)
|
|
n=0
|
|
for f in $(recetas); do
|
|
l=$(licencia_de "$f")
|
|
case "$l" in
|
|
GPL-[0-9]*|LGPL-[0-9]*|AGPL-[0-9]*)
|
|
case "$l" in *-only|*-or-later|*OR*|*AND*|*WITH*) ;; *) echo "$(basename "$f" .toml) $l"; n=$((n+1));; esac ;;
|
|
esac
|
|
done
|
|
echo "==> $n recetas con SPDX ambiguo (falta -only / -or-later). Resolver mirando el fuente." >&2
|
|
;;
|
|
|
|
# ── informe (default) ───────────────────────────────────────────────────────────────────────────
|
|
*)
|
|
tot=0; con=0
|
|
: > /tmp/.lic-cuenta.$$
|
|
for f in $(recetas); do
|
|
tot=$((tot+1))
|
|
if tiene_licencia "$f"; then con=$((con+1)); licencia_de "$f" >> /tmp/.lic-cuenta.$$; fi
|
|
done
|
|
pct=0; [ "$tot" -gt 0 ] && pct=$((con*100/tot))
|
|
echo "==> licencias declaradas: $con de $tot recetas (${pct}%)"
|
|
|
|
# ── GUARDIÁN: licencias con FORMA INVÁLIDA ───────────────────────────────────────────────────
|
|
# El 2026-08-07 se escribió el cuerpo de un error JSON de la API de GitHub dentro de 5 recetas
|
|
# (`license = "{"message":"Not Found",...}"`). Las comillas rompen el TOML ⇒ esas recetas dejaron
|
|
# de parsear y de tener ArtifactHash, y así se COMMITEARON. No lo vio nadie leyendo: lo cazó la
|
|
# comparación de hashes. Por eso el informe lo comprueba SIEMPRE y sale distinto de cero: un dato
|
|
# con forma imposible es un fallo aguas arriba disfrazado de dato, y hay que verlo sin buscarlo.
|
|
malas=0
|
|
for f in $(recetas); do
|
|
l=$(licencia_de "$f")
|
|
[ -z "$l" ] && continue
|
|
case "$l" in *[!A-Za-z0-9.+\ -]*) echo "!! FORMA INVÁLIDA en $f: $(printf '%.50s' "$l")"; malas=$((malas+1));; esac
|
|
done
|
|
if [ "$malas" -gt 0 ]; then
|
|
echo "!! $malas recetas con licencia de forma imposible — probablemente TOML roto. Arreglar YA."
|
|
exit 1
|
|
fi
|
|
if [ "$con" -gt 0 ]; then
|
|
echo "==> reparto:"
|
|
sort /tmp/.lic-cuenta.$$ | uniq -c | sort -rn | head -25 | sed 's/^/ /'
|
|
fi
|
|
rm -f /tmp/.lic-cuenta.$$
|
|
[ "$con" -lt "$tot" ] && echo "==> faltan $((tot-con)). Lista: scripts/licencias.sh --faltan"
|
|
echo "==> recordá: NO se rellena a ojo. Sin evidencia, se deja vacío y se cuenta (ver cabecera)."
|
|
;;
|
|
esac
|