Brand the Windows installer wizard (#7) #42
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| push: | |
| # Triggered by ``git push origin v1.2.1`` and similar version tags. | |
| # Pre-release tags (e.g. v1.2.1-rc1) are also matched. | |
| tags: | |
| - "v*" | |
| workflow_dispatch: | |
| inputs: | |
| tag_name: | |
| description: "Version tag to release, for example v1.2.1" | |
| required: true | |
| type: string | |
| # The release job needs ``contents: write`` so the workflow can upload the | |
| # built archives to the GitHub Release the tag creates. | |
| permissions: | |
| contents: write | |
| env: | |
| PYTHON_VERSION: "3.12" | |
| RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag_name || github.ref_name }} | |
| concurrency: | |
| group: release-${{ github.event_name == 'workflow_dispatch' && inputs.tag_name || github.ref }} | |
| cancel-in-progress: false | |
| jobs: | |
| validate-release: | |
| name: Validate release metadata | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check out release ref | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag_name || github.ref }} | |
| - name: Verify tag matches application version | |
| shell: bash | |
| run: | | |
| tag_version="${RELEASE_TAG#v}" | |
| app_version="$(python -c 'from version import __version__; print(__version__)')" | |
| if [ "$tag_version" != "$app_version" ]; then | |
| echo "Release tag $RELEASE_TAG does not match application version $app_version" >&2 | |
| exit 1 | |
| fi | |
| build-windows: | |
| name: Build Windows installer | |
| needs: validate-release | |
| runs-on: windows-latest | |
| steps: | |
| - name: Check out release ref | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag_name || github.ref }} | |
| - name: Set up Python ${{ env.PYTHON_VERSION }} | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| # 1.3.1 dropped python-vlc in favour of PyAV; the FFmpeg shared | |
| # libs are bundled inside the PyAV wheel, so no choco install | |
| # step is needed here any more. | |
| - name: Install Python dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| python -m pip install -e ".[dev]" | |
| - name: Resolve Windows build script | |
| shell: pwsh | |
| run: | | |
| if (Test-Path "packaging/build_windows_optimized.py") { | |
| $script = "packaging/build_windows_optimized.py" | |
| } elseif (Test-Path "build_windows_optimized.py") { | |
| $script = "build_windows_optimized.py" | |
| } else { | |
| Write-Error "Windows build script was not found" | |
| exit 1 | |
| } | |
| "BUILD_SCRIPT=$script" | Out-File -Append -FilePath $env:GITHUB_ENV | |
| - name: Keep Windows runtime DLLs bundled | |
| shell: pwsh | |
| run: | | |
| $content = Get-Content -Raw -Path $env:BUILD_SCRIPT | |
| $content = $content -replace "\s*'msvcp140_1',\r?\n", "" | |
| $content = $content -replace "\s*'vcruntime140_1',\r?\n", "" | |
| Set-Content -Path $env:BUILD_SCRIPT -Value $content -NoNewline | |
| - name: Build Windows onedir distribution | |
| run: > | |
| python $env:BUILD_SCRIPT | |
| --include-module distutils | |
| --include-module wheel | |
| --include-module pip | |
| --include-module easy_install | |
| --include-module unittest | |
| --include-module venv | |
| --include-module ensurepip | |
| --include-module lib2to3 | |
| --include-module pkg_resources | |
| - name: Install Inno Setup | |
| run: choco install innosetup --no-progress -y | |
| - name: Compile Windows installer (onedir) | |
| shell: pwsh | |
| run: | | |
| $version = "${{ env.RELEASE_TAG }}".TrimStart("v") | |
| $iscc = Get-ChildItem -Path "${env:ProgramFiles(x86)}","${env:ProgramFiles}" -Recurse -Filter "ISCC.exe" -ErrorAction SilentlyContinue | | |
| Select-Object -First 1 -ExpandProperty FullName | |
| if (-not $iscc) { Write-Error "ISCC.exe not found after installing Inno Setup"; exit 1 } | |
| Write-Host "Using ISCC: $iscc" | |
| & $iscc "/DAppVersion=$version" "packaging\RABET.iss" | |
| if ($LASTEXITCODE -ne 0) { Write-Error "Inno Setup compile failed ($LASTEXITCODE)"; exit 1 } | |
| - name: Stage installer asset | |
| shell: pwsh | |
| run: | | |
| $version = "${{ env.RELEASE_TAG }}".TrimStart("v") | |
| $src = "dist/RABET-Setup-$version.exe" | |
| if (-not (Test-Path $src)) { Write-Error "Installer not found: $src"; exit 1 } | |
| New-Item -ItemType Directory -Path "release-assets" -Force | Out-Null | |
| $stage = "release-assets/RABET-Windows-$version-Setup" | |
| New-Item -ItemType Directory -Path $stage -Force | Out-Null | |
| # Zip the installer so browsers and managed networks are less likely | |
| # to block the download solely because the release asset is an .exe. | |
| Copy-Item -Path $src -Destination "$stage/RABET-Setup.exe" -Force | |
| Compress-Archive -Path "$stage/*" -DestinationPath "release-assets/RABET-Windows-$version-Setup.zip" -Force | |
| Remove-Item -Recurse -Force $stage | |
| - name: Build Windows onefile (portable) | |
| run: > | |
| python $env:BUILD_SCRIPT | |
| --onefile | |
| --include-module distutils | |
| --include-module wheel | |
| --include-module pip | |
| --include-module easy_install | |
| --include-module unittest | |
| --include-module venv | |
| --include-module ensurepip | |
| --include-module lib2to3 | |
| --include-module pkg_resources | |
| - name: Stage portable asset | |
| shell: pwsh | |
| run: | | |
| $version = "${{ env.RELEASE_TAG }}".TrimStart("v") | |
| $src = "dist/RABET.exe" | |
| if (-not (Test-Path $src)) { Write-Error "Portable exe not found: $src"; exit 1 } | |
| New-Item -ItemType Directory -Path "release-assets" -Force | Out-Null | |
| $stage = "release-assets/RABET-Windows-$version-portable" | |
| New-Item -ItemType Directory -Path $stage -Force | Out-Null | |
| # Single-file portable: keep the executable name simple inside the | |
| # archive while shipping the asset itself as a browser-friendlier zip. | |
| Copy-Item -Path $src -Destination "$stage/RABET.exe" -Force | |
| Compress-Archive -Path "$stage/*" -DestinationPath "release-assets/RABET-Windows-$version-portable.zip" -Force | |
| Remove-Item -Recurse -Force $stage | |
| - name: Upload Windows artefacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: RABET-Windows-${{ env.RELEASE_TAG }} | |
| path: release-assets/* | |
| retention-days: 30 | |
| if-no-files-found: error | |
| build-macos: | |
| name: Build macOS distribution (${{ matrix.arch }}) | |
| needs: validate-release | |
| runs-on: ${{ matrix.runner }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - arch: arm64 | |
| runner: macos-15 | |
| target_arch: arm64 | |
| - arch: x86_64 | |
| runner: macos-15-intel | |
| target_arch: x86_64 | |
| steps: | |
| - name: Check out release ref | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag_name || github.ref }} | |
| - name: Set up Python ${{ env.PYTHON_VERSION }} | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| # 1.3.1 dropped python-vlc in favour of PyAV. The PyAV macOS wheel | |
| # ships its own FFmpeg dylibs, so the brew install step is gone. | |
| - name: Install Python dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| python -m pip install -e ".[dev]" | |
| - name: Resolve macOS build script | |
| shell: bash | |
| run: | | |
| if [ -f "packaging/build_macos_optimized.py" ]; then | |
| script="packaging/build_macos_optimized.py" | |
| elif [ -f "build_macos_optimized.py" ]; then | |
| script="build_macos_optimized.py" | |
| else | |
| echo "macOS build script was not found" >&2 | |
| exit 1 | |
| fi | |
| echo "BUILD_SCRIPT=${script}" >> "$GITHUB_ENV" | |
| - name: Build unsigned macOS package | |
| run: python "$BUILD_SCRIPT" --target-arch "${{ matrix.target_arch }}" | |
| - name: Build macOS DMG | |
| shell: bash | |
| run: | | |
| version="${RELEASE_TAG#v}" | |
| bash packaging/build_macos_dmg.sh "$version" "${{ matrix.arch }}" | |
| - name: Stage macOS DMG | |
| shell: bash | |
| run: | | |
| version="${RELEASE_TAG#v}" | |
| asset="dist/RABET-macOS-${{ matrix.arch }}-${version}.dmg" | |
| if [ ! -f "$asset" ]; then | |
| echo "Expected DMG was not found: $asset" >&2 | |
| exit 1 | |
| fi | |
| echo "ASSET_PATH=${asset}" >> "$GITHUB_ENV" | |
| echo "ASSET_NAME=$(basename "$asset")" >> "$GITHUB_ENV" | |
| - name: Upload macOS artefact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ env.ASSET_NAME }} | |
| path: ${{ env.ASSET_PATH }} | |
| retention-days: 30 | |
| if-no-files-found: error | |
| build-linux: | |
| name: Build Linux distribution | |
| needs: validate-release | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check out release ref | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag_name || github.ref }} | |
| - name: Set up Python ${{ env.PYTHON_VERSION }} | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| - name: Install Qt runtime libraries | |
| run: | | |
| sudo apt-get update | |
| # As of 1.3.1 RABET no longer needs libvlc; PyAV's manylinux | |
| # wheel bundles FFmpeg. Qt's xcb platform plugin still depends | |
| # on system GUI libraries on Linux, so install the same runtime | |
| # set documented in the release README before packaging. | |
| sudo apt-get install -y --no-install-recommends \ | |
| libxcb-cursor0 \ | |
| libxcb-icccm4 \ | |
| libxcb-image0 \ | |
| libxcb-keysyms1 \ | |
| libxcb-render-util0 \ | |
| libxcb-xkb1 \ | |
| libxcb-randr0 \ | |
| libxcb-render0 \ | |
| libxcb-shape0 \ | |
| libxcb-shm0 \ | |
| libxcb-sync1 \ | |
| libxcb-xfixes0 \ | |
| libxkbcommon-x11-0 \ | |
| libxrender1 \ | |
| libx11-xcb1 \ | |
| libsm6 \ | |
| libice6 \ | |
| libglib2.0-0 \ | |
| libfontconfig1 \ | |
| libfreetype6 | |
| - name: Install Python dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| python -m pip install -e ".[dev]" | |
| - name: Resolve Linux build script | |
| shell: bash | |
| run: | | |
| if [ -f "packaging/build_linux_optimized.py" ]; then | |
| script="packaging/build_linux_optimized.py" | |
| elif [ -f "build_linux_optimized.py" ]; then | |
| script="build_linux_optimized.py" | |
| else | |
| echo "Linux build script was not found" >&2 | |
| exit 1 | |
| fi | |
| echo "BUILD_SCRIPT=${script}" >> "$GITHUB_ENV" | |
| - name: Build Linux onedir distribution | |
| run: python "$BUILD_SCRIPT" | |
| - name: Build Linux AppImage | |
| shell: bash | |
| run: | | |
| sudo apt-get install -y --no-install-recommends desktop-file-utils | |
| version="${RELEASE_TAG#v}" | |
| bash packaging/build_linux_appimage.sh "$version" | |
| - name: Stage Linux AppImage | |
| shell: bash | |
| run: | | |
| version="${RELEASE_TAG#v}" | |
| arch="$(uname -m)" | |
| asset="dist/RABET-Linux-${arch}-${version}.AppImage" | |
| if [ ! -f "$asset" ]; then | |
| echo "Expected AppImage was not found: $asset" >&2 | |
| exit 1 | |
| fi | |
| echo "ASSET_PATH=${asset}" >> "$GITHUB_ENV" | |
| echo "ASSET_NAME=$(basename "$asset")" >> "$GITHUB_ENV" | |
| - name: Upload Linux artefact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ env.ASSET_NAME }} | |
| path: ${{ env.ASSET_PATH }} | |
| retention-days: 30 | |
| if-no-files-found: error | |
| publish-release: | |
| name: Publish GitHub Release | |
| needs: [build-windows, build-macos, build-linux] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check out release ref | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag_name || github.ref }} | |
| - name: Download release artefacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: "RABET-*" | |
| path: dist | |
| merge-multiple: true | |
| - name: Generate release body placeholder | |
| id: changelog | |
| shell: bash | |
| run: | | |
| version="${RELEASE_TAG#v}" | |
| cat > release_body.md <<'EOF' | |
| ## RABET vVERSION_PLACEHOLDER | |
| RABET vVERSION_PLACEHOLDER gives each project its own action map, so | |
| one key means the same behaviour for every video in a project, and | |
| makes the auto-save destination configurable outside Project mode. | |
| ### Added | |
| - **Project action maps.** | |
| Creating a project now saves a copy of the action map in use into | |
| the project and records it in `project.json`. Opening the project | |
| loads that map; closing it restores your global map. Edits made | |
| while the project is open are saved into the project, so the global | |
| action map is never altered by working on a project. | |
| - **The active action map is always identifiable.** | |
| The Action Map panel heading names what you are recording with: | |
| the project name when the project has its own map, or "global" | |
| when it does not. | |
| - **Notification when opening a project changes the keys.** | |
| If opening a project changes what any key means, RABET lists the | |
| differences before you can record. Nothing is shown when the map is | |
| unchanged. | |
| - **Guard on changing an action map that already has annotations.** | |
| Renaming a behaviour, switching a key between State and Point, | |
| removing a mapping, or replacing the whole map now asks for | |
| confirmation when the open project already contains annotated | |
| videos. Adding a new key is additive and is never questioned. | |
| Existing annotation CSVs are never modified. | |
| - **Configurable auto-save folder for non-project recordings.** | |
| File > Set Auto-Save Folder chooses where annotation CSVs are | |
| written when a recording ends outside Project mode. File > Reset | |
| Auto-Save Folder to Default restores saving next to each video. | |
| Project-mode auto-save remains fixed to the project's `annotations` | |
| folder. | |
| ### Changed | |
| - **Refreshed Windows installer.** | |
| The setup wizard now carries RABET's own artwork, and its opening | |
| page no longer asks you to close all other applications — RABET | |
| installs per-user and registers nothing, so the only thing worth | |
| closing is RABET itself when upgrading. | |
| - **Clearer Action Map type labels.** | |
| The editor now shows the concise labels State and Point, and the | |
| guides explain that State records an onset and offset whereas Point | |
| records a single timestamp. The choice describes the data | |
| representation rather than how quickly the behaviour occurs. | |
| ### Fixed | |
| - **Configuration and project files are read as UTF-8.** | |
| Action maps, `project.json`, settings, and metric configurations | |
| are now read as UTF-8 and tolerate a byte order mark. Previously a | |
| hand-edited or externally generated file could fail to load, or | |
| load with corrupted non-ASCII behaviour labels, depending on the | |
| system locale. Files written by RABET were unaffected. | |
| - **The annotation auto-save preference is applied at startup.** | |
| The existing `general.auto_save_annotations` setting now controls | |
| end-of-recording auto-save instead of being stored but ignored. It | |
| remains enabled by default. | |
| ### Compatibility | |
| Projects created before vVERSION_PLACEHOLDER have no action map of | |
| their own and continue to use the global map exactly as before. To | |
| give one its own map, open it and choose | |
| `File > Use Current Action Map for This Project`. RABET does not do | |
| this automatically, because it cannot know whether the map loaded now | |
| is the one those videos were originally scored with. | |
| ### Packaging | |
| - **Windows installer and portable builds are zip-wrapped.** | |
| The release assets are `RABET-Windows-VERSION_PLACEHOLDER-Setup.zip` | |
| and `RABET-Windows-VERSION_PLACEHOLDER-portable.zip`. The portable | |
| zip contains a simple `RABET.exe`. | |
| - **macOS builds are distributed as DMG images.** | |
| Open the DMG, then drag `RABET.app` to Applications. | |
| - **Linux is distributed as an AppImage.** | |
| Make it executable and launch it directly. | |
| ### macOS first launch | |
| The macOS builds are unsigned and not notarized. After downloading | |
| the DMG, open it and drag `RABET.app` to Applications. On first | |
| launch, macOS may report that the app is "damaged" or cannot be | |
| verified. This is Gatekeeper quarantine, not a corrupt download. | |
| The most reliable fix is: | |
| ```bash | |
| xattr -dr com.apple.quarantine /Applications/RABET.app | |
| ``` | |
| Then open `RABET.app` normally. You only need to do this once for | |
| each downloaded copy. | |
| EOF | |
| sed -i "s/VERSION_PLACEHOLDER/$version/g" release_body.md | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ env.RELEASE_TAG }} | |
| name: ${{ env.RELEASE_TAG }} | |
| body_path: release_body.md | |
| draft: false | |
| prerelease: ${{ contains(env.RELEASE_TAG, '-rc') || contains(env.RELEASE_TAG, '-beta') }} | |
| files: dist/* | |
| overwrite_files: true |