Skip to content

Brand the Windows installer wizard (#7) #42

Brand the Windows installer wizard (#7)

Brand the Windows installer wizard (#7) #42

Workflow file for this run

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