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: