name: Windows Installer on: push: tags: - 'v*' workflow_dispatch: {} permissions: contents: write jobs: build-windows: name: Build Windows installer runs-on: windows-latest steps: - name: Checkout repository uses: actions/checkout@v6 - name: Set up Python 3.12 uses: actions/setup-python@v6 with: python-version: '3.12' cache: 'pip' - name: Install dependencies run: | python -m pip install --upgrade pip pip install pyinstaller pip install ".[nvidia,windows]" # Use 7-Zip pre-installed on the runner — no download needed, no network flakiness - name: Prepare 7-Zip for bundling run: | New-Item -ItemType Directory -Force -Path 7z-standalone | Out-Null Copy-Item "C:\Program Files\7-Zip\7z.exe" -Destination 7z-standalone\7za.exe -Force Copy-Item "C:\Program Files\7-Zip\7z.dll" -Destination 7z-standalone\7za.dll -Force # Download ffmpeg essentials (LGPL) for video playback — C# app bundles it too - name: Download ffmpeg run: | Invoke-WebRequest -Uri "https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip" -OutFile ffmpeg.zip Expand-Archive ffmpeg.zip -DestinationPath ffmpeg-tmp $ffmpegExe = Get-ChildItem -Path ffmpeg-tmp -Recurse -Filter "ffmpeg.exe" | Select-Object -First 1 Copy-Item $ffmpegExe.FullName -Destination ffmpeg.exe -Force # Ensure libusb DLL is available (pyusb loads via ctypes, PyInstaller misses it) - name: Download libusb run: | pip install libusb-package $libusb = python -c "import libusb_package; print(libusb_package.get_library_path())" echo "LIBUSB_PATH=$libusb" >> $env:GITHUB_OUTPUT id: libusb # Build CLI exe (with console) and GUI exe (without console) - name: Build CLI with PyInstaller run: | pyinstaller ` --name trcc ` --onedir ` --console ` --uac-admin ` --icon src/trcc/assets/icons/app.ico ` --add-data "src/trcc/assets;trcc/assets" ` --hidden-import PySide6.QtSvg ` --hidden-import pynvml ` --hidden-import wmi ` --collect-submodules trcc ` --noconfirm ` src/trcc/__main__.py - name: Build GUI with PyInstaller run: | pyinstaller ` --name trcc-gui ` --onedir ` --windowed ` --uac-admin ` --icon src/trcc/assets/icons/app.ico ` --add-data "src/trcc/assets;trcc/assets" ` --hidden-import PySide6.QtSvg ` --hidden-import pynvml ` --hidden-import wmi ` --collect-submodules trcc ` --noconfirm ` src/trcc/__main__.py # Merge GUI exe into CLI dist folder (share the runtime) Copy-Item -Path dist/trcc-gui/trcc-gui.exe -Destination dist/trcc/ -Force # Bundle 7z for theme extraction (LGPL, redistributable) Copy-Item -Path 7z-standalone/7za.exe -Destination dist/trcc/7z.exe -Force Copy-Item -Path 7z-standalone/7za.dll -Destination dist/trcc/7z.dll -Force # Bundle ffmpeg for video playback (LGPL, like C# app) Copy-Item -Path ffmpeg.exe -Destination dist/trcc/ffmpeg.exe -Force # Bundle libusb (pyusb loads via ctypes, PyInstaller misses it) if ("${{ steps.libusb.outputs.LIBUSB_PATH }}") { Copy-Item -Path "${{ steps.libusb.outputs.LIBUSB_PATH }}" -Destination dist/trcc/ -Force } - name: Log build environment run: | Write-Host "=== Windows Build Environment ===" Write-Host "OS: $([System.Environment]::OSVersion)" Write-Host "Python: $(python --version)" Write-Host "PyInstaller: $(python -c 'import PyInstaller; print(PyInstaller.__version__)')" Write-Host "PySide6: $(python -c 'import PySide6; print(PySide6.__version__)')" Write-Host "Working dir: $(Get-Location)" - name: Verify bundled dependencies run: | Write-Host "=== Bundle Contents ===" Write-Host "--- dist/trcc/ (top-level) ---" Get-ChildItem "dist/trcc/*.exe" | ForEach-Object { Write-Host " $($_.Name) ($([math]::Round($_.Length/1MB, 1)) MB)" } Get-ChildItem "dist/trcc/*.dll" | ForEach-Object { Write-Host " $($_.Name) ($([math]::Round($_.Length/1KB, 0)) KB)" } Write-Host "--- Total files ---" $total = (Get-ChildItem "dist/trcc" -Recurse | Measure-Object).Count $size = (Get-ChildItem "dist/trcc" -Recurse | Measure-Object -Property Length -Sum).Sum / 1MB Write-Host " $total files, $([math]::Round($size, 1)) MB total" Write-Host "" Write-Host "=== Verification ===" $missing = @() if (-not (Test-Path "dist/trcc/trcc.exe")) { $missing += "trcc.exe" } if (-not (Test-Path "dist/trcc/trcc-gui.exe")) { $missing += "trcc-gui.exe" } if (-not (Test-Path "dist/trcc/7z.exe")) { $missing += "7z.exe" } if (-not (Test-Path "dist/trcc/7z.dll")) { $missing += "7z.dll" } if (-not (Test-Path "dist/trcc/ffmpeg.exe")) { $missing += "ffmpeg.exe" } $libusb = Get-ChildItem "dist/trcc/libusb*" -ErrorAction SilentlyContinue if (-not $libusb) { $missing += "libusb-1.0.dll" } if ($missing.Count -gt 0) { Write-Error "FAILED — Missing: $($missing -join ', ')" exit 1 } Write-Host "PASSED — trcc.exe, trcc-gui.exe, 7z.exe, ffmpeg.exe, libusb OK" - name: Extract version id: version run: | $ver = "${{ github.ref_name }}" -replace '^v', '' echo "VERSION=$ver" >> $env:GITHUB_OUTPUT - name: Create Inno Setup installer uses: Minionguyjpro/Inno-Setup-Action@v1.2.7 with: path: installer/trcc.iss options: /DAPP_VERSION=${{ steps.version.outputs.VERSION }} - name: Upload installer artifact uses: actions/upload-artifact@v7 with: name: trcc-windows-installer path: installer/Output/trcc-${{ steps.version.outputs.VERSION }}-setup.exe - name: Create fixed-name alias for stable download URL if: startsWith(github.ref, 'refs/tags/v') run: | Copy-Item "installer/Output/trcc-${{ steps.version.outputs.VERSION }}-setup.exe" ` "installer/Output/trcc-latest-setup.exe" - name: Attach to GitHub Release if: startsWith(github.ref, 'refs/tags/v') env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | gh release upload ${{ github.ref_name }} ` "installer/Output/trcc-${{ steps.version.outputs.VERSION }}-setup.exe" ` "installer/Output/trcc-latest-setup.exe" ` --clobber