# SPDX-License-Identifier: AGPL-3.0-or-later # Copyright (c) 2025-2026 Cristian Cezar Moisés # # Cross-platform GUI + CLI binaries, built on REAL Windows and macOS runners. # # Why a dedicated workflow: the GUI is a PySide6/PyQt6 app and the CLI is # portable C11, but self-contained native installers (Windows .exe/.msi, # macOS .app/.dmg) can only be produced on the target OS. This workflow builds # them on GitHub's windows-latest and macos-latest runners and attaches them to # the GitHub release on a `v*` tag. Run it manually with "Run workflow" # (workflow_dispatch) to smoke-test the build before tagging. # # Artifacts produced: # Windows: vaptvupt.exe (CLI, mingw), vaptvupt-gui.exe (PyInstaller onefile), # VaptVupt-Setup-.exe (Inno Setup installer) # macOS: vaptvupt (CLI, universal where possible), VaptVupt-.dmg # All: vaptvupt-gui--portable.zip (Python GUI + launchers) name: cross-platform on: push: tags: ['v*'] workflow_dispatch: permissions: contents: write jobs: # ─────────────────────────── Windows ─────────────────────────── windows: runs-on: windows-latest defaults: run: { shell: msys2 {0} } steps: - uses: actions/checkout@v4 - name: Set up MSYS2 (mingw gcc + make) uses: msys2/setup-msys2@v2 with: msystem: UCRT64 update: true install: >- mingw-w64-ucrt-x86_64-gcc make coreutils - name: Build CLI (vaptvupt.exe, source-only, C fallback crypto) run: | make CC=gcc WITH_SDK=0 -j2 ./vaptvupt.exe version || ./vaptvupt version cp "$(ls vaptvupt.exe vaptvupt 2>/dev/null | head -1)" vaptvupt.exe 2>/dev/null || true - name: Set up Python shell: pwsh run: | # Use the runner's native Python (not MSYS) for PyInstaller so the # produced .exe targets the standard Windows Python ABI. python -m pip install --upgrade pip python -m pip install PySide6 pyinstaller - name: Get version id: ver shell: pwsh run: | $ver = (Select-String -Path include/zupt.h -Pattern '^#define ZUPT_VERSION_STRING "([^"]+)"').Matches.Groups[1].Value "version=$ver" | Out-File -FilePath $env:GITHUB_OUTPUT -Append - name: Bundle GUI with PyInstaller (vaptvupt-gui.exe) shell: pwsh run: | # onefile GUI that carries the CLI beside it via --add-binary. pyinstaller --noconfirm --onefile --windowed ` --name vaptvupt-gui ` --icon gui/assets/zupt-icon.png ` --add-binary "vaptvupt.exe;." ` --add-data "gui/assets/zupt-icon.png;assets" ` gui/src/zupt_gui.py - name: Build Inno Setup installer shell: pwsh run: | choco install innosetup --no-progress -y & "$env:ChocolateyInstall\bin\ISCC.exe" ` "/DAppVersion=${{ steps.ver.outputs.version }}" ` packaging/windows/vaptvupt-gui.iss - name: Collect artifacts shell: pwsh run: | $v = "${{ steps.ver.outputs.version }}" New-Item -ItemType Directory -Force out | Out-Null Copy-Item vaptvupt.exe "out/vaptvupt-$v-windows-x86_64.exe" Copy-Item dist/vaptvupt-gui.exe "out/vaptvupt-gui-$v-windows-x86_64.exe" if (Test-Path "packaging/windows/Output") { Copy-Item packaging/windows/Output/*.exe "out/" -ErrorAction SilentlyContinue } - uses: actions/upload-artifact@v4 with: name: windows path: out/* - name: Attach to release if: startsWith(github.ref, 'refs/tags/v') uses: softprops/action-gh-release@v2 with: files: out/* # ─────────────────────────── macOS ─────────────────────────── macos: runs-on: macos-latest steps: - uses: actions/checkout@v4 - name: Build CLI (vaptvupt, clang) run: | make CC=clang WITH_SDK=0 -j3 ./vaptvupt version - uses: actions/setup-python@v5 with: { python-version: '3.12' } - name: Install GUI build deps run: | python -m pip install --upgrade pip python -m pip install PySide6 pyinstaller brew install create-dmg || true - name: Get version id: ver run: echo "version=$(awk -F'\"' '/^#define ZUPT_VERSION_STRING/{print $2}' include/zupt.h)" >> "$GITHUB_OUTPUT" - name: Bundle GUI (.app) with PyInstaller run: | pyinstaller --noconfirm --windowed \ --name "VaptVupt" \ --add-binary "vaptvupt:." \ --add-data "gui/assets/zupt-icon.png:assets" \ gui/src/zupt_gui.py - name: Build .dmg run: | V="${{ steps.ver.outputs.version }}" create-dmg --volname "VaptVupt $V" --window-size 500 300 \ --app-drop-link 350 120 --icon "VaptVupt.app" 150 120 \ "VaptVupt-$V.dmg" "dist/VaptVupt.app" || \ { mkdir -p dmgroot && cp -R dist/VaptVupt.app dmgroot/ && \ hdiutil create -volname "VaptVupt $V" -srcfolder dmgroot -ov -format UDZO "VaptVupt-$V.dmg"; } - name: Collect artifacts run: | V="${{ steps.ver.outputs.version }}" mkdir -p out cp vaptvupt "out/vaptvupt-$V-macos" cp "VaptVupt-$V.dmg" out/ - uses: actions/upload-artifact@v4 with: name: macos path: out/* - name: Attach to release if: startsWith(github.ref, 'refs/tags/v') uses: softprops/action-gh-release@v2 with: files: out/* # ─────────────── Portable GUI (works on every OS) ─────────────── portable: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Get version id: ver run: echo "version=$(awk -F'\"' '/^#define ZUPT_VERSION_STRING/{print $2}' include/zupt.h)" >> "$GITHUB_OUTPUT" - name: Assemble portable package run: | V="${{ steps.ver.outputs.version }}" D="vaptvupt-gui-$V-portable" mkdir -p "$D/assets" cp gui/src/zupt_gui.py "$D/" cp gui/assets/zupt-icon.png "$D/assets/" cp packaging/portable/vaptvupt-gui.bat "$D/" cp packaging/portable/vaptvupt-gui.command "$D/" cp packaging/portable/vaptvupt-gui.sh "$D/" cp packaging/portable/README.txt "$D/" chmod +x "$D/vaptvupt-gui.command" "$D/vaptvupt-gui.sh" zip -r "$D.zip" "$D" - uses: actions/upload-artifact@v4 with: name: portable path: vaptvupt-gui-*-portable.zip - name: Attach to release if: startsWith(github.ref, 'refs/tags/v') uses: softprops/action-gh-release@v2 with: files: vaptvupt-gui-*-portable.zip