Logo Das Programmlogo steht jetzt klein neben "powered by ravokk" - dort sitzt die Kennung des Werkzeugs, nicht die des Kunden. Eine eigene Kopfzeile hat dieses Programm nicht; oben sind die Reiter. bauen.py Der Weg von der Quelle zur Setup-Datei war eine Folge von Befehlen, die jemand von Hand eintippen musste. Wer einen Schritt vergisst, liefert eine Setup-Datei mit einer alten EXE aus - und sieht es ihr nicht an. Genau das ist heute passiert. Jetzt ist es ein Befehl, die Nummer kommt aus version.py, und am Ende stehen die SHA256-Summen fuer den Release-Text. pruef_start_exe.py Prueft, was kein anderer Pruefstand prueft: ob die GEBAUTE EXE ein sichtbares Fenster zeigt. Alle uebrigen fahren das Programm aus der Quelle, dort trat der Fehler nie auf. "Der Prozess laeuft" beweist dabei nichts - er lief ja, waehrend nichts zu sehen war. Aufgeraeumt "Rechnung Campinghof.exe" ist weg; gebaute Dateien gehoeren ins Release, nicht ins Repo. Die .spec-Dateien dagegen schon: ohne sie kann niemand die EXE nachbauen, die der Updater ausliefert. Fassung 1.7.0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V4t48uxDok5rJXhC9bX1Ax
109 lines
3.9 KiB
Python
109 lines
3.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Der ganze Weg von der Quelle zur Setup-Datei - an EINER Stelle.
|
|
|
|
WARUM DAS HIER STEHT
|
|
--------------------
|
|
Bis hierher lag der Bau in einer Folge von Befehlen, die jemand von Hand
|
|
eintippen musste: PyInstaller fuers Programm, PyInstaller fuer den Updater,
|
|
umbenennen, Inno Setup mit den richtigen Schaltern. Wer einen Schritt
|
|
vergisst, liefert eine Setup-Datei aus, die eine alte EXE enthaelt - und
|
|
niemand sieht es ihr an. Genau das ist am 06.09.2026 passiert (der Installer
|
|
schrieb ueber eine laufende, gesperrte EXE hinweg und meldete trotzdem Erfolg).
|
|
|
|
Ab jetzt ist der Bau ein Befehl:
|
|
|
|
python bauen.py
|
|
|
|
WAS ER TUT
|
|
----------
|
|
1. Die Nummer kommt aus version.py - nicht von der Kommandozeile. So kann sie
|
|
nicht auseinanderlaufen mit dem, was das Programm ueber sich selbst sagt.
|
|
2. Programm und Updater werden gebaut.
|
|
3. Die Programm-EXE bekommt die Nummer in den Namen. Der Updater erkennt daran,
|
|
welche Fassungen installiert sind, und legt neue DANEBEN statt darueber -
|
|
deshalb kann er auch dann austauschen, wenn Windows die alte Datei sperrt.
|
|
4. Inno Setup baut die Setup-Datei.
|
|
5. Zum Schluss stehen die SHA256-Summen da. Die gehoeren in den Release-Text:
|
|
nur damit laesst sich spaeter nachweisen, dass die heruntergeladene Datei
|
|
genau die gebaute ist.
|
|
"""
|
|
import hashlib
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
|
|
HIER = os.path.dirname(os.path.abspath(__file__))
|
|
sys.path.insert(0, HIER)
|
|
|
|
import version # noqa: E402
|
|
|
|
# Inno Setup liegt bei ihm unter LOCALAPPDATA (Installation ohne Adminrechte).
|
|
ISCC_ORTE = [
|
|
os.path.join(os.environ.get("LOCALAPPDATA", ""), "Programs", "Inno Setup 6", "ISCC.exe"),
|
|
r"C:\Program Files (x86)\Inno Setup 6\ISCC.exe",
|
|
r"C:\Program Files\Inno Setup 6\ISCC.exe",
|
|
]
|
|
|
|
|
|
def summe(pfad):
|
|
h = hashlib.sha256()
|
|
with open(pfad, "rb") as f:
|
|
for brocken in iter(lambda: f.read(1 << 20), b""):
|
|
h.update(brocken)
|
|
return h.hexdigest()
|
|
|
|
|
|
def lauf(befehl, was):
|
|
print(f"--- {was}")
|
|
e = subprocess.run(befehl, cwd=HIER, capture_output=True, text=True,
|
|
encoding="utf-8", errors="replace")
|
|
if e.returncode != 0:
|
|
print(e.stdout[-3000:])
|
|
print(e.stderr[-3000:])
|
|
raise SystemExit(f"{was} fehlgeschlagen (Rueckgabe {e.returncode})")
|
|
|
|
|
|
def iscc():
|
|
for p in ISCC_ORTE:
|
|
if p and os.path.exists(p):
|
|
return p
|
|
raise SystemExit("ISCC.exe nicht gefunden - Inno Setup 6 fehlt")
|
|
|
|
|
|
def main():
|
|
stamm, endung = os.path.splitext(version.EXE_NAME)
|
|
mit_nummer = f"{stamm}-{version.VERSION}{endung}"
|
|
|
|
# Die Bauordner werden geleert. Ein PyInstaller-Lauf ueber einen alten
|
|
# Bauordner uebernimmt sonst Reste, die es in der Quelle nicht mehr gibt -
|
|
# und in setup/ laege noch die Datei der VORIGEN Nummer herum. Beim
|
|
# Ausliefern greift man dann leicht zur falschen.
|
|
for ordner in ("build", "dist", "setup"):
|
|
shutil.rmtree(os.path.join(HIER, ordner), ignore_errors=True)
|
|
|
|
lauf([sys.executable, "-m", "PyInstaller", "--noconfirm", f"{stamm}.spec"],
|
|
f"{stamm} {version.VERSION}")
|
|
lauf([sys.executable, "-m", "PyInstaller", "--noconfirm", "Updater.spec"],
|
|
"Updater")
|
|
|
|
quelle = os.path.join(HIER, "dist", version.EXE_NAME)
|
|
ziel = os.path.join(HIER, "dist", mit_nummer)
|
|
os.replace(quelle, ziel)
|
|
|
|
lauf([iscc(), f"/DVersion={version.VERSION}", f"/DExeProgramm={mit_nummer}",
|
|
"Installer.iss"], "Setup")
|
|
|
|
print()
|
|
print(f"FERTIG | {stamm} {version.VERSION}")
|
|
for name in sorted(os.listdir(os.path.join(HIER, "dist"))):
|
|
p = os.path.join(HIER, "dist", name)
|
|
print(f" {name:44s} {os.path.getsize(p):>10,d} B {summe(p)}")
|
|
setup = os.path.join(HIER, "setup")
|
|
for name in sorted(os.listdir(setup)) if os.path.isdir(setup) else []:
|
|
p = os.path.join(setup, name)
|
|
print(f" {name:44s} {os.path.getsize(p):>10,d} B {summe(p)}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|