From aadc31dfca2ec96d1be72b016042554605eccc4f Mon Sep 17 00:00:00 2001 From: Ben Huddart Date: Sun, 5 Jul 2026 22:43:38 +0100 Subject: [PATCH 1/2] fix(ci): grant actions:write so release can dispatch the binary build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "Build and attach desktop installers" step in release.yml calls `gh workflow run release-binaries.yml` via the GitHub API, which requires `actions: write`. The job only had contents/issues/pull-requests write, so the dispatch failed with HTTP 403 "Resource not accessible by integration" on the v1.4.0 release — the release + wheels published fine, but the desktop installers were never kicked off. Add the missing permission so every future release automatically builds and attaches the Windows .exe / macOS .dmg. (v1.4.0's installers were attached via a manual one-off dispatch.) Co-Authored-By: Claude Opus 4.8 --- .github/workflows/release.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2ef0e9e..9f55f54 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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: From 87f135d6a7c83608e194380792ceca5eafa109dc Mon Sep 17 00:00:00 2001 From: Ben Huddart Date: Sun, 5 Jul 2026 22:46:17 +0100 Subject: [PATCH 2/2] =?UTF-8?q?fix(packaging):=20commit=20gleplot.spec=20?= =?UTF-8?q?=E2=80=94=20the=20*.spec=20gitignore=20rule=20excluded=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit release-binaries.yml runs `pyinstaller packaging/gleplot.spec`, but that file was never in the repo: the generic PyInstaller `*.spec` rule in .gitignore (meant for auto-generated specs) silently swallowed our hand-authored one. So both desktop build jobs failed at the very first step with: ERROR: Spec file "packaging/gleplot.spec" not found! This was never caught because the binary workflow had never actually run before v1.4.0. Add a `!packaging/gleplot.spec` negation and commit the file. Same class of bug as the earlier *.png/asset exclusion fixed in PR #4. Co-Authored-By: Claude Opus 4.8 --- .gitignore | 4 ++ packaging/gleplot.spec | 112 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 packaging/gleplot.spec diff --git a/.gitignore b/.gitignore index ae0c3f8..68371ab 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/packaging/gleplot.spec b/packaging/gleplot.spec new file mode 100644 index 0000000..9c810db --- /dev/null +++ b/packaging/gleplot.spec @@ -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", + )