11name : Build executables
22
33# Workflow 1 of 2: builds the three PyInstaller executables (Text2Pen, Update,
4- # Installer) for Windows and Linux and uploads them as separate build
5- # artifacts. It does not touch GitHub Releases — that's workflow 2, which will
6- # take these artifacts and publish them as individual release assets, the
7- # same way existing releases already do (so in-place updates via Update.exe
8- # keep working).
4+ # Installer) for Windows and every supported Linux architecture, and uploads
5+ # them as separate build artifacts. It does not touch GitHub Releases —
6+ # that's workflow 2, which will take these artifacts and publish them as
7+ # individual release assets, the same way existing releases already do (so
8+ # in-place updates via Update.exe keep working).
99#
1010# `workflow_call` is included so a future release workflow can trigger a
1111# fresh build directly instead of reusing old artifacts.
@@ -32,13 +32,27 @@ jobs:
3232 include :
3333 - os : windows-latest
3434 label : windows
35- # Self-hosted (a Raspberry Pi) rather than a GitHub-hosted runner.
36- # NOTE: this makes the Linux build an ARM binary, not x86_64 — it
37- # will only run on ARM Linux (e.g. other Pis), not typical x86_64
38- # desktops. If both are needed, add back a GitHub-hosted
39- # `ubuntu-22.04` leg alongside this one.
40- - os : [self-hosted, Linux]
41- label : linux
35+ hosted : github
36+ pyinstaller : pyinstaller
37+ add_data_sep : " ;"
38+ exe_suffix : " .exe"
39+ # GitHub-hosted, so a fresh x86_64 VM every run: the common case,
40+ # what almost every Linux desktop needs.
41+ - os : ubuntu-22.04
42+ label : linux-x86_64
43+ hosted : github
44+ pyinstaller : pyinstaller
45+ add_data_sep : " :"
46+ exe_suffix : " "
47+ # Self-hosted (a Raspberry Pi) — the only realistic way to get an
48+ # ARM64 build without cross-compiling. NOTE: this binary only runs
49+ # on ARM64 Linux (other Pis, ARM SBCs), never on x86_64 desktops.
50+ - os : [self-hosted, Linux, ARM64]
51+ label : linux-arm64
52+ hosted : self
53+ pyinstaller : venv/bin/pyinstaller
54+ add_data_sep : " :"
55+ exe_suffix : " "
4256 runs-on : ${{ matrix.os }}
4357 steps :
4458 - uses : actions/checkout@v4
@@ -48,13 +62,26 @@ jobs:
4862 # runner is a persistent machine with its own system Python already
4963 # set up (see the apt note below), unlike an ephemeral GitHub-hosted VM.
5064 - uses : actions/setup-python@v5
51- if : matrix.label == 'windows '
65+ if : matrix.hosted == 'github '
5266 with :
5367 python-version : " 3.12"
5468
55- # Deliberately no apt-get step for Linux here: the self-hosted runner
56- # is a persistent machine, so system packages only need installing
57- # once, by hand, not on every run:
69+ # pywebview renders through WebKitGTK on Linux, and the portal
70+ # screen-capture code (backend/portal_capture.py) needs GLib/D-Bus/
71+ # GStreamer bindings. None of this is pip-installable; it has to come
72+ # from apt, on the same interpreter pip installs into.
73+ - name : Install Linux system dependencies
74+ if : matrix.label == 'linux-x86_64'
75+ run : |
76+ sudo apt-get update
77+ sudo apt-get install -y \
78+ python3-gi gir1.2-webkit2-4.1 python3-tk \
79+ gir1.2-gst-plugins-base-1.0 gstreamer1.0-plugins-base \
80+ gstreamer1.0-plugins-good gstreamer1.0-pipewire \
81+ python3-dbus libgirepository1.0-dev
82+
83+ # The self-hosted runner is persistent, not an ephemeral VM: install
84+ # these once by hand instead, so every run doesn't re-apt-get them:
5885 # sudo apt-get install -y python3-gi gir1.2-webkit2-4.1 python3-tk \
5986 # gir1.2-gst-plugins-base-1.0 gstreamer1.0-plugins-base \
6087 # gstreamer1.0-plugins-good gstreamer1.0-pipewire \
@@ -63,64 +90,49 @@ jobs:
6390 # (the last three are for pip to build python-uinput/pynput from
6491 # source on ARM, where prebuilt wheels are less common.)
6592
66- - name : Install Python dependencies (Windows )
67- if : matrix.label == 'windows '
93+ - name : Install Python dependencies (GitHub-hosted )
94+ if : matrix.hosted == 'github '
6895 run : |
6996 python -m pip install --upgrade pip
7097 pip install -r requirements.txt pyinstaller
7198
72- # --system-site-packages: pywebview's WebKitGTK bindings and the
73- # portal-capture GLib/GStreamer bindings only exist as apt packages
74- # ( see above), not on PyPI, so the venv needs to see the system ones .
75- - name : Install Python dependencies (Linux )
76- if : matrix.label == 'linux '
99+ # --system-site-packages: the apt packages above (WebKitGTK/GStreamer
100+ # bindings) only exist system-wide, not on PyPI, so the venv needs to
101+ # see them. Recreated every run for a clean, reproducible build .
102+ - name : Install Python dependencies (self-hosted )
103+ if : matrix.hosted == 'self '
77104 run : |
78105 rm -rf venv
79106 python3 -m venv --system-site-packages venv
80107 venv/bin/pip install --upgrade pip
81108 venv/bin/pip install -r requirements.txt pyinstaller
82109
83- - name : Build Text2Pen (Windows)
84- if : matrix.label == 'windows'
85- run : pyinstaller --onefile --noconsole --add-data "webui;webui" --name Text2Pen Text2Pen.py
86-
87- - name : Build Text2Pen (Linux)
88- if : matrix.label == 'linux'
89- run : venv/bin/pyinstaller --onefile --noconsole --add-data "webui:webui" --name Text2Pen Text2Pen.py
90-
91- - name : Build Update (Windows)
92- if : matrix.label == 'windows'
93- run : pyinstaller --onefile --noconsole --name Update Update.py
94-
95- - name : Build Update (Linux)
96- if : matrix.label == 'linux'
97- run : venv/bin/pyinstaller --onefile --noconsole --name Update Update.py
110+ - name : Build Text2Pen
111+ run : ${{ matrix.pyinstaller }} --onefile --noconsole --add-data "webui${{ matrix.add_data_sep }}webui" --name Text2Pen Text2Pen.py
98112
99- - name : Build Installer (Windows)
100- if : matrix.label == 'windows'
101- run : pyinstaller --onefile --noconsole --name Installer Installer.py
113+ - name : Build Update
114+ run : ${{ matrix.pyinstaller }} --onefile --noconsole --name Update Update.py
102115
103- - name : Build Installer (Linux)
104- if : matrix.label == 'linux'
105- run : venv/bin/pyinstaller --onefile --noconsole --name Installer Installer.py
116+ - name : Build Installer
117+ run : ${{ matrix.pyinstaller }} --onefile --noconsole --name Installer Installer.py
106118
107119 - name : Upload Text2Pen artifact
108120 uses : actions/upload-artifact@v4
109121 with :
110122 name : Text2Pen-${{ matrix.label }}${{ inputs.version != '' && format('-{0}', inputs.version) || '' }}
111- path : dist/Text2Pen${{ matrix.label == 'windows' && '.exe' || '' }}
123+ path : dist/Text2Pen${{ matrix.exe_suffix }}
112124 if-no-files-found : error
113125
114126 - name : Upload Update artifact
115127 uses : actions/upload-artifact@v4
116128 with :
117129 name : Update-${{ matrix.label }}${{ inputs.version != '' && format('-{0}', inputs.version) || '' }}
118- path : dist/Update${{ matrix.label == 'windows' && '.exe' || '' }}
130+ path : dist/Update${{ matrix.exe_suffix }}
119131 if-no-files-found : error
120132
121133 - name : Upload Installer artifact
122134 uses : actions/upload-artifact@v4
123135 with :
124136 name : Installer-${{ matrix.label }}${{ inputs.version != '' && format('-{0}', inputs.version) || '' }}
125- path : dist/Installer${{ matrix.label == 'windows' && '.exe' || '' }}
137+ path : dist/Installer${{ matrix.exe_suffix }}
126138 if-no-files-found : error
0 commit comments