[CmdletBinding()] param( [switch]$NoClean ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $repoRoot = Resolve-Path (Join-Path $scriptDir '..') Set-Location $repoRoot function Get-VersionString { $versionFile = Join-Path $repoRoot 'VERSION' if (!(Test-Path $versionFile)) { return 'unknown' } return (Get-Content $versionFile -Raw).Trim() } function Sanitize-FilePart([string]$text) { if ([string]::IsNullOrWhiteSpace($text)) { return 'unknown' } $s = $text.Trim() foreach ($ch in @('\','/','?',':','*','"','<','>','|')) { $s = $s.Replace($ch, '_') } $s = $s -replace '\s+', '_' return $s } function Get-PlatformToken { return "windows" } function Get-ArchToken { switch ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString().ToLowerInvariant()) { "x64" { return "amd64" } "arm64" { return "arm64" } default { return ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString().ToLowerInvariant()) } } } $python = Join-Path $repoRoot '.venv\Scripts\python.exe' if (!(Test-Path $python)) { Write-Host "No .venv found. Creating venv..." -ForegroundColor Yellow python -m venv .venv if (!(Test-Path $python)) { throw "Failed to create venv; expected at $python" } } Write-Host "Using Python: $python" -ForegroundColor Cyan & $python -m pip install -r requirements.txt & $python -m pip install pyinstaller pillow $pngIcon = Join-Path $repoRoot 'src\ccma\assets\icons\ccma.png' $icoOut = Join-Path $repoRoot 'build\app.ico' if (Test-Path $pngIcon) { Write-Host "Generating ICO: $icoOut" -ForegroundColor Cyan & $python -c @" from pathlib import Path from PIL import Image png = Path(r'$pngIcon') ico = Path(r'$icoOut') ico.parent.mkdir(parents=True, exist_ok=True) img = Image.open(png).convert('RGBA') img.save(ico, format='ICO', sizes=[(16,16),(24,24),(32,32),(48,48),(64,64),(128,128),(256,256)]) print(str(ico)) "@ } else { Write-Host "PNG icon not found at: $pngIcon (building without icon)" -ForegroundColor Yellow } if (-not $NoClean) { if (Test-Path (Join-Path $repoRoot 'dist')) { Remove-Item -Recurse -Force (Join-Path $repoRoot 'dist') } if (Test-Path (Join-Path $repoRoot 'build\pyinstaller')) { Remove-Item -Recurse -Force (Join-Path $repoRoot 'build\pyinstaller') } } Write-Host "Building with PyInstaller spec..." -ForegroundColor Cyan & $python -m PyInstaller --noconfirm --clean --distpath dist --workpath build\pyinstaller build\ccma.spec $exe = Join-Path $repoRoot 'dist\ccma.exe' if (!(Test-Path $exe)) { throw "Build finished but exe not found at: $exe" } $version = Get-VersionString $versionSafe = Sanitize-FilePart $version $platformToken = Get-PlatformToken $archToken = Get-ArchToken $versionedExe = Join-Path $repoRoot ("dist\ccma-$versionSafe-$platformToken-$archToken.exe") Copy-Item -Force $exe $versionedExe Write-Host "" Write-Host "Build OK" -ForegroundColor Green Write-Host "Base EXE: $exe" -ForegroundColor Green Write-Host "Versioned EXE: $versionedExe" -ForegroundColor Green Write-Host "Version: $version" -ForegroundColor Green