Publish a Windows setup beside the AppImage and the disk image

The releases page had nothing for Windows, so the only way in was a
checkout, a Python and a pip install. What goes out now is one setup
program per release: PyInstaller's directory, the pinned ffmpeg the
disk image already uses, and Inno Setup around both. It installs for
the account alone, so no administrator is asked for.

Two executables over the one program there, because a windowed one on
Windows has no standard output at all: Dikte.exe for the Start Menu and
dikte.exe for the terminal, sharing everything they carry. The icon is
drawn by Dikte itself into an .ico, the way the Mac's .icns and Linux's
PNGs already are, so there is still no image file in the repository.

Starting at sign-in is a registry value rather than a Startup shortcut,
which is what lets the setup program, the uninstaller and
`dikte integrate` all mean the same thing: the wizard asks once, and
typing the command changes the answer later.

The three builds move into build.yml, which release.yml now calls
instead of holding its own copy, and which a pull request touching the
packaging runs on its own. A broken build is then a red pull request
rather than a failed release.
This commit is contained in:
2026-08-18 16:08:47 +03:00
parent d24da7df52
commit 23d56acfb5
17 changed files with 696 additions and 132 deletions
+112
View File
@@ -0,0 +1,112 @@
name: build
# The three downloads, in one place. release.yml calls this one rather than
# holding a copy of it, and a pull request that touches the packaging runs it
# on its own, because the alternative is finding out that a build is broken
# from the release that was supposed to publish it.
on:
workflow_call:
inputs:
ref:
description: what to check out; the caller's own ref when empty
type: string
required: false
default: ""
version:
description: write this version into the tree before building
type: string
required: false
default: ""
pull_request:
paths:
- packaging/**
- .github/workflows/build.yml
- dikte/integrate.py
- dikte/trayicon.py
workflow_dispatch:
permissions:
contents: read
jobs:
build:
strategy:
fail-fast: false
matrix:
include:
# The oldest Ubuntu still offered, because the glibc a build links
# against is the oldest one it will run on, and 22.04's covers every
# distribution released since. Move it up only when it goes away.
- os: ubuntu-22.04
kind: appimage
- os: macos-latest
kind: dmg
# Intel Macs. This runner is the last x86_64 image Actions will
# offer, and it goes away in August 2027.
- os: macos-15-intel
kind: dmg
# x64 only, which is what the PyQt6 wheel and whisper.cpp both
# publish for Windows; a machine on ARM runs the result emulated,
# the same way it runs everything else that was never built for it.
- os: windows-latest
kind: windows
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
- uses: actions/setup-python@v5
with:
python-version: "3.12"
# PyQt6 ships Qt itself, but Qt still loads these from the system, and
# the build draws its own icon before it packages anything.
- name: Install the Qt runtime libraries
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install --no-install-recommends -y \
libegl1 libgl1 libxkbcommon0 libdbus-1-3 libglib2.0-0 \
libfontconfig1 libfreetype6 libgssapi-krb5-2
- name: Install PyQt6 and PyInstaller
run: python -m pip install --quiet PyQt6 pyinstaller
# Only for the builds off master: a tagged build already says the number
# it was tagged with, and rewriting it would be rewriting the tag.
- name: Write the version being built
if: inputs.version
# Named, because the Windows runner's own shell is PowerShell and this
# is a here-document.
shell: bash
env:
VERSION: ${{ inputs.version }}
run: |
python - <<'PY'
import os, pathlib, re
path = pathlib.Path("dikte/__init__.py")
path.write_text(re.sub(r'^__version__ = ".*"$',
f'__version__ = "{os.environ["VERSION"]}"',
path.read_text(), flags=re.M))
PY
- name: Build
if: runner.os != 'Windows'
run: ./packaging/build-${{ matrix.kind }}.sh
# The same steps as the other two, in the language the platform already
# has: drawing the icon, PyInstaller, and wrapping the result in what
# people download.
- name: Build
if: runner.os == 'Windows'
shell: pwsh
run: ./packaging/build-${{ matrix.kind }}.ps1
- uses: actions/upload-artifact@v4
with:
name: dikte-${{ matrix.os }}
path: dist/*
if-no-files-found: error
+14 -62
View File
@@ -107,70 +107,16 @@ jobs:
esac
echo "version=$version" >> "$GITHUB_OUTPUT"
# The builds themselves are build.yml, which a pull request touching the
# packaging also runs on its own. One definition, so the download somebody
# gets from a release and the one a pull request was checked against cannot
# come out of two different sets of steps.
build:
needs: version
strategy:
fail-fast: false
matrix:
include:
# The oldest Ubuntu still offered, because the glibc a build links
# against is the oldest one it will run on, and 22.04's covers every
# distribution released since. Move it up only when it goes away.
- os: ubuntu-22.04
kind: appimage
- os: macos-latest
kind: dmg
# Intel Macs. This runner is the last x86_64 image Actions will
# offer, and it goes away in August 2027.
- os: macos-15-intel
kind: dmg
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ needs.version.outputs.ref }}
- uses: actions/setup-python@v5
with:
python-version: "3.12"
# PyQt6 ships Qt itself, but Qt still loads these from the system, and
# the build draws its own icon before it packages anything.
- name: Install the Qt runtime libraries
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install --no-install-recommends -y \
libegl1 libgl1 libxkbcommon0 libdbus-1-3 libglib2.0-0 \
libfontconfig1 libfreetype6 libgssapi-krb5-2
- name: Install PyQt6 and PyInstaller
run: python -m pip install --quiet PyQt6 pyinstaller
# Only for the builds off master: a tagged build already says the number
# it was tagged with, and rewriting it would be rewriting the tag.
- name: Write the version being built
if: needs.version.outputs.prerelease == 'true'
env:
VERSION: ${{ needs.version.outputs.version }}
run: |
python - <<'PY'
import os, pathlib, re
path = pathlib.Path("dikte/__init__.py")
path.write_text(re.sub(r'^__version__ = ".*"$',
f'__version__ = "{os.environ["VERSION"]}"',
path.read_text(), flags=re.M))
PY
- name: Build
run: ./packaging/build-${{ matrix.kind }}.sh
- uses: actions/upload-artifact@v4
with:
name: dikte-${{ matrix.os }}
path: dist/*
if-no-files-found: error
uses: ./.github/workflows/build.yml
with:
ref: ${{ needs.version.outputs.ref }}
version: ${{ needs.version.outputs.prerelease == 'true' && needs.version.outputs.version || '' }}
publish:
needs: [version, build]
@@ -207,6 +153,12 @@ jobs:
Accessibility permissions, which macOS asks for the first time each is
used. It asks again after an update, because an application signed with
no certificate is one macOS has never seen before.
Windows: run the setup, which installs for your account alone and asks
for no administrator. It carries the ffmpeg recording needs and adds a
Start Menu entry, a `dikte` command and, unless you untick it, a start
at sign-in. It is not signed either, so SmartScreen offers only "Don't
run" until you press More info. Add/Remove Programs uninstalls it.
EOF
)
+11 -6
View File
@@ -107,11 +107,16 @@ jobs:
run: python -m unittest discover --verbose
# What the Mac does for its installer, in the language this one is in.
# Parsing only: install.ps1 writes into the Start Menu and the user PATH.
- name: Check the installer parses
# Parsing only: install.ps1 writes into the Start Menu and the user PATH,
# and build-windows.ps1 downloads an ffmpeg and runs PyInstaller. The
# setup program itself is compiled by build.yml, on the pull requests
# that touch it.
- name: Check the installer and the build script parse
shell: pwsh
run: |
$problems = $null
[System.Management.Automation.Language.Parser]::ParseFile(
"$PWD/install.ps1", [ref]$null, [ref]$problems) > $null
if ($problems) { $problems; exit 1 }
foreach ($script in "install.ps1", "packaging/build-windows.ps1") {
$problems = $null
[System.Management.Automation.Language.Parser]::ParseFile(
"$PWD/$script", [ref]$null, [ref]$problems) > $null
if ($problems) { $problems; exit 1 }
}