From 13c37454797fe825ffe6d12c64e2b32f2d0f676d Mon Sep 17 00:00:00 2001 From: Julian Appel Date: Fri, 14 Aug 2026 23:15:25 +0200 Subject: [PATCH] Make build_and_deploy.ps1 self-installing and fail loudly pip install of pyinstaller/pystray/pillow was a separate manual step the README only mentioned in prose, so the script silently died on missing packages -- especially bad on Explorer double-click, where the window closes before any error is visible. Consolidate all dependencies into requirements.txt (also used by README's plain "pip install -r" flow), have the script install it itself, wrap the whole build in try/catch with exit-code checks after every native call, and pause on both success and failure unless -NoPause is passed. Also move the build output from %LOCALAPPDATA% into dist/ next to the script, so it's easy to find and matches the already-gitignored dist/ entry. --- README.md | 24 +++++++---- build_and_deploy.ps1 | 98 +++++++++++++++++++++++++++++++++----------- requirements.txt | 5 +++ 3 files changed, 96 insertions(+), 31 deletions(-) create mode 100644 requirements.txt diff --git a/README.md b/README.md index ebe6691..c1f56c5 100644 --- a/README.md +++ b/README.md @@ -37,15 +37,18 @@ Windows-Maschine hartkodiert (`versapad_data.CONFIG_PATHS`, ## Voraussetzungen - Python 3.11 oder neuer -- Pakete: `pyserial` (Board-Kommunikation), `pystray` + `pillow` - (Tray-Icon/Desktop-App), optional `mcp` (nur für den MCP-Server) +- Alle Pakete stehen in `requirements.txt` (`pyserial` fürs Board, + `pystray` + `pillow` fürs Tray-Icon/Desktop-Fenster, `mcp` für den + MCP-Server, `pyinstaller` fürs `.exe`-Bauen): ```bash -pip install pyserial pystray pillow mcp +pip install -r requirements.txt ``` Für den Browser-Modus (`server.py`) reicht die Python-Standardbibliothek — -keine zusätzlichen Pakete nötig. +keine zusätzlichen Pakete nötig. `build_and_deploy.ps1` installiert +`requirements.txt` beim Bauen automatisch selbst — ein manuelles +`pip install` vorher ist dafür nicht nötig. ## Starten @@ -65,8 +68,15 @@ Maschine/Python-Version unterschiedlich). Selbst bauen: .\build_and_deploy.ps1 ``` -Das Skript baut mit PyInstaller (`--onedir --windowed`, eigenes Icon) und -kopiert das Ergebnis nach `%LOCALAPPDATA%\VersaPadViewer\VersaPadViewer.exe`. +Das Skript installiert/aktualisiert selbst alle nötigen Pakete aus +`requirements.txt` (kein manuelles `pip install` vorher nötig), baut dann +mit PyInstaller (`--onedir --windowed`, eigenes Icon) und kopiert das +Ergebnis nach `dist\VersaPadViewer\VersaPadViewer.exe` im Projektordner. +Bricht ein Schritt ab (fehlendes Python, PyInstaller-Fehler, ...), zeigt das +Skript eine klare Fehlermeldung und wartet auf einen Tastendruck, statt sich +bei Doppelklick im Explorer kommentarlos zu schließen (`-NoPause` +unterdrückt das für automatisierte Aufrufe/CI). + **Wichtig:** Sowohl Bauen als auch Ausführen müssen auf einem lokalen Laufwerk passieren — von einem Netzlaufwerk (SMB-Share) aus scheitert PyInstaller beim Bauen (Pfadlängen-Problem mit Tcl/Tk-Zeitzonendaten) und @@ -75,8 +85,6 @@ das Nachladen der Bundle-DLLs von einem Netzwerkpfad, ohne jede Fehlermeldung). `build_and_deploy.ps1` kopiert den Quellcode deshalb automatisch zuerst nach `%TEMP%` und baut nur dort. -Voraussetzung: `pip install pyinstaller` zusätzlich zu den obigen Paketen. - ## Aufbau | Datei | Zweck | diff --git a/build_and_deploy.ps1 b/build_and_deploy.ps1 index 8243798..e0617e0 100644 --- a/build_and_deploy.ps1 +++ b/build_and_deploy.ps1 @@ -1,5 +1,5 @@ # Baut VersaPadViewer.exe (PyInstaller --onedir) und kopiert das Ergebnis -# nach lokal (C:\Users\\AppData\Local\VersaPadViewer). +# nach $projectDir\dist\VersaPadViewer. # # WICHTIG: Sowohl Bauen als auch Laufen muessen lokal passieren, NICHT auf # dem Netzlaufwerk (Z:\Git\...): @@ -12,35 +12,87 @@ # SMB-Share, kombiniert mit dem eh schon langen Projektpfad. # # Deshalb: Quellcode zuerst nach lokal (%TEMP%) kopieren, dort bauen, danach -# das fertige Bundle nach %LOCALAPPDATA% kopieren. Z: wird nur zum Lesen der -# Quelldateien angefasst. +# das fertige Bundle nach $projectDir\dist kopieren. Z: wird nur zum Lesen +# der Quelldateien angefasst. +# +# Fehlerverhalten: Bei Doppelklick im Explorer schliesst sich das Fenster +# sofort nach Skriptende -- ohne Pause waeren Fehlermeldungen unsichtbar +# ("stirbt ohne jede Fehlermeldung"). Deshalb: alles in try/catch, im +# Fehlerfall UND am Ende eine Pause, ausser bei -NoPause (fuer CI/Automation). + +param( + [switch]$NoPause +) $ErrorActionPreference = "Stop" $projectDir = $PSScriptRoot $buildSrc = "$env:TEMP\versapad_build_src" -$localDir = "$env:LOCALAPPDATA\VersaPadViewer" +$localDir = "$projectDir\dist\VersaPadViewer" +$requirementsFile = "$projectDir\requirements.txt" -if (Test-Path $buildSrc) { - Remove-Item $buildSrc -Recurse -Force +function Assert-LastExitCode([string]$step) { + if ($LASTEXITCODE -ne 0) { + throw "$step ist fehlgeschlagen (Exit-Code $LASTEXITCODE) -- Ausgabe oben pruefen." + } +} + +function Wait-ForKeyIfInteractive { + if ($NoPause) { return } + try { + Read-Host "Taste druecken zum Schliessen" + } catch { + # z.B. nicht-interaktiver Aufruf (stdin nicht verfuegbar) -- einfach ignorieren + } } -New-Item -ItemType Directory -Path $buildSrc | Out-Null -Copy-Item "$projectDir\*.py" -Destination $buildSrc -Copy-Item "$projectDir\icon.ico" -Destination $buildSrc -Copy-Item "$projectDir\icon.png" -Destination $buildSrc -Push-Location $buildSrc try { - py -m PyInstaller --windowed --onedir --name VersaPadViewer ` - --icon icon.ico --add-data "icon.png;." --add-data "icon.ico;." ` - desktop_viewer.py --noconfirm -} finally { - Pop-Location -} + Write-Host "Pruefe Python-Installation..." + py --version + Assert-LastExitCode "'py --version' (ist Python installiert und im PATH?)" -if (Test-Path $localDir) { - Remove-Item $localDir -Recurse -Force -} -Copy-Item "$buildSrc\dist\VersaPadViewer" -Destination $localDir -Recurse -Remove-Item $buildSrc -Recurse -Force + if (-not (Test-Path $requirementsFile)) { + throw "requirements.txt nicht gefunden unter $requirementsFile" + } + Write-Host "Installiere/aktualisiere benoetigte Pakete aus requirements.txt..." + py -m pip install --quiet --disable-pip-version-check -r $requirementsFile + Assert-LastExitCode "Paketinstallation (pip install -r requirements.txt)" -Write-Host "Fertig: $localDir\VersaPadViewer.exe" + if (Test-Path $buildSrc) { + Remove-Item $buildSrc -Recurse -Force + } + New-Item -ItemType Directory -Path $buildSrc | Out-Null + Copy-Item "$projectDir\*.py" -Destination $buildSrc + Copy-Item "$projectDir\icon.ico" -Destination $buildSrc + Copy-Item "$projectDir\icon.png" -Destination $buildSrc + + Push-Location $buildSrc + try { + Write-Host "Baue mit PyInstaller (--onedir --windowed)..." + py -m PyInstaller --windowed --onedir --name VersaPadViewer ` + --icon icon.ico --add-data "icon.png;." --add-data "icon.ico;." ` + desktop_viewer.py --noconfirm + Assert-LastExitCode "PyInstaller-Build" + } finally { + Pop-Location + } + + if (-not (Test-Path "$buildSrc\dist\VersaPadViewer\VersaPadViewer.exe")) { + throw "PyInstaller hat keine VersaPadViewer.exe erzeugt, obwohl der Exit-Code 0 war -- Build-Ausgabe oben pruefen." + } + + if (Test-Path $localDir) { + Remove-Item $localDir -Recurse -Force + } + Copy-Item "$buildSrc\dist\VersaPadViewer" -Destination $localDir -Recurse + Remove-Item $buildSrc -Recurse -Force + + Write-Host "" + Write-Host "Fertig: $localDir\VersaPadViewer.exe" -ForegroundColor Green + Wait-ForKeyIfInteractive +} +catch { + Write-Host "" + Write-Host "FEHLER: $($_.Exception.Message)" -ForegroundColor Red + Wait-ForKeyIfInteractive + exit 1 +} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ed0027c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +pyserial +pystray +pillow +mcp +pyinstaller