licencias: resolver -only vs -or-later CON LA CITA — y tres «pruebas» que no probaban nada
Los identificadores obsoletos (`GPL-2.0`, `LGPL-2.1`, `AGPL-3.0`) no dicen si el proyecto
concede «sólo esta versión» o «ésta o cualquier posterior», y eso decide con qué se puede
combinar el paquete y bajo qué términos puede redistribuirlo quien lo reciba.
NO SE PUEDE MIRAR EL COPYING, que es la trampa evidente: el texto de la GPL es IDÉNTICO en
los dos casos —es la licencia, no la concesión— y encima su apéndice «cómo aplicar la
licencia» contiene literalmente «or (at your option) any later version», así que buscarla
ahí da SIEMPRE positivo y parece evidencia siendo plantilla. Misma trampa que la regla del
`.a` no-PIC, donde grep contaba reubicaciones de `.debug_*`. La concesión vive en las
cabeceras de los fuentes y en el README; ahí se busca, excluyendo los ficheros de licencia.
SE GUARDA LA CITA, no sólo el veredicto: fichero + frase exacta que decidió. Una licencia es
una afirmación legal y quien la revise tiene que poder ver POR QUÉ dice lo que dice sin
repetir el trabajo.
Y eso pagó de inmediato: de las 12 primeras resoluciones, TRES eran evidencia inválida y
sólo se vieron porque estaba la cita. Cada una de una clase distinta:
· caligula — la frase salía de `checks/headless/expected.iso`, un FIXTURE DE TEST;
· libnl — de `include/linux-private/linux/seg6.h`, una cabecera del KERNEL vendorizada.
La licencia del kernel no es la de libnl;
· lm-sensors — declarado GPL-2.0 y la cita decía «version 2.1 of the License», que es la
LGPL. La frase era real pero no sostenía lo que se le atribuía.
Las tres clases quedan filtradas en el script: ficheros de licencia, rutas de test/fixture, y
código vendorizado; más una comprobación nueva de que **la cita hable de la misma versión que
la licencia declarada**.
Sembradas las 9 auditadas (13 ficheros; algunas viven en varias colas). Ambiguas: 55 → 41.
Las 40 sin evidencia quedan listadas y siguen contadas — la mayoría son Go y la familia
cosmic, donde la frase no aparece fuera del COPYING.
Hashes verificados: 0 movidos.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Executable
+100
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env bash
|
||||
# licencias-desambiguar.sh — resuelve `-only` vs `-or-later` en los SPDX obsoletos, CON LA CITA.
|
||||
#
|
||||
# ── EL PROBLEMA QUE CIERRA ─────────────────────────────────────────────────────────────────────
|
||||
# La API de licencias de GitHub devuelve identificadores obsoletos (`GPL-2.0`, `GPL-3.0`, `LGPL-2.1`,
|
||||
# `AGPL-3.0`). SPDX los declaró obsoletos **porque son ambiguos**: no dicen si el proyecto concede
|
||||
# «sólo esta versión» o «ésta o cualquier posterior». La diferencia no es cosmética — decide con qué
|
||||
# se puede combinar el paquete y bajo qué términos puede redistribuirlo quien lo reciba.
|
||||
#
|
||||
# ── POR QUÉ NO SE PUEDE MIRAR EL COPYING (la trampa) ───────────────────────────────────────────
|
||||
# El fichero COPYING de la GPL es **idéntico** en los dos casos: es el texto de la licencia, no la
|
||||
# concesión. Y encima su apéndice «cómo aplicar la licencia a tus programas» contiene literalmente la
|
||||
# frase «or (at your option) any later version» ⇒ buscarla en el COPYING da SIEMPRE positivo y
|
||||
# parecería evidencia siendo texto de plantilla. Es la misma trampa que la regla del `.a` no-PIC,
|
||||
# donde `grep R_X86_64_32` contaba reubicaciones de `.debug_*` y mentía.
|
||||
#
|
||||
# La concesión vive en las **cabeceras de los fuentes** y en el README. Por eso acá se busca la frase
|
||||
# en TODO el repo y se **descartan explícitamente** COPYING/LICENSE/LICENCE: si sólo aparece ahí, no
|
||||
# hay evidencia y el paquete se deja para el humano.
|
||||
#
|
||||
# ── SE GUARDA LA CITA, NO SÓLO EL VEREDICTO ────────────────────────────────────────────────────
|
||||
# Cada línea de salida lleva el fichero y la frase exacta que decidió. Una licencia es una afirmación
|
||||
# legal: quien la revise tiene que poder ver POR QUÉ dice lo que dice sin repetir el trabajo. Un
|
||||
# veredicto sin su prueba obliga a confiar; con la prueba, se audita en diez segundos.
|
||||
#
|
||||
# Ejemplo real (htop): yo lo habría puesto `-only` de memoria. `htop.c` dice «Released under the GNU
|
||||
# GPLv2+» y el README «version 2 (GPL-2.0) or, at your option, any later version» ⇒ es `-or-later`.
|
||||
# La memoria se equivoca; la cita no.
|
||||
#
|
||||
# Uso: scripts/licencias-desambiguar.sh (escribe docs/licencias-desambiguadas.tsv)
|
||||
set -uo pipefail
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$ROOT"
|
||||
SALIDA="${SALIDA:-docs/licencias-desambiguadas.tsv}"
|
||||
command -v gh >/dev/null && gh auth status >/dev/null 2>&1 || { echo "!! 'gh' ausente o sin auth"; exit 1; }
|
||||
TMP=$(mktemp -d); trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
# Las ambiguas con fuente en GitHub. (La búsqueda de código de GitHub sólo funciona sobre GitHub;
|
||||
# el resto se queda para el humano, y eso se dice al final en vez de disimularlo.)
|
||||
./scripts/licencias.sh --revisar 2>/dev/null | while IFS=$'\t' read -r n lic; do
|
||||
[ -z "${n:-}" ] && continue
|
||||
f=$(ls "recipes/$n.toml" 2>/dev/null | head -1)
|
||||
[ -z "$f" ] && f=$(ls recipes/incoming-*/"$n.toml" 2>/dev/null | head -1)
|
||||
[ -z "$f" ] && continue
|
||||
u=$(grep -m1 -E '^[[:space:]]*(tarball|repo)[[:space:]]*=' "$f" 2>/dev/null)
|
||||
case "$u" in *github.com*) ;; *) continue ;; esac
|
||||
slug=$(printf '%s\n' "$u" | sed -E 's#.*github\.com[:/]+([^/"]+)/([^/"]+).*#\1/\2#; s/\.git$//')
|
||||
case "$slug" in */*) printf '%s\t%s\t%s\n' "$n" "$lic" "$slug" ;; esac
|
||||
done | sort -u > "$TMP/obj.tsv"
|
||||
echo "==> ambiguas con fuente en GitHub: $(wc -l < "$TMP/obj.tsv")"
|
||||
|
||||
# En SERIE: la búsqueda de código de GitHub va a 10 consultas por minuto. Paralelizar sólo consigue
|
||||
# que devuelva 403 y que la mitad se pierda en silencio, que es peor que tardar.
|
||||
: > "$TMP/ok.tsv"; : > "$TMP/no.tsv"
|
||||
while IFS=$'\t' read -r n lic slug; do
|
||||
paths=$(gh api -X GET search/code -f q="repo:$slug \"any later version\"" \
|
||||
--jq '[.items[].path] | join(" ")' 2>/dev/null)
|
||||
# Tres clases de fichero que PARECEN evidencia y no lo son. Las tres aparecieron en la primera
|
||||
# corrida, y sólo se vieron porque guardamos la cita:
|
||||
# · ficheros de licencia → la frase es plantilla del propio COPYING (ver cabecera);
|
||||
# · FIXTURES DE TEST → `caligula` "probaba" ser GPL-3.0-or-later por una frase dentro de
|
||||
# `checks/headless/expected.iso`, una imagen de prueba;
|
||||
# · CÓDIGO VENDORIZADO → `libnl` citaba `include/linux-private/linux/seg6.h`, una cabecera del
|
||||
# KERNEL incrustada. La licencia del kernel no es la de libnl.
|
||||
util=""
|
||||
for p in $paths; do
|
||||
bn=$(basename "$p" | tr '[:upper:]' '[:lower:]')
|
||||
case "$bn" in copying*|license*|licence*|copyright) continue ;; esac
|
||||
case "$p" in
|
||||
*test*|*Test*|*fixture*|*checks/*|*testdata/*|*.iso|*.bin|*.img) continue ;;
|
||||
*vendor/*|*third_party/*|*3rdparty/*|*linux-private/*|*/deps/*|*external/*) continue ;;
|
||||
esac
|
||||
util="$p"; break
|
||||
done
|
||||
if [ -z "$util" ]; then printf '%s\t%s\t%s\n' "$n" "$lic" "$slug" >> "$TMP/no.tsv"; sleep 6; continue; fi
|
||||
cita=$(gh api "repos/$slug/contents/$util" --jq .content 2>/dev/null | base64 -d 2>/dev/null \
|
||||
| grep -m1 -i "any later version" | tr -s ' \t' ' ' | sed 's/^ *//' | cut -c1-120)
|
||||
# ── LA CITA TIENE QUE HABLAR DE LA MISMA VERSIÓN QUE LA LICENCIA DECLARADA ───────────────────
|
||||
# `lm-sensors` estaba declarado GPL-2.0 y su cita decía «version 2.1 of the License», que es la
|
||||
# LGPL: la frase era real pero NO sostenía lo que se le atribuía. Una evidencia que habla de otra
|
||||
# licencia no es evidencia de ésta.
|
||||
ver=$(printf '%s' "$lic" | sed -E 's/^A?L?GPL-([0-9.]+).*/\1/')
|
||||
case "$cita" in
|
||||
*"$ver"*) ;;
|
||||
*) printf '%s\t%s\t%s\n' "$n" "$lic" "$slug" >> "$TMP/no.tsv"; sleep 6; continue ;;
|
||||
esac
|
||||
nuevo=$(printf '%s' "$lic" | sed -E 's/^(A?L?GPL-[0-9.]+)$/\1-or-later/')
|
||||
printf '%s\t%s\t%s :: %s\n' "$n" "$nuevo" "$util" "${cita:-(sin cita legible)}" >> "$TMP/ok.tsv"
|
||||
sleep 6
|
||||
done < "$TMP/obj.tsv"
|
||||
|
||||
N=$(wc -l < "$TMP/ok.tsv"); M=$(wc -l < "$TMP/no.tsv")
|
||||
{
|
||||
echo "# licencias-desambiguadas.tsv — GENERADO por scripts/licencias-desambiguar.sh."
|
||||
echo "# Resuelve -only vs -or-later buscando la concesión FUERA del COPYING (donde la frase es"
|
||||
echo "# plantilla y daría siempre positivo). Tercera columna: fichero :: cita que lo decidió."
|
||||
echo "# Resueltas $N · sin evidencia (quedan para el humano) $M"
|
||||
sort "$TMP/ok.tsv"
|
||||
} > "$SALIDA"
|
||||
echo "==> resueltas $N · sin evidencia $M → $SALIDA"
|
||||
[ "$M" -gt 0 ] && { echo "==> sin evidencia:"; cut -f1 "$TMP/no.tsv" | tr '\n' ' '; echo; }
|
||||
Reference in New Issue
Block a user