Files
datatools-dev/build/generate_icons.py
Michael 9c426194b1 build: add single-command release script + portable zip artifacts
One-developer workflow: ``python build/make_release.py`` on each
target OS produces both the installer and a portable .zip for that
platform. Preflight checks PyInstaller / Pillow / iscc / hdiutil /
ditto / appimagetool and bails with install hints if anything is
missing — no half-built dist/.

New scripts:
- build/make_release.py   — orchestrator, auto-detects host OS.
- build/generate_icons.py — icon.ico / icon.icns / icon.png from
  src/gui/assets/datatools_icon_256.png (Pillow ships ICO + ICNS
  writers; no platform tooling needed).
- build/build_portable_zip.py — Win/Linux portable zip via stdlib.
- build/macos/build_zip.sh — Mac portable .app via ditto so
  bundle metadata survives.

installer.iss now adds: Quick Launch task (opt-in, legacy Win 7),
App Paths registry entry (Win+R "DataTools" works), SetupIconFile,
UninstallDisplayIcon, AppSupportURL, AppUpdatesURL.

CI workflow uploads installer + portable per platform and attaches
both to GitHub Releases on tag push.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 19:30:17 +00:00

79 lines
2.7 KiB
Python

"""Generate platform-specific app icons from the source PNG asset.
Outputs:
build/icon.ico Windows multi-resolution icon (16..256 px sizes).
build/icon.icns macOS icon bundle (16..1024 px scaled tiers).
build/icon.png Plain 256x256 PNG used by the Linux AppImage.
Source: ``src/gui/assets/datatools_icon_256.png`` (the same icon
``st.set_page_config`` uses, so the installer / Dock / Taskbar match
the in-app tab favicon).
Run manually:
python build/generate_icons.py
CI runs this automatically before invoking PyInstaller (see
``.github/workflows/build.yml``). Both files are .gitignored — they
are build artifacts derived from the committed PNG.
Self-contained: pulls only Pillow (already a transitive dep of
``pdfplumber``) so no extra installs are required.
"""
from __future__ import annotations
import sys
from pathlib import Path
from PIL import Image
# Repo layout: this script lives at <REPO>/build/. The source PNG is at
# <REPO>/src/gui/assets/datatools_icon_256.png.
BUILD_DIR = Path(__file__).resolve().parent
REPO = BUILD_DIR.parent
SOURCE_PNG = REPO / "src" / "gui" / "assets" / "datatools_icon_256.png"
# Windows ICO needs every size the OS might render at: taskbar (16/24),
# Start Menu (32/48), tile (64/128), shell properties dialog (256).
ICO_SIZES = [(16, 16), (24, 24), (32, 32), (48, 48), (64, 64),
(128, 128), (256, 256)]
def main() -> int:
if not SOURCE_PNG.exists():
sys.stderr.write(
f"Source icon not found at {SOURCE_PNG}.\n"
"Add a 256x256 (or larger) RGBA PNG there and re-run.\n"
)
return 1
src = Image.open(SOURCE_PNG).convert("RGBA")
if src.size[0] < 256 or src.size[1] < 256:
sys.stderr.write(
f"Source icon is {src.size}; recommend 256x256 or larger "
"so downscaled tiers look crisp.\n"
)
ico_path = BUILD_DIR / "icon.ico"
src.save(ico_path, format="ICO", sizes=ICO_SIZES)
print(f"wrote {ico_path} ({ico_path.stat().st_size:,} bytes)")
icns_path = BUILD_DIR / "icon.icns"
# Pillow's ICNS writer derives the per-tier sizes from the source
# image; passing a 256x256 source yields ic07..ic12 entries which
# cover Finder, Dock, and the Get Info panel.
src.save(icns_path, format="ICNS")
print(f"wrote {icns_path} ({icns_path.stat().st_size:,} bytes)")
# AppImage uses a plain PNG for its desktop entry. Copy the source
# so the AppImage build script doesn't have to know the asset path.
png_path = BUILD_DIR / "icon.png"
src.save(png_path, format="PNG")
print(f"wrote {png_path} ({png_path.stat().st_size:,} bytes)")
return 0
if __name__ == "__main__":
sys.exit(main())