Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ permissions:
contents: write
issues: write
pull-requests: write
# Needed by the "Build and attach desktop installers" step, which dispatches
# the release-binaries workflow via the GitHub API (gh workflow run).
actions: write

jobs:
release:
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ MANIFEST
# PyInstaller
*.manifest
*.spec
# ... but our hand-authored, checked-in build spec IS tracked (the release CI
# runs `pyinstaller packaging/gleplot.spec`; without this negation the *.spec
# rule above silently kept it out of the repo, so CI builds could never start).
!packaging/gleplot.spec

# Installer logs
pip-log.txt
Expand Down
112 changes: 112 additions & 0 deletions packaging/gleplot.spec
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# -*- mode: python ; coding: utf-8 -*-
#
# PyInstaller spec for the gleplot GUI editor.
#
# Layout: onedir (a directory bundle) on all platforms, wrapped in a macOS
# .app BUNDLE on Darwin. Windows CI feeds the resulting dist/gleplot directory
# to Inno Setup (packaging/windows/gleplot.iss); macOS CI wraps dist/gleplot.app
# into a .dmg (packaging/macos/dmg_settings.py).
#
# The icon path is supplied by CI via the PYI_ICON_PATH environment variable
# (.ico on Windows, .icns on macOS, generated from
# src/gleplot/gui/assets/gleplot.png). If unset or missing we fall back to no
# icon rather than failing the build.
#
# NOTE: gleplot's GUI shells out to an EXTERNAL `gle` binary at runtime. GLE is
# NOT bundled here; it is a runtime prerequisite the user installs separately.

from __future__ import annotations

import os
import sys
from pathlib import Path

from PyInstaller.utils.hooks import copy_metadata


# SPECPATH is the directory containing this spec file (packaging/), so the
# project root is its parent.
project_root = Path(SPECPATH).resolve().parent
src_root = project_root / "src"
entry_script = src_root / "gleplot" / "gui" / "app.py"

# Icon is generated per-platform by CI from src/gleplot/gui/assets/gleplot.png.
icon_path = os.environ.get("PYI_ICON_PATH")
if icon_path and not Path(icon_path).is_file():
icon_path = None

# Bundle the package's own dist-info so importlib.metadata can read the version
# from inside the frozen app (gleplot reports its own version at runtime).
datas = copy_metadata("gleplot")

# Bundle the window-icon PNG so app.py can set the window icon at runtime via
# importlib.resources on the gleplot.gui package.
icon_png = src_root / "gleplot" / "gui" / "assets" / "gleplot.png"
if icon_png.is_file():
datas += [(str(icon_png), "gleplot/gui/assets")]

# PySide6 has a bundled PyInstaller hook that collects the Qt runtime, so we do
# not manually collect its submodules. numpy is likewise picked up by static
# analysis of the imports in gleplot's core. Keep the spec minimal.
hiddenimports: list[str] = []

# Exclude heavy modules gleplot never imports. matplotlib appears only in
# comments/docstrings and marker-name strings (see gleplot.markers /
# gleplot.axes) — there is no `import matplotlib`, so excluding it is safe.
excludes = [
"tkinter",
"pytest",
"matplotlib",
"IPython",
"notebook",
"sphinx",
]

a = Analysis(
[str(entry_script)],
pathex=[str(src_root)],
binaries=[],
datas=datas,
hiddenimports=hiddenimports,
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=excludes,
win_no_prefer_redirects=False,
win_private_assemblies=False,
noarchive=False,
)

pyz = PYZ(a.pure)

exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name="gleplot",
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=False,
console=False,
icon=icon_path,
)

coll = COLLECT(
exe,
a.binaries,
a.datas,
strip=False,
upx=False,
upx_exclude=[],
name="gleplot",
)

if sys.platform == "darwin":
app = BUNDLE(
coll,
name="gleplot.app",
icon=icon_path if icon_path and icon_path.endswith(".icns") else None,
bundle_identifier="io.github.benhuddart.gleplot",
)
Loading