licencias: 96% (1124/1164) — --fetch para los tarballs que no estaban en caché
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.
This commit is contained in:
@@ -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'
|
||||
|
||||
|
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 <receta> 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:
|
||||
|
||||
Reference in New Issue
Block a user