Gleiche Umstellung wie im Rechnungstool, gleiche gemeinsame Dateien
(gemeinsam.py, ordnerwahl.py, wasserzeichen.py, bauen.py).
%LOCALAPPDATA%/ravokk/
journal.sqlite3, berichte/, Nummernbuch, config.json <- Daten
Rechnungstool/ <- Programm
Steuerrechnungstool/ <- Programm
⚠ Hier gefunden und behoben: das Journal der INSTALLIERTEN Fassung lag im
Temp-Ordner. Der Merkzettel (ablage.py) zeigte auf
%TEMP%/abnahme_bst/Daten - haengengeblieben aus einem Abnahmelauf vom
03.09.2026. Windows raeumt den Temp-Ordner von selbst auf; das Journal
waere ohne Vorwarnung verschwunden. Ein Merkzettel, der dorthin zeigt,
wird jetzt verworfen statt befolgt.
Sein Echtbestand (238 Buchungen) lag davon unberuehrt in der Quelle und
ist gesichert.
Kein Ordner-Dialog mehr beim ersten Start - der gemeinsame Ordner steht
fest. Der Knopf hiess dort "Install-Ordner auswaehlen...", was schon immer
falsch war: es geht um das gemeinsame Nummernbuch, nicht um den
Installationsort.
Uninstaller raeumt jetzt auch die vom Updater geladenen Fassungen weg.
pruef_installation.py prueft beides zusammen: Programmordner leer, Daten
unangetastet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V4t48uxDok5rJXhC9bX1Ax
138 lines
5.3 KiB
Python
138 lines
5.3 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", "release"):
|
|
shutil.rmtree(os.path.join(HIER, ordner), ignore_errors=True)
|
|
|
|
# Das Windows-Symbol aus dem Logo bauen: helle Kachel mit runden Ecken,
|
|
# Zeichen darauf, in allen Groessen von 16 bis 256. Es wird HIER erzeugt
|
|
# und nicht zur Laufzeit - ein installiertes Programm soll nicht in seinen
|
|
# eigenen Ordner schreiben muessen.
|
|
sys.path.insert(0, HIER)
|
|
import wasserzeichen
|
|
ico = wasserzeichen.symbol_bauen(
|
|
os.path.join(HIER, "assets", "programm.ico"))
|
|
print(f"--- Symbol: {ico or 'FEHLT'}")
|
|
|
|
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")
|
|
|
|
# ---- release/ : genau das, was ins Release gehoert ---------------------
|
|
# Der Anhang, den der Updater sucht, heisst OHNE Nummer - so steht es in
|
|
# version.EXE_NAME, und danach sucht passendes_release(). Haengt man ihn
|
|
# mit Nummer an, findet der Updater nie ein Update und meldet stumm
|
|
# "aktuell". Die Nummer kommt erst beim Ablegen dazu (updater.dateiname).
|
|
#
|
|
# Daneben die Pruefsumme als eigene Datei: ohne sie prueft der Updater nur
|
|
# die GROESSE. Eine Datei, die unterwegs verfaelscht wurde, hat aber genau
|
|
# die richtige Groesse.
|
|
freigabe = os.path.join(HIER, "release")
|
|
os.makedirs(freigabe, exist_ok=True)
|
|
shutil.copy2(ziel, os.path.join(freigabe, version.EXE_NAME))
|
|
with open(os.path.join(freigabe, version.EXE_NAME + ".sha256"),
|
|
"w", encoding="ascii") as f:
|
|
f.write(summe(ziel) + chr(10))
|
|
shutil.copy2(os.path.join(HIER, "dist", "Updater.exe"),
|
|
os.path.join(freigabe, "Updater.exe"))
|
|
for name in os.listdir(os.path.join(HIER, "setup")):
|
|
shutil.copy2(os.path.join(HIER, "setup", name),
|
|
os.path.join(freigabe, name))
|
|
|
|
print()
|
|
print(f"FERTIG | {stamm} {version.VERSION}")
|
|
print()
|
|
print(" release/ - genau diese Dateien gehoeren ans Release:")
|
|
for name in sorted(os.listdir(freigabe)):
|
|
p = os.path.join(freigabe, name)
|
|
print(f" {name:44s} {os.path.getsize(p):>10,d} B {summe(p)}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|