Files
takana/recipes/incoming-kde/syntax-highlighting-jinja-reproducible.patch
T
Sergio 981709d241 syntax-highlighting: arreglado el no-determinismo — era el generador de Jinja
Antes: dos reconstrucciones daban `libKF6SyntaxHighlighting.so` distintos —
8.800.310 bytes diferentes desde el offset 41, y hasta el tamaño cambiaba
(10.345.888 contra 10.345.840). Ahora: REPRODUCE.

Hash: 4c1638a0… → 3db73c19… (arrastra 4 dependientes directos, 5 transitivos).

── Dónde NO estaba, que también cuenta ────────────────────────────────────────────────
No era el indexer: `katehighlightingindexer` arma el índice con `QVariantMap`, que es
QMap y va ordenado. No eran los generadores de Perl: ninguno de los cuatro itera un
hash. Mirarlo antes ahorró parchear lo que no era.

── Dónde estaba: `data/generators/generate_jinja.py`, con tres dependencias del azar ──
1. `to_do.pop()` sobre un `set` saca un elemento ARBITRARIO. En `--dry-run` el orden de
   los `print(out_file)` es lo que CMake recoge en `out_xmls`, y eso acaba siendo **el
   orden de las entradas del `.qrc`** ⇒ el recurso compilado cambiaba de disposición
   entera. Se toma el menor: mismo conjunto, orden fijo.
2. `version = str(round(time.time()))` hornea la HORA DEL BUILD en el XML generado. Se
   honra `SOURCE_DATE_EPOCH`, que es la convención de reproducible-builds y que el
   sandbox de takana ya fija (=1).
3. `os.listdir()` sin ordenar. Sólo importa si dos ficheros declaran el mismo lenguaje,
   pero quitar la dependencia del readdir no cuesta nada.

── Medido en los dos sentidos ANTES de tocar la receta ────────────────────────────────
Corriendo el generador a mano, sin builds de por medio:

  · antes  — tres PYTHONHASHSEED distintos ⇒ TRES md5 distintos, y el orden salta a la
             vista: `jinja-json, jinja-yaml, jinja-toml…` contra
             `jinja-qml, jinja-dockerfile, jinja-typescript…`
  · después — las mismas tres semillas ⇒ el MISMO md5
  · generando de verdad con semillas Y momentos distintos ⇒ los 35 XML IDÉNTICOS,
    con `version="1"`
  · control negativo — sin `SOURCE_DATE_EPOCH` sigue poniendo la hora actual
    (comprobado: coincidía con `date +%s` al segundo) ⇒ fuera del sandbox no cambia nada

Primer aviso de esta medición: mi primera comparación dio «idéntico con las tres
semillas» y era MENTIRA — el script salía con «Destination folder does not exist» y yo
comparaba md5 de un mensaje de error. Comparar salidas sin mirar que la herramienta
hiciera algo es inventarse un control.
2026-09-14 21:15:00 +00:00

51 lines
2.3 KiB
Diff

Hacer determinista generate_jinja.py (reproducibilidad — SDD 23)
El generador de las definiciones Jinja tenía TRES dependencias del azar, y entre
ellas hacían que `libKF6SyntaxHighlighting.so` saliera distinto en cada build
(8.800.310 bytes distintos y hasta el tamaño cambiaba):
1. `to_do.pop()` sobre un `set` saca un elemento ARBITRARIO. En `--dry-run` el
orden de los `print(out_file)` es lo que CMake usa para `out_xmls`, que acaba
siendo el ORDEN DE LAS ENTRADAS DEL .qrc ⇒ el recurso compilado cambiaba de
disposición entera. Se toma el menor: mismo conjunto, orden fijo.
2. `version = str(round(time.time()))` hornea la HORA DEL BUILD en el XML
generado. Se honra `SOURCE_DATE_EPOCH` (que el sandbox de takana ya fija),
que es la convención de reproducible-builds; fuera del sandbox se comporta
igual que antes.
3. `os.listdir()` devuelve el orden del sistema de ficheros. Sólo importa si dos
ficheros declaran el mismo lenguaje (gana el último), pero ordenarlo cuesta
nada y quita la última dependencia del orden de readdir.
--- a/data/generators/generate_jinja.py
+++ b/data/generators/generate_jinja.py
@@ -113,7 +113,7 @@
to_visit = set()
for grammar_dir in grammar_dirs:
- for file_name in os.listdir(grammar_dir):
+ for file_name in sorted(os.listdir(grammar_dir)):
if not file_name.lower().endswith('.xml'):
continue
file_path = os.path.join(grammar_dir, file_name)
@@ -151,7 +151,7 @@
lang_tag = grammar.getroot()
lang_tag.set('name', jinja_tag.get('name') + '/' + lang_tag.get('name'))
lang_tag.set('section', jinja_tag.get('section'))
- lang_tag.set('version', str(round(time.time())))
+ lang_tag.set('version', os.environ.get('SOURCE_DATE_EPOCH') or str(round(time.time())))
lang_tag.set('license', jinja_tag.get('license'))
lang_tag.set('priority', lang_tag.get('priority', '0'))
lang_tag.set('kateversion', max(lang_tag.get('kateversion'),
@@ -265,7 +265,8 @@
sys.exit(2)
while to_do:
- lang = to_do.pop()
+ lang = min(to_do)
+ to_do.remove(lang)
out_file = os.path.join(args.output_dir,
args.prefix + os.path.basename(xmls[lang]))
infuse_header(grammars[lang], jinja.getroot(), args.xtra_ext)