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>
79 lines
2.7 KiB
Python
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())
|