vigia-imagen: un módulo QML registrado en C++ no está «sin instalar» — se comprueba, no se recuerda
Los DOS únicos ✗ que reportaba este vigía eran falsos, y de la misma clase: un `import` no siempre se
satisface con un directorio y su `qmldir`. El greeter de la pantalla de bloqueo hace
`qmlRegisterUncreatableType<PamAuthenticator>("org.kde.kscreenlocker", …)` — el módulo existe SÓLO
dentro del proceso que carga ese `.qml` y en disco no hay nada que instalar. Buscarlo en el sistema
de ficheros da ✗ con la imagen perfecta.
Los dos, verificados uno por uno antes de tocar el script:
· `org.kde.kscreenlocker` ← `usr/lib64/libexec/kscreenlocker_greet`. Y valía la pena mirarlo: los
que lo importan son `lockscreen/LockScreenUi.qml` y `MainBlock.qml` de plasma-desktop, o sea la
pantalla de bloqueo de verdad. Si hubiera faltado no era un diálogo de ajustes.
· `org.kde.newstuff.core` ← `usr/lib/qt6/qml/org/kde/newstuff/libnewstuffqmlplugin.so`.
Antes esto se tapaba anotando el módulo a mano en `QML_EN_RUNTIME`. `registrado_en_binario()` lo
contesta ahora con evidencia —el URI queda como literal en `.rodata`, así que se busca en los ELF del
cierre— y además NOMBRA al registrador, que es lo que permite auditar el veredicto sin repetirlo.
Mira primero los artefactos cuyo nombre comparte una palabra con el URI, que es donde está casi
siempre, y sólo si no aparece recorre el resto.
⇒ la lista escrita a mano SE BORRA ENTERA. Al vaciarla, los cuatro módulos que tenía se resolvieron
solos, cada uno nombrando su binario (plasmashell, kwin_wayland). Y salió gratis un hallazgo que
ninguna lista podía dar: `org.kde.kwin.effect` no se importa en NINGÚN cierre — era una entrada
muerta, y nadie tenía cómo saberlo.
Los cinco perfiles quedan en ✓ de qml, sin un solo ✗ en todo el informe.
La regla detrás: un guardián que grita en falso no se endurece, se ignora. Cuando el 100% de sus
hallazgos son falsos positivos, el trabajo no es anotarlos — es enseñarle a preguntar bien.
This commit is contained in:
+72
-14
@@ -41,8 +41,9 @@
|
||||
# ══ RUIDO LEGÍTIMO, para poder triar ═══════════════════════════════════════════════════════════
|
||||
# Hay módulos QML que NO se instalan en disco porque el binario los REGISTRA en runtime
|
||||
# (`org.kde.plasma.shell` lo registra plasmashell; los `Qt*` los trae el propio Qt en otra ruta).
|
||||
# Están en QML_EN_RUNTIME y se excluyen; la lista se amplía con lo que se demuestre, no con lo que
|
||||
# se suponga. Todo lo demás que salga acá es accionable.
|
||||
# No se excluyen por una lista escrita a mano —eso envejece y no dice por qué— sino comprobando que
|
||||
# el URI esté como literal en un ELF del cierre: ver `registrado_en_binario()`. Se reportan aparte,
|
||||
# nombrando al registrador. Todo lo demás que salga acá es accionable.
|
||||
#
|
||||
# uso: python3 scripts/vigia-imagen.py [perfil...] [--fail] [--qml]
|
||||
# --fail → exit 1 si algún invariante falla; exit 2 si NO SE PUDO medir (nodos del cierre
|
||||
@@ -72,13 +73,10 @@ EXCEPCIONES = {
|
||||
"promover adwaita o breeze al corpus — decisión, no olvido",
|
||||
}
|
||||
|
||||
# Módulos QML que NADIE instala en disco y aun así resuelven: los registra un binario en runtime.
|
||||
QML_EN_RUNTIME = {
|
||||
"org.kde.plasma.shell", # plasmashell los registra al arrancar el shell
|
||||
"org.kde.plasma.shell.panel",
|
||||
"org.kde.kwin", # kwin_wayland registra su propia API de efectos
|
||||
"org.kde.kwin.effect",
|
||||
}
|
||||
# (Acá vivía `QML_EN_RUNTIME`, una lista escrita a mano de módulos que nadie instala en disco. La
|
||||
# reemplaza `registrado_en_binario()`, que contesta lo mismo con evidencia y además NOMBRA al
|
||||
# registrador. Al vaciarla, los cuatro que tenía se resolvieron solos —y `org.kde.kwin.effect` ni
|
||||
# siquiera se importa en ningún cierre: era una entrada muerta que nadie podía saber que sobraba.)
|
||||
|
||||
_cache = {}
|
||||
def artefacto(cola, nombre):
|
||||
@@ -104,16 +102,62 @@ ICONO_EXT = (".png", ".svg", ".svgz", ".xpm")
|
||||
FUENTE_EXT = (".ttf", ".otf", ".ttc", ".pfb", ".pcf", ".pcf.gz")
|
||||
|
||||
|
||||
def registrado_en_binario(uri, dirs):
|
||||
"""¿Algún ELF del cierre lleva `uri` como literal? Entonces lo registra en C++ y NO falta.
|
||||
|
||||
── POR QUÉ HACE FALTA ──────────────────────────────────────────────────────────────────────
|
||||
Un `import org.kde.kscreenlocker` no siempre se satisface con un directorio y su `qmldir`. El
|
||||
greeter de la pantalla de bloqueo hace `qmlRegisterUncreatableType<PamAuthenticator>(
|
||||
"org.kde.kscreenlocker", …)`: el módulo existe SÓLO dentro del proceso que carga ese `.qml`, y
|
||||
en disco no hay nada que instalar. Buscar el módulo en el sistema de ficheros da ✗ y la imagen
|
||||
está perfecta.
|
||||
|
||||
Medido el 2026-09-05: los DOS únicos ✗ de qml que reportaba este vigía eran de esta clase
|
||||
—`org.kde.kscreenlocker` en `libexec/kscreenlocker_greet` y `org.kde.newstuff.core` en
|
||||
`libnewstuffqmlplugin.so`— o sea que el 100% de sus hallazgos eran falsos. Y un guardián que
|
||||
grita en falso no se endurece: se ignora. Antes esto se tapaba anotando el módulo a mano en
|
||||
una lista escrita a mano, que envejece y no dice por qué.
|
||||
|
||||
El literal queda en `.rodata` del binario, así que la pregunta se contesta con evidencia. Se
|
||||
miran primero los artefactos cuyo nombre comparte una palabra con el URI (`kscreenlocker` →
|
||||
`kscreenlocker`), que es donde está casi siempre, y sólo si no aparece se recorre el resto.
|
||||
"""
|
||||
aguja = uri.encode()
|
||||
tokens = [t for t in re.split(r"[.\-_]", uri.lower()) if len(t) > 3]
|
||||
|
||||
def primero_los_parecidos(par):
|
||||
return 0 if any(t in par[0].lower() for t in tokens) else 1
|
||||
|
||||
for nombre, d in sorted(dirs.items(), key=primero_los_parecidos):
|
||||
for raiz, _, ficheros in os.walk(d):
|
||||
for f in ficheros:
|
||||
ruta = os.path.join(raiz, f)
|
||||
try:
|
||||
if os.path.getsize(ruta) > 256 * 1024 * 1024:
|
||||
continue
|
||||
with open(ruta, "rb") as fh:
|
||||
if fh.read(4) != b"\x7fELF":
|
||||
continue
|
||||
fh.seek(0)
|
||||
if aguja in fh.read():
|
||||
return "%s (%s)" % (os.path.relpath(ruta, d), nombre)
|
||||
except OSError:
|
||||
continue
|
||||
return None
|
||||
|
||||
|
||||
def escanear(nodos):
|
||||
"""Recorre los artefactos del cierre una sola vez y junta los cinco hechos."""
|
||||
temas, cursores, fuentes, terminales = {}, {}, 0, {}
|
||||
qml_modulos, qml_imports = set(), {}
|
||||
sin_artefacto = []
|
||||
dirs = {}
|
||||
for (cola, nombre) in sorted(nodos):
|
||||
d = artefacto(cola, nombre)
|
||||
if not d:
|
||||
sin_artefacto.append(nombre)
|
||||
continue
|
||||
dirs[nombre] = d
|
||||
# ── temas de iconos y de cursores ───────────────────────────────────────────────────────
|
||||
base = os.path.join(d, "usr/share/icons")
|
||||
if os.path.isdir(base):
|
||||
@@ -175,11 +219,19 @@ def escanear(nodos):
|
||||
continue
|
||||
if re.search(r"^Categories=.*TerminalEmulator", txt, re.M):
|
||||
terminales.setdefault(f[:-8], nombre)
|
||||
faltan_qml = {m: v for m, v in qml_imports.items()
|
||||
if m not in qml_modulos and m not in QML_EN_RUNTIME
|
||||
candidatos = {m: v for m, v in qml_imports.items()
|
||||
if m not in qml_modulos
|
||||
and not m.startswith(("QtQuick", "QtQml", "QtCore", "QtTest", "Qt5", "Qt."))
|
||||
and m != "Qt"}
|
||||
return temas, cursores, fuentes, terminales, faltan_qml, sin_artefacto
|
||||
# Último filtro, y el que decide: un módulo registrado en C++ no está «sin instalar».
|
||||
faltan_qml, en_binario = {}, {}
|
||||
for m, v in candidatos.items():
|
||||
donde = registrado_en_binario(m, dirs)
|
||||
if donde:
|
||||
en_binario[m] = donde
|
||||
else:
|
||||
faltan_qml[m] = v
|
||||
return temas, cursores, fuentes, terminales, faltan_qml, sin_artefacto, en_binario
|
||||
|
||||
|
||||
def main():
|
||||
@@ -196,7 +248,7 @@ def main():
|
||||
nodos = {n for n, ps in memb.items() if perfil in ps and n[0] != "wanted"}
|
||||
if not nodos:
|
||||
continue
|
||||
temas, cursores, fuentes, terminales, faltan_qml, sin_art = escanear(nodos)
|
||||
temas, cursores, fuentes, terminales, faltan_qml, sin_art, en_binario = escanear(nodos)
|
||||
reales = {t: v for t, v in temas.items() if t != "hicolor"}
|
||||
print("== %-20s %3d nodos del cierre" % (perfil, len(nodos)))
|
||||
parcial = bool(sin_art)
|
||||
@@ -237,9 +289,15 @@ def main():
|
||||
linea(bool(terminales), "terminal",
|
||||
", ".join("%s (%s)" % (k, v) for k, v in sorted(terminales.items()))
|
||||
or "NINGÚN emulador de terminal — no se puede salir de un fallo")
|
||||
nota = ""
|
||||
if en_binario:
|
||||
nota = " (%d registrado(s) en C++, no en disco)" % len(en_binario)
|
||||
linea(not faltan_qml, "qml",
|
||||
"todos los import resuelven" if not faltan_qml
|
||||
("todos los import resuelven" + nota) if not faltan_qml
|
||||
else "%d módulo(s) importado(s) y NO instalado(s)" % len(faltan_qml))
|
||||
if en_binario and detalle_qml:
|
||||
for m, donde in sorted(en_binario.items()):
|
||||
print(" · %-38s lo registra: %s" % (m, donde))
|
||||
if faltan_qml and (detalle_qml or True):
|
||||
for m, quienes in sorted(faltan_qml.items()):
|
||||
print(" FALTA %-34s ← lo importa: %s"
|
||||
|
||||
Reference in New Issue
Block a user