From 96855001c6e9276014d68fc9f9f85ec765673e28 Mon Sep 17 00:00:00 2001 From: Sergio Date: Sat, 5 Sep 2026 15:43:51 +0000 Subject: [PATCH] =?UTF-8?q?licencias:=2096%=20(1124/1164)=20=E2=80=94=20`-?= =?UTF-8?q?-fetch`=20para=20los=20tarballs=20que=20no=20estaban=20en=20cac?= =?UTF-8?q?h=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ocho paquetes más (giflib, pcre2, nghttp2, libogg, libvorbis, speexdsp, libuv, tea) con la misma regla de evidencia. Su tarball no estaba en `work/tarballs/`, así que el modo `--fetch` lo baja a un temporal, lo verifica contra el sha256 QUE LA RECETA PINEA —o es el árbol exacto o no se mira— y lo borra. No se escribe en la caché de hammer a propósito: es suya, y un fichero puesto ahí por otro camino es una vía de envenenamiento que nadie audita. Dos correcciones al criterio, las dos porque produjo una afirmación falsa: · `gmp` salía GPL-3.0-or-later. Su `COPYING` es la GPLv3, pero al lado trae `COPYING.LESSERv3` y `COPYINGv2` porque la biblioteca es LGPL. La regla anterior —«el `COPYING` a secas gana al sufijado»— arregla socat, cuyo `COPYING.OpenSSL` es una excepción, y rompe gmp, donde el extra nombra otra VERSIÓN y no una dep. Esa diferencia es demasiado fina para codificarla sin equivocarse, así que ahora se identifican TODOS los ficheros de la raíz y sólo hay veredicto si dicen lo mismo. gmp, ffmpeg y los tres `gi-*` quedan pendientes, que es lo correcto: en ffmpeg la respuesta ni siquiera está en el árbol, la deciden los flags de la receta. · Pero «varios ficheros» no es «ambigüedad»: el `COPYING` de pcre2 son dos líneas apuntando a `LICENCE.md`, y los dos dicen BSD-3. Por eso se unen las identificaciones en vez de contar ficheros — ambiguo es que digan cosas DISTINTAS. Y el TSV pasa a ser ACUMULATIVO. Una receta ya sembrada sale del conjunto de entrada porque ya tiene `license`, así que regenerar el fichero entero borraba su cita — y la cita es el rastro de auditoría, lo único que deja revisar un veredicto sin repetir el trabajo. Un fichero de evidencia que se olvida de lo que ya probó no es evidencia. Los 10 ArtifactHash afectados, idénticos antes y después. --- docs/licencias-evidencia.tsv | 20 ++-- recipes/giflib.toml | 1 + recipes/incoming-cosmic/pcre2-shared.toml | 1 + recipes/incoming-gnome/nghttp2.toml | 1 + recipes/incoming-kde/libogg.toml | 1 + recipes/incoming-kde/libvorbis.toml | 1 + recipes/incoming-kde/pcre2-shared.toml | 1 + recipes/incoming-kde/speexdsp.toml | 1 + recipes/libuv.toml | 1 + recipes/pcre2-shared.toml | 1 + recipes/tea.toml | 1 + scripts/licencias-tarball.py | 107 ++++++++++++++++++---- 12 files changed, 112 insertions(+), 25 deletions(-) diff --git a/docs/licencias-evidencia.tsv b/docs/licencias-evidencia.tsv index 8824b860..72a9705b 100644 --- a/docs/licencias-evidencia.tsv +++ b/docs/licencias-evidencia.tsv @@ -1,29 +1,37 @@ # licencias-evidencia.tsv — lo generó scripts/licencias-tarball.py desde el # TARBALL PINEADO de cada receta. Tres columnas: paquete, SPDX, y la CITA que lo # decidió. REVISAR A MANO antes de sembrar: es evidencia, no un veredicto firmado. +# Se ACUMULA: las filas de lo ya sembrado se conservan como rastro de auditoría. adwaita-hello LGPL-2.1-or-later meson.build: license: 'LGPL-2.1-or-later' +dav1d BSD-2-Clause COPYING (texto de BSD-2-Clause) dwarves GPL-2.0-only COPYING + concesión en btf_encoder.c: …/* SPDX-License-Identifier: GPL-2.0-only Copyright (C) 20… -elfutils-libdw GPL-3.0-or-later COPYING + concesión en tests/run-eu-search-die.sh: …ion; either version 3 of the License, or # (at your option) any later version. # # elfutils is di… elfutils GPL-3.0-or-later COPYING + concesión en tests/run-eu-search-die.sh: …ion; either version 3 of the License, or # (at your option) any later version. # # elfutils is di… +elfutils-libdw GPL-3.0-or-later COPYING + concesión en tests/run-eu-search-die.sh: …ion; either version 3 of the License, or # (at your option) any later version. # # elfutils is di… +exiv2 GPL-2.0-or-later COPYING + concesión en README.md: …ation; either version 2 of the License, or (at your option) any later version. Exiv2 program is… file BSD-2-Clause COPYING (texto de BSD-2-Clause) +giflib MIT COPYING (texto de MIT) go BSD-3-Clause LICENSE (texto de BSD-3-Clause) gtk4-hello LGPL-2.1-or-later meson.build: license: 'LGPL-2.1-or-later' hammer-edit LGPL-2.1-or-later meson.build: license: 'LGPL-2.1-or-later' -dav1d BSD-2-Clause COPYING (texto de BSD-2-Clause) -exiv2 GPL-2.0-or-later COPYING + concesión en README.md: …ation; either version 2 of the License, or (at your option) any later version. Exiv2 program is… intltool GPL-2.0-or-later COPYING + concesión en intltool-extract.in: …on; either version 2 of the # License, or (at your option) any later version. # # Intltool is d… -libpcap BSD-3-Clause LICENSE (texto de BSD-3-Clause) -libzip BSD-3-Clause LICENSE (texto de BSD-3-Clause) -mtdev MIT COPYING (texto de MIT) json-c MIT COPYING (texto de MIT) +libogg BSD-3-Clause COPYING (texto de BSD-3-Clause) +libpcap BSD-3-Clause LICENSE (texto de BSD-3-Clause) libsodium ISC LICENSE (texto de ISC) libssh2 BSD-3-Clause COPYING (texto de BSD-3-Clause) +libuv MIT LICENSE (texto de MIT) +libvorbis BSD-3-Clause COPYING (texto de BSD-3-Clause) +libzip BSD-3-Clause LICENSE (texto de BSD-3-Clause) mesa-llvmpipe MIT meson.build: license: 'MIT' mesa-swrast MIT meson.build: license: 'MIT' mtdev MIT COPYING (texto de MIT) nano GPL-3.0-or-later COPYING + concesión en NEWS: …Finally, nano is now licensed under the GNU GPL version 3 or later, and its documentat… +nghttp2 MIT COPYING (texto de MIT) +pcre2-shared BSD-3-Clause LICENCE.md (texto de BSD-3-Clause) scdoc MIT COPYING (texto de MIT) seatd MIT meson.build: license: 'MIT' socat GPL-2.0-or-later COPYING + concesión en socat_buildscript_for_android.sh: …; either version 2.1 of the License, or (at your option) any later version. The GNU C Libr… sourceview-hello LGPL-2.1-or-later meson.build: license: 'LGPL-2.1-or-later' +speexdsp BSD-3-Clause COPYING (texto de BSD-3-Clause) +tea MIT LICENSE (texto de MIT) tllist MIT meson.build: license: 'MIT' diff --git a/recipes/giflib.toml b/recipes/giflib.toml index ad213ab9..0de2c0e1 100644 --- a/recipes/giflib.toml +++ b/recipes/giflib.toml @@ -3,6 +3,7 @@ # sha256 del tarball (el wrapper lo calcula), y adaptar build/install del shell de abuild. name = "giflib" version = "5.2.2" +license = "MIT" [source] tarball = "https://downloads.sourceforge.net/sourceforge/giflib/giflib-5.2.2.tar.gz" diff --git a/recipes/incoming-cosmic/pcre2-shared.toml b/recipes/incoming-cosmic/pcre2-shared.toml index 7ebc72d7..4dcb0927 100644 --- a/recipes/incoming-cosmic/pcre2-shared.toml +++ b/recipes/incoming-cosmic/pcre2-shared.toml @@ -7,6 +7,7 @@ # --disable-symvers como el canónico (musl no maneja symbol versioning de la forma que asume pcre2). name = "pcre2-shared" version = "10.47" +license = "BSD-3-Clause" [source] tarball = "https://github.com/PhilipHazel/pcre2/releases/download/pcre2-10.47/pcre2-10.47.tar.bz2" diff --git a/recipes/incoming-gnome/nghttp2.toml b/recipes/incoming-gnome/nghttp2.toml index f749b3b5..654639a7 100644 --- a/recipes/incoming-gnome/nghttp2.toml +++ b/recipes/incoming-gnome/nghttp2.toml @@ -10,6 +10,7 @@ # ISLA DINÁMICA: shared, porque libsoup y todo el stack GNOME de esta onda son .so. name = "nghttp2" version = "1.64.0" +license = "MIT" [source] tarball = "https://github.com/nghttp2/nghttp2/releases/download/v1.64.0/nghttp2-1.64.0.tar.xz" diff --git a/recipes/incoming-kde/libogg.toml b/recipes/incoming-kde/libogg.toml index 067e98a1..4e1123ed 100644 --- a/recipes/incoming-kde/libogg.toml +++ b/recipes/incoming-kde/libogg.toml @@ -1,6 +1,7 @@ # libogg 1.3.5 — contenedor Ogg (C). Dep de libvorbis (→ libcanberra → plasma-workspace). Autotools limpio. name = "libogg" version = "1.3.5" +license = "BSD-3-Clause" [source] tarball = "https://downloads.xiph.org/releases/ogg/libogg-1.3.5.tar.xz" sha256 = "c4d91be36fc8e54deae7575241e03f4211eb102afb3fc0775fbbc1b740016705" diff --git a/recipes/incoming-kde/libvorbis.toml b/recipes/incoming-kde/libvorbis.toml index 0f302ee0..d4ee8541 100644 --- a/recipes/incoming-kde/libvorbis.toml +++ b/recipes/incoming-kde/libvorbis.toml @@ -1,6 +1,7 @@ # libvorbis 1.3.7 — códec Vorbis + vorbisfile (C). Lo exige libcanberra (lector de sonidos .ogg). Autotools. name = "libvorbis" version = "1.3.7" +license = "BSD-3-Clause" [source] tarball = "https://downloads.xiph.org/releases/vorbis/libvorbis-1.3.7.tar.xz" sha256 = "b33cc4934322bcbf6efcbacf49e3ca01aadbea4114ec9589d1b1e9d20f72954b" diff --git a/recipes/incoming-kde/pcre2-shared.toml b/recipes/incoming-kde/pcre2-shared.toml index 7ebc72d7..4dcb0927 100644 --- a/recipes/incoming-kde/pcre2-shared.toml +++ b/recipes/incoming-kde/pcre2-shared.toml @@ -7,6 +7,7 @@ # --disable-symvers como el canónico (musl no maneja symbol versioning de la forma que asume pcre2). name = "pcre2-shared" version = "10.47" +license = "BSD-3-Clause" [source] tarball = "https://github.com/PhilipHazel/pcre2/releases/download/pcre2-10.47/pcre2-10.47.tar.bz2" diff --git a/recipes/incoming-kde/speexdsp.toml b/recipes/incoming-kde/speexdsp.toml index 07956aa6..718dda26 100644 --- a/recipes/incoming-kde/speexdsp.toml +++ b/recipes/incoming-kde/speexdsp.toml @@ -2,6 +2,7 @@ # PulseAudio lo usa para el remuestreo de audio (su resampler por defecto). name = "speexdsp" version = "1.2.1" +license = "BSD-3-Clause" [source] tarball = "https://downloads.us.xiph.org/releases/speex/speexdsp-1.2.1.tar.gz" diff --git a/recipes/libuv.toml b/recipes/libuv.toml index 9b020452..ff01cb35 100644 --- a/recipes/libuv.toml +++ b/recipes/libuv.toml @@ -1,6 +1,7 @@ # libuv 1.52.1 — lib C base (de-Alpinizada, Etapa G). compiler=zig-cc (migrado de gcc, matar-gcc 2026-07-16), configure-split, estático. name = "libuv" version = "1.52.1" +license = "MIT" [source] tarball = "https://dist.libuv.org/dist/v1.52.1/libuv-v1.52.1.tar.gz" diff --git a/recipes/pcre2-shared.toml b/recipes/pcre2-shared.toml index 7ebc72d7..4dcb0927 100644 --- a/recipes/pcre2-shared.toml +++ b/recipes/pcre2-shared.toml @@ -7,6 +7,7 @@ # --disable-symvers como el canónico (musl no maneja symbol versioning de la forma que asume pcre2). name = "pcre2-shared" version = "10.47" +license = "BSD-3-Clause" [source] tarball = "https://github.com/PhilipHazel/pcre2/releases/download/pcre2-10.47/pcre2-10.47.tar.bz2" diff --git a/recipes/tea.toml b/recipes/tea.toml index 90e54d9e..a1ebba74 100644 --- a/recipes/tea.toml +++ b/recipes/tea.toml @@ -3,6 +3,7 @@ # sha256 del tarball (el wrapper lo calcula), y adaptar build/install del shell de abuild. name = "tea" version = "0.9.2" +license = "MIT" [source] tarball = "https://gitea.com/gitea/tea/archive/v0.9.2.tar.gz" diff --git a/scripts/licencias-tarball.py b/scripts/licencias-tarball.py index 9b3f440a..82e9d227 100755 --- a/scripts/licencias-tarball.py +++ b/scripts/licencias-tarball.py @@ -31,14 +31,26 @@ quien la revise tiene que poder auditarla sin repetir el trabajo. Lo que no alca veredicto sale como `pendiente` con lo que SÍ se encontró, para que el humano arranque desde ahí. NADA se escribe en las recetas: la salida es un TSV que revisa una persona y siembra `licencias.sh`. +── EL MODO `--fetch` ─────────────────────────────────────────────────────────────────────────── +Por defecto NO toca la red: mira sólo lo que ya está en `work/tarballs/`. Con `--fetch` baja los +tarballs que faltan a un temporal, los verifica por sha256 —el mismo sha que la receta pinea, así +que o es el árbol exacto o no se mira— y los borra al terminar. No escribe en la caché de hammer a +propósito: la caché es suya y un fichero puesto ahí por otro camino es una vía de envenenamiento +que nadie audita. + Uso: scripts/licencias-tarball.py informe + escribe docs/licencias-evidencia.tsv + scripts/licencias-tarball.py --fetch además baja los tarballs que falten (red) scripts/licencias-tarball.py una sola, con el detalle a la vista """ import os import re import sys import glob +import shutil +import hashlib import tarfile +import tempfile +import subprocess import tomllib RAIZ = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -298,20 +310,29 @@ def analizar(tar_path): cita_only = f"{rel}: …{texto[max(0, mm.start()-60):mm.end()+20].strip()}…" if candidatos: - exactos = [c for c in candidatos if c[0] == 0] - if not exactos and len(candidatos) > 1: - # ffmpeg trae COPYING.GPLv2, COPYING.GPLv3, COPYING.LGPLv2.1 y COPYING.LGPLv3 en la - # raíz: cuál rige lo deciden los FLAGS de la receta (`--enable-gpl`), no el árbol. - # Elegir el primero por orden alfabético sería inventar. - return None, ("la raíz trae " + str(len(candidatos)) + " ficheros de licencia y ninguno " - "canónico (" + ", ".join(sorted(c[1] for c in candidatos)) - + "): decide la receta, no el árbol") - _, familia_fichero, texto = sorted(candidatos)[0] - hits = identificar(texto) - if len(hits) == 1: - familia = hits[0] - elif len(hits) > 1: - varias = hits + # Se identifica CADA fichero de la raíz y se unen los resultados. Que haya varios no es + # ambigüedad por sí solo: el `COPYING` de pcre2 son dos líneas que apuntan a `LICENCE.md`, + # y los dos dicen BSD-3. Ambiguo es que digan COSAS DISTINTAS. + # + # Probé antes con «el `COPYING` a secas gana al sufijado»: arregla socat (cuyo + # `COPYING.OpenSSL` es una excepción) y ROMPE gmp, que junto a `COPYING` (GPLv3) trae + # `COPYING.LESSERv3` porque la biblioteca es LGPL — el veredicto salía GPL-3.0-or-later, que + # es falso. La diferencia entre «el extra nombra una dep» y «el extra nombra otra versión» + # es demasiado fina para codificarla sin equivocarse, y equivocarse acá es afirmar algo que + # no es. ffmpeg es el caso límite: sus cuatro COPYING.* los desempatan los FLAGS de la + # receta (`--enable-gpl`), o sea que la respuesta ni siquiera está en el árbol. + vistos = {} + for _, ruta, texto in sorted(candidatos): + for h in identificar(texto): + vistos.setdefault(h, ruta) + if len(vistos) == 1: + familia, familia_fichero = next(iter(vistos.items())) + elif len(vistos) > 1: + return None, ("la raíz dice " + str(len(vistos)) + " licencias distintas (" + + ", ".join(f"{k} en {v}" for k, v in sorted(vistos.items())) + + "): la expresión la compone un humano") + else: + familia_fichero = sorted(candidatos)[0][1] # 1. Declaración del autor — si es un SPDX de verdad. meson deja escribir cualquier cadena, y # `gsd-schemas`/`libgdm` declaran «GPL2+», que NO es un identificador: sembrarlo sería meter @@ -358,8 +379,29 @@ def analizar(tar_path): return None, "sin COPYING ni declaración en el árbol" +def bajar_verificado(url, sha, destino): + """Baja `url` a `destino` y confirma su sha256. Devuelve el porqué si no se pudo.""" + r = subprocess.run( + ["curl", "-fsSL", "--max-time", "180", "-o", destino, url], + capture_output=True, text=True, + ) + if r.returncode != 0: + return f"curl falló ({r.returncode}): {r.stderr.strip()[:80]}" + h = hashlib.sha256() + with open(destino, "rb") as fh: + for bloque in iter(lambda: fh.read(1 << 20), b""): + h.update(bloque) + if h.hexdigest() != sha: + return f"sha256 NO coincide: bajó {h.hexdigest()[:16]}…, la receta pinea {sha[:16]}…" + return None + + def main(): - objetivo = sys.argv[1] if len(sys.argv) > 1 else None + args = [a for a in sys.argv[1:]] + con_red = "--fetch" in args + args = [a for a in args if a != "--fetch"] + objetivo = args[0] if args else None + tmp = tempfile.mkdtemp(prefix="licencias-") if con_red else None filas, pendientes, sin_tar = [], [], [] for f, d in recetas_sin_licencia(): @@ -372,14 +414,25 @@ def main(): continue tar_path = os.path.join(CACHE, sha + ".tar") if not os.path.exists(tar_path): - sin_tar.append((nombre, "tarball no cacheado")) - continue + url = d.get("source", {}).get("tarball") + if not (con_red and url): + sin_tar.append((nombre, "tarball no cacheado" + + ("" if con_red else " (probá --fetch)"))) + continue + tar_path = os.path.join(tmp, sha) + fallo = bajar_verificado(url, sha, tar_path) + if fallo: + sin_tar.append((nombre, fallo)) + continue spdx, cita = analizar(tar_path) if spdx: filas.append((nombre, spdx, una_linea(cita))) else: pendientes.append((nombre, una_linea(cita))) + if tmp: + shutil.rmtree(tmp, ignore_errors=True) + if objetivo: for n, s, c in filas: print(f"{n}\t{s}\n evidencia: {c}") @@ -387,12 +440,28 @@ def main(): print(f"{n}\tPENDIENTE — {una_linea(c)}") return + # ACUMULATIVO, no regenerado: una receta ya sembrada sale del conjunto de entrada (porque ya + # tiene `license`), así que reescribir el fichero entero borraría su cita. Y la cita es el + # rastro de auditoría — lo único que deja revisar un veredicto sin repetir el trabajo. Un + # fichero de evidencia que se olvida de lo que ya probó no es evidencia. + previas = {} + if os.path.exists(SALIDA): + for linea in open(SALIDA): + if linea.startswith("#") or not linea.strip(): + continue + partes = linea.rstrip("\n").split("\t") + if len(partes) == 3: + previas[partes[0]] = tuple(partes) + for n, sp, c in filas: + previas[n] = (n, sp, c) + with open(SALIDA, "w") as fh: fh.write("# licencias-evidencia.tsv — lo generó scripts/licencias-tarball.py desde el\n") fh.write("# TARBALL PINEADO de cada receta. Tres columnas: paquete, SPDX, y la CITA que lo\n") fh.write("# decidió. REVISAR A MANO antes de sembrar: es evidencia, no un veredicto firmado.\n") - for n, s, c in filas: - fh.write(f"{n}\t{s}\t{c}\n") + fh.write("# Se ACUMULA: las filas de lo ya sembrado se conservan como rastro de auditoría.\n") + for fila in sorted(previas.values()): + fh.write("\t".join(fila) + "\n") print(f"==> resueltas con evidencia del tarball: {len(filas)}") for n, s, c in filas: