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.
51 lines
2.3 KiB
Diff
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)
|