Package the tray+daemon as a single PyInstaller onefile exe so it runs on a fresh Windows 11 box with no Python and no pip install. Verified on hardware: the frozen exe connects over BLE, reads the Windows media session, and pushes now-playing (incl. Cyrillic) at the 3s / 60s cadence. - clawdmeter.spec: onefile, windowed (no console; logs to daemon.log). collect_all for winrt + bleak is the key bit — bleak loads its WinRT backend dynamically and the winrt.windows.* projections are split distributions that PyInstaller's static analysis misses, so BLE and the media session would otherwise fail at runtime. upx off (lowers AV false-positive rate). - build-exe.ps1: one-command build (venv + deps + pyinstaller + spec). - tray_windows.py: resolve the brand-logo asset from sys._MEIPASS when frozen. - autostart_windows.py: when frozen, the HKCU\Run "Start at login" value points at the exe itself (sys.executable), not pythonw + script. - .gitignore: ignore /build, /dist, /.venv — the exe ships via a Gitea release. - README-windows.md: document the standalone-exe path (download / run / build). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
49 lines
2.2 KiB
PowerShell
49 lines
2.2 KiB
PowerShell
# build-exe.ps1 — build the standalone Clawdmeter.exe (PyInstaller).
|
|
#
|
|
# Produces dist\Clawdmeter.exe: a single self-contained tray + daemon executable
|
|
# that runs on ANY Windows 11 machine with no Python install and no pip. Build it
|
|
# on a machine that DOES have Python (e.g. your dev box), then distribute the exe
|
|
# via a Gitea release; end users just download and run it (see daemon\README-windows.md).
|
|
#
|
|
# Usage (from anywhere):
|
|
# powershell -ExecutionPolicy Bypass -File build-exe.ps1
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
function Log { param([string]$Msg) Write-Host "[$(Get-Date -Format HH:mm:ss)] $Msg" }
|
|
|
|
$RepoRoot = $PSScriptRoot
|
|
if (-not $RepoRoot) { $RepoRoot = (Get-Location).Path }
|
|
Set-Location $RepoRoot
|
|
|
|
$VenvDir = Join-Path $RepoRoot ".venv"
|
|
$PythonExe = Join-Path $VenvDir "Scripts\python.exe"
|
|
|
|
Log "=== Clawdmeter exe build ==="
|
|
|
|
# 1. Virtual environment
|
|
if (-not (Test-Path $PythonExe)) {
|
|
Log "Creating virtual environment at .venv ..."
|
|
& python -m venv $VenvDir
|
|
if ($LASTEXITCODE -ne 0) { throw "venv creation failed (exit $LASTEXITCODE) — is Python on PATH?" }
|
|
}
|
|
|
|
# 2. Runtime dependencies + PyInstaller (the only build-time extra)
|
|
Log "Installing runtime dependencies + PyInstaller ..."
|
|
& $PythonExe -m pip install --quiet -r (Join-Path $RepoRoot "daemon\requirements-windows.txt")
|
|
if ($LASTEXITCODE -ne 0) { throw "pip install (runtime deps) failed (exit $LASTEXITCODE)" }
|
|
& $PythonExe -m pip install --quiet pyinstaller
|
|
if ($LASTEXITCODE -ne 0) { throw "pip install pyinstaller failed (exit $LASTEXITCODE)" }
|
|
|
|
# 3. Build from the committed spec (onefile, windowed)
|
|
Log "Building dist\Clawdmeter.exe (this takes a minute) ..."
|
|
& $PythonExe -m PyInstaller --noconfirm (Join-Path $RepoRoot "clawdmeter.spec")
|
|
if ($LASTEXITCODE -ne 0) { throw "PyInstaller build failed (exit $LASTEXITCODE)" }
|
|
|
|
$ExePath = Join-Path $RepoRoot "dist\Clawdmeter.exe"
|
|
if (-not (Test-Path $ExePath)) { throw "Build reported success but $ExePath is missing" }
|
|
$sizeMB = [math]::Round((Get-Item $ExePath).Length / 1MB, 1)
|
|
Log "Build complete: $ExePath ($sizeMB MB)"
|
|
Log "Distribute this exe via a Gitea release — it is intentionally not committed to git."
|