Files
dikte/packaging/dikte.spec
T
yusufipek 23d56acfb5 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.
2026-08-18 16:08:47 +03:00

133 lines
5.6 KiB
RPMSpec

# PyInstaller's description of the build, shared by the AppImage, the disk
# image and the Windows setup. Run it through build-appimage.sh, build-dmg.sh
# or build-windows.ps1 rather than by hand: each of those has a few steps of
# its own on either side of this.
#
# A directory rather than a single file, on all three. Onefile unpacks itself
# into a temporary directory on every start, which for something a global
# shortcut is meant to bring up is a second of nothing happening, and for the
# AppImage it would be an unpacking inside an unpacking. The single file people
# download is the AppImage, the .dmg and the setup program; this only has to be
# tidy inside them.
import os
import pathlib
import re
import sys
ROOT = pathlib.Path(SPECPATH).parent # noqa: F821 (PyInstaller's)
# Read rather than imported. Putting the checkout on sys.path to import dikte
# would put this directory there under the name `packaging`, which is a real
# library that PyInstaller itself uses, and a spec file is no place to find out
# whether that matters.
__version__ = re.search(r'^__version__ = "(.*)"$',
(ROOT / "dikte" / "__init__.py").read_text(),
re.M).group(1)
MACOS = sys.platform == "darwin"
WINDOWS = sys.platform == "win32"
BUNDLE_ID = "io.github.yusufipk.dikte"
# PyQt6's wheel is most of the build, and most of the wheel is modules nothing
# here imports: Qt ships a browser engine, three declarative UI stacks and a
# 3D renderer. Naming them keeps the download to something a person on a slow
# connection will actually finish. Only the four in dikte's imports are left.
UNUSED_QT = [
"PyQt6." + name for name in (
"Qt3DAnimation", "Qt3DCore", "Qt3DExtras", "Qt3DInput", "Qt3DLogic",
"Qt3DRender", "QtBluetooth", "QtCharts", "QtDataVisualization",
"QtDesigner", "QtHelp", "QtLocation", "QtMultimedia",
"QtMultimediaWidgets", "QtNfc", "QtPdf", "QtPdfWidgets",
"QtPositioning", "QtQml", "QtQuick", "QtQuick3D", "QtQuickWidgets",
"QtRemoteObjects", "QtSensors", "QtSerialPort", "QtSpatialAudio",
"QtSql", "QtTest", "QtTextToSpeech", "QtWebChannel", "QtWebEngineCore",
"QtWebEngineQuick", "QtWebEngineWidgets", "QtWebSockets",
)
]
analysis = Analysis( # noqa: F821
[str(ROOT / "packaging" / "entry.py")],
pathex=[str(ROOT)],
hiddenimports=["PyQt6.QtNetwork"],
# tkinter is the other GUI toolkit CPython ships and would be dead weight;
# dikte's own tests have no business in a build at all.
excludes=UNUSED_QT + ["tkinter", "tests"],
noarchive=False,
)
archive = PYZ(analysis.pure) # noqa: F821
executable = EXE( # noqa: F821
archive,
analysis.scripts,
[],
exclude_binaries=True,
name="Dikte" if MACOS or WINDOWS else "dikte",
console=False,
# Every platform uses whatever the machine is, because no build here is
# cross-compiled: the workflow runs one job per architecture.
target_arch=None,
# Ad-hoc, and only on a Mac, where an arm64 binary that carries no
# signature at all is refused by the kernel rather than merely warned
# about. build-dmg.sh signs the finished bundle over the top of this.
codesign_identity="-" if MACOS else None,
# Windows keeps the icon inside the executable, and build-windows.ps1 draws
# it from the same shapes the tray uses. A Mac reads the one BUNDLE names
# below, and the AppImage installs PNGs into the icon theme instead.
icon=os.environ.get("DIKTE_ICO") or None,
)
# The same program a second time, as a console application, and only on
# Windows. A windowed executable there is one the loader gives no console and
# no standard output at all, so `dikte doctor` started from a terminal would
# print nothing to it and answer nothing to a script. Everywhere else the one
# executable does both jobs: a terminal that started it keeps its output, and
# nothing opens a window nobody asked for.
console_executable = EXE( # noqa: F821
archive,
analysis.scripts,
[],
exclude_binaries=True,
name="dikte",
console=True,
target_arch=None,
icon=os.environ.get("DIKTE_ICO") or None,
) if WINDOWS else None
collection = COLLECT( # noqa: F821
executable,
*([console_executable] if WINDOWS else []),
analysis.binaries,
analysis.datas,
name="dikte",
)
if MACOS:
# LSUIElement is the line that makes this a menu bar application: no Dock
# icon, no menu of its own, nothing in the app switcher. The usage strings
# are not decoration either, they are what the permission dialogs read out,
# and a bundle that asks for the microphone without one is killed rather
# than asked about.
app = BUNDLE( # noqa: F821
collection,
name="Dikte.app",
icon=os.environ.get("DIKTE_ICNS") or None,
bundle_identifier=BUNDLE_ID,
version=__version__,
info_plist={
"CFBundleName": "Dikte",
"CFBundleDisplayName": "Dikte",
"CFBundleShortVersionString": __version__,
"CFBundleVersion": __version__,
"LSMinimumSystemVersion": "11.0",
"LSUIElement": True,
"NSHighResolutionCapable": True,
"NSMicrophoneUsageDescription":
"Dikte records what you dictate so that it can be transcribed.",
"NSAppleEventsUsageDescription":
"Dikte puts the transcript on the clipboard and pastes it into "
"the window you were typing in.",
},
)