mirror of
https://github.com/yusufipk/dikte.git
synced 2026-09-11 10:56:10 +00:00
Qt's xcb plugin links libxkbcommon and libxkbcommon-x11, and the x11 half hands keymap objects to the core half to free, so the pair must come from one build. The build machine has only the core half installed, so PyInstaller bundled that one alone and the other kept loading from the user's system; an Ubuntu 22.04 core freeing what a current x11 half allocated is the segfault of issue #57, which a Turkish layout happened to move to startup. Every desktop that can show a window carries both libraries from one build, so the bundle now carries neither. Fixes #57
150 lines
6.6 KiB
RPMSpec
150 lines
6.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,
|
|
)
|
|
|
|
# Qt's xcb platform plugin uses libxkbcommon in two halves: the core library
|
|
# and libxkbcommon-x11, which allocates keymap objects and hands them to the
|
|
# core half to use and free, so the two must come from the same build. The
|
|
# build machine has only the core half installed, which had PyInstaller
|
|
# bundling that one while the other kept coming from the user's system, and a
|
|
# 22.04-era core freeing what a current x11 half allocated is the startup
|
|
# crash of issue #57. Ship neither: any desktop that can show a window
|
|
# carries both, from one build.
|
|
if not (MACOS or WINDOWS):
|
|
analysis.binaries = [entry for entry in analysis.binaries
|
|
if "libxkbcommon" not in entry[0]]
|
|
|
|
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.
|
|
#
|
|
# Named apart from the windowed one rather than `dikte` beside `Dikte`.
|
|
# Windows matches a filename without regard to its case, so those two are one
|
|
# file in one directory: whichever PyInstaller writes second is the only one
|
|
# installed, and a build made on a case-sensitive filesystem never sees it.
|
|
console_executable = EXE( # noqa: F821
|
|
archive,
|
|
analysis.scripts,
|
|
[],
|
|
exclude_binaries=True,
|
|
name="dikte-cli",
|
|
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.",
|
|
},
|
|
)
|