#!/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; }