mirror of
https://github.com/yusufipk/dikte.git
synced 2026-09-11 10:56:10 +00:00
Build an AppImage and a disk image, and publish them
Installing meant cloning the repository and running a shell script, which is a fair ask of somebody who already has a terminal open and no ask at all of anybody else. The releases page now carries an AppImage and a disk image per Mac architecture: a push to master rebuilds a rolling "latest", a v* tag publishes a version and leaves it there, and the Run button in the Actions tab raises the number by running scripts/release.sh, which is the same script and not a second copy of what it does. Two things in the application had to give for that. A build has no __main__.py on disk, and an AppImage is mounted somewhere new every run, so the command a shortcut is registered with cannot go on being this interpreter and this file; ipc.launcher() answers with the AppImage or the bundle instead. And a build carries its own libstdc++, which every process it starts inherits through LD_LIBRARY_PATH and none of them can live with: ffmpeg, ydotool and wl-copy are the distribution's binaries built against the distribution's libraries, and AppImageLauncher, which is what starting the AppImage again goes through, refuses outright. integrate.py puts that variable back before anything else runs. Nothing installs itself over an installation that is already there. install.sh's menu entry, install-mac.sh's login item and the desktop file AppImageLauncher writes are each recognised and left alone, so trying a download once does not quietly move the machine onto it. `dikte integrate` is how you ask for it outright, and --remove takes it back. The disk image carries an ffmpeg, pinned and checksummed, because macOS records through one and ships nothing like it. It is signed ad-hoc and not with an Apple certificate, so a first launch is refused until Open Anyway and the permissions are asked for again after each update; both READMEs and the release notes say so.
This commit is contained in:
@@ -0,0 +1,229 @@
|
||||
name: release
|
||||
|
||||
# Three ways in, one set of builds behind them.
|
||||
#
|
||||
# a push to master rebuilds the "latest" release, which is the newest
|
||||
# commit, prerelease, and always at the same download URL
|
||||
# a v* tag publishes that version and leaves it there
|
||||
# the Run button raises the version, tags it, and then does the above
|
||||
#
|
||||
# The Run button and scripts/release.sh do the same thing, and this runs that
|
||||
# script rather than repeating it, so the two cannot drift apart.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
tags: ["v*"]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
bump:
|
||||
description: which part of the version to raise
|
||||
type: choice
|
||||
options: [patch, minor, major]
|
||||
default: patch
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
# Two pushes in a row would otherwise race each other to replace the same
|
||||
# "latest" release, and the one that finishes second is not the newer one. Only
|
||||
# those are cancelled: a run that is publishing a version has already pushed a
|
||||
# tag by the time it gets there, and cancelling it would leave that tag with no
|
||||
# release under it.
|
||||
concurrency:
|
||||
group: release-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/') }}
|
||||
|
||||
jobs:
|
||||
# tests.yml runs the same suite on the same push, and this runs it again
|
||||
# rather than reaching across to it: what that one guards is the branch,
|
||||
# what this one guards is the download, and a "latest" built from a commit
|
||||
# whose tests fail is worse than no latest at all.
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- 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
|
||||
- run: python -m pip install --quiet PyQt6
|
||||
- run: python -m unittest discover
|
||||
|
||||
version:
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
ref: ${{ steps.decide.outputs.ref }}
|
||||
tag: ${{ steps.decide.outputs.tag }}
|
||||
version: ${{ steps.decide.outputs.version }}
|
||||
prerelease: ${{ steps.decide.outputs.prerelease }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Raise the version, when that is what was asked for
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
./scripts/release.sh "${{ inputs.bump }}" --yes
|
||||
|
||||
# A push made with the workflow's own token starts no further workflows,
|
||||
# which is what keeps this from setting itself off again, and is also why
|
||||
# the tag it just made has to be built by this run rather than the next.
|
||||
- name: Work out what is being built
|
||||
id: decide
|
||||
run: |
|
||||
read_version() { sed -n 's/^__version__ = "\(.*\)"$/\1/p' dikte/__init__.py; }
|
||||
case "${{ github.event_name }}" in
|
||||
workflow_dispatch)
|
||||
version="$(read_version)"
|
||||
echo "ref=v$version" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=v$version" >> "$GITHUB_OUTPUT"
|
||||
echo "prerelease=false" >> "$GITHUB_OUTPUT"
|
||||
;;
|
||||
*)
|
||||
if [[ "$GITHUB_REF" == refs/tags/* ]]; then
|
||||
version="${GITHUB_REF#refs/tags/v}"
|
||||
echo "ref=$GITHUB_SHA" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=v$version" >> "$GITHUB_OUTPUT"
|
||||
echo "prerelease=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
# Not a version anybody released: the number in the tree, said
|
||||
# to be ahead of it, and the commit, so that a bug report from
|
||||
# somebody running "latest" names one.
|
||||
version="$(read_version)-dev.${GITHUB_SHA::7}"
|
||||
echo "ref=$GITHUB_SHA" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=latest" >> "$GITHUB_OUTPUT"
|
||||
echo "prerelease=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||
|
||||
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
|
||||
|
||||
publish:
|
||||
needs: [version, build]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ needs.version.outputs.ref }}
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: downloads
|
||||
merge-multiple: true
|
||||
|
||||
- name: Publish
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
TAG: ${{ needs.version.outputs.tag }}
|
||||
VERSION: ${{ needs.version.outputs.version }}
|
||||
run: |
|
||||
ls -la downloads
|
||||
|
||||
notes=$(cat <<'EOF'
|
||||
Linux: download the AppImage, `chmod +x` it, run it. It writes its own
|
||||
menu entry and starts with you at login the first time it runs, and it
|
||||
leaves an installation that is already on the machine alone.
|
||||
|
||||
macOS: open the .dmg and drag Dikte to Applications. It is not signed
|
||||
with an Apple certificate, so the first launch is refused: open System
|
||||
Settings, Privacy & Security, and press Open Anyway. Or, in a terminal:
|
||||
`xattr -dr com.apple.quarantine /Applications/Dikte.app`. Take the arm64
|
||||
image for an Apple silicon Mac and the x86_64 one for an Intel Mac.
|
||||
Recording, transcribing and pasting need the microphone and
|
||||
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.
|
||||
EOF
|
||||
)
|
||||
|
||||
if [ "$TAG" = latest ]; then
|
||||
# Rolling: the release and its tag are replaced rather than added
|
||||
# to, so that the download URL stays the one people wrote down.
|
||||
gh release delete latest --yes --cleanup-tag 2>/dev/null || true
|
||||
gh release create latest downloads/* \
|
||||
--title "latest ($VERSION)" \
|
||||
--prerelease \
|
||||
--target "$GITHUB_SHA" \
|
||||
--notes "The newest commit on master, built and not released. For a version somebody meant to publish, take the release below.
|
||||
|
||||
$notes"
|
||||
else
|
||||
gh release create "$TAG" downloads/* \
|
||||
--title "Dikte $VERSION" \
|
||||
--generate-notes \
|
||||
--notes "$notes"
|
||||
fi
|
||||
@@ -74,3 +74,6 @@ jobs:
|
||||
bash -n scripts/install-mac.sh
|
||||
bash -n scripts/update.sh
|
||||
bash -n scripts/uninstall.sh
|
||||
bash -n scripts/release.sh
|
||||
bash -n packaging/build-appimage.sh
|
||||
bash -n packaging/build-dmg.sh
|
||||
|
||||
Reference in New Issue
Block a user