Archivio sorgente con submodule e vision-engine-version.txt (#109)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
"""Archivio sorgente di TieMeasureFlow per un PC senza git (TieMeasureFlow#109).
|
||||
|
||||
Cosa fa
|
||||
Crea uno zip del repo a un commit, con dentro anche il submodule vendor/visionsuite (che
|
||||
`git archive` da solo NON include) e il file vision-engine-version.txt nella radice dello zip.
|
||||
Quel file contiene lo sha del submodule REGISTRATO nel commit (non la HEAD del checkout del
|
||||
submodule): packaging/windows/avvio.py lo legge e lo passa al worker di visione come
|
||||
VISION_ENGINE_VERSION; senza, il worker rifiuta ogni job. Formato: 40 caratteri ASCII,
|
||||
senza BOM e senza a capo (avvio.py usa .strip(), che non toglie un BOM).
|
||||
|
||||
Come si usa (dalla radice del repo, con il submodule inizializzato)
|
||||
uv run python scripts/archivio_sorgente.py --out C:/tmp/tmf.zip [--ref HEAD]
|
||||
Esce 0 e stampa lo sha; esce 1 con un messaggio e senza lasciare lo zip se il submodule
|
||||
non e' inizializzato o non ha il commit registrato (git submodule update --init vendor/visionsuite).
|
||||
|
||||
Dipende da: git nel PATH, solo libreria standard.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import io
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
SUBMODULE = "vendor/visionsuite"
|
||||
FILE_VERSIONE = "vision-engine-version.txt"
|
||||
|
||||
|
||||
class ErroreArchivio(RuntimeError):
|
||||
"""Errore chiaro da mostrare all'operatore (niente traceback)."""
|
||||
|
||||
|
||||
def _git(cartella: Path, *args: str, binario: bool = False):
|
||||
"""Esegue git in `cartella`; solleva ErroreArchivio con lo stderr se fallisce."""
|
||||
r = subprocess.run(["git", "-C", str(cartella), *args], capture_output=True)
|
||||
if r.returncode != 0:
|
||||
raise ErroreArchivio(f"git {' '.join(args)} fallito in {cartella}: {r.stderr.decode(errors='replace').strip()}")
|
||||
return r.stdout if binario else r.stdout.decode().strip()
|
||||
|
||||
|
||||
def sha_registrato(repo: Path, ref: str) -> str:
|
||||
"""Sha del submodule registrato nel commit `ref` (gitlink, modo 160000), non la HEAD del checkout."""
|
||||
campi = _git(repo, "ls-tree", ref, SUBMODULE).split()
|
||||
if len(campi) < 3 or campi[0] != "160000":
|
||||
raise ErroreArchivio(f"{SUBMODULE} non e' un submodule registrato in {ref}")
|
||||
return campi[2]
|
||||
|
||||
|
||||
def crea_archivio(repo: Path, out: Path, ref: str = "HEAD") -> str:
|
||||
"""Scrive lo zip in `out` e ritorna lo sha del submodule; in caso di errore non lascia nulla."""
|
||||
repo, out = Path(repo), Path(out)
|
||||
sha = sha_registrato(repo, ref)
|
||||
sub = repo / SUBMODULE
|
||||
# submodule non inizializzato o senza il commit registrato: errore prima di scrivere lo zip
|
||||
if not (sub / ".git").exists():
|
||||
raise ErroreArchivio(f"{SUBMODULE} non e' inizializzato: git submodule update --init {SUBMODULE}")
|
||||
if subprocess.run(["git", "-C", str(sub), "cat-file", "-e", f"{sha}^{{commit}}"], capture_output=True).returncode != 0:
|
||||
raise ErroreArchivio(f"{SUBMODULE} non ha il commit {sha}: git submodule update --init {SUBMODULE}")
|
||||
|
||||
zip_repo = _git(repo, "archive", "--format=zip", ref, binario=True)
|
||||
zip_sub = _git(sub, "archive", "--format=zip", f"--prefix={SUBMODULE}/", sha, binario=True)
|
||||
|
||||
out.parent.mkdir(parents=True, exist_ok=True)
|
||||
fd, tmp = tempfile.mkstemp(suffix=".zip.tmp", dir=out.parent) # scrittura atomica: o c'e' tutto o niente
|
||||
os.close(fd)
|
||||
try:
|
||||
viste: set[str] = set()
|
||||
with zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED) as z:
|
||||
for blob in (zip_repo, zip_sub):
|
||||
with zipfile.ZipFile(io.BytesIO(blob)) as sorgente:
|
||||
for info in sorgente.infolist():
|
||||
if info.filename in viste:
|
||||
continue # es. la cartella vendor/visionsuite/ del gitlink, gia' nel primo zip
|
||||
viste.add(info.filename)
|
||||
z.writestr(info, sorgente.read(info.filename), compress_type=zipfile.ZIP_DEFLATED)
|
||||
if FILE_VERSIONE in viste:
|
||||
raise ErroreArchivio(f"{FILE_VERSIONE} e' gia' nel repo a {ref}: va tolto dal versionamento")
|
||||
# 40 caratteri ASCII, senza BOM e senza a capo (avvio.py fa .strip(), che non toglie un BOM)
|
||||
z.writestr(FILE_VERSIONE, sha.encode("ascii"))
|
||||
os.replace(tmp, out)
|
||||
except BaseException:
|
||||
if os.path.exists(tmp):
|
||||
os.unlink(tmp)
|
||||
raise
|
||||
return sha
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
p = argparse.ArgumentParser(description="Zip del sorgente con submodule e vision-engine-version.txt (#109)")
|
||||
p.add_argument("--out", required=True, type=Path, help="file .zip da creare")
|
||||
p.add_argument("--ref", default="HEAD", help="commit da archiviare (default HEAD)")
|
||||
a = p.parse_args(argv)
|
||||
radice = Path(__file__).resolve().parents[1]
|
||||
try:
|
||||
sha = crea_archivio(radice, a.out, a.ref)
|
||||
except ErroreArchivio as e:
|
||||
print(f"ERRORE: {e}", file=sys.stderr)
|
||||
return 1
|
||||
print(f"{a.out} creato; {FILE_VERSIONE} = {sha}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user