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>
70 lines
2.2 KiB
Python
70 lines
2.2 KiB
Python
"""Wrap the PyInstaller folder build into a portable .zip.
|
|
|
|
Self-contained download: unzip → double-click the launcher → app runs.
|
|
No installer, no Python install, no admin rights required.
|
|
|
|
Usage:
|
|
python build/build_portable_zip.py <platform> <version>
|
|
|
|
Where ``platform`` is one of ``win`` / ``mac`` / ``linux``. The
|
|
script just produces a generic ``dist/DataTools/`` zip; on macOS the
|
|
preferred portable format is the ``ditto``-wrapped .app — see
|
|
``build/macos/build_zip.sh`` for that flow. This helper exists mainly
|
|
for Windows + Linux, where there's no .app bundle to wrap.
|
|
|
|
Output:
|
|
dist/DataTools-<version>-<platform>-portable.zip
|
|
|
|
The zip root is the ``DataTools/`` folder so an unzip produces a
|
|
self-contained dir the user can drop anywhere (Desktop, USB stick,
|
|
network share). On Windows, the launcher is ``DataTools.exe`` inside
|
|
that folder; on Linux, ``DataTools``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import shutil
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
REPO = Path(__file__).resolve().parent.parent
|
|
DIST_DIR = REPO / "dist"
|
|
BUNDLE_DIR = DIST_DIR / "DataTools"
|
|
|
|
|
|
def main() -> int:
|
|
if len(sys.argv) < 3:
|
|
sys.stderr.write(
|
|
"usage: python build/build_portable_zip.py <platform> <version>\n"
|
|
)
|
|
return 2
|
|
platform = sys.argv[1]
|
|
version = sys.argv[2]
|
|
|
|
if not BUNDLE_DIR.is_dir():
|
|
sys.stderr.write(
|
|
f"Bundle dir not found at {BUNDLE_DIR}.\n"
|
|
"Run ``pyinstaller build/datatools.spec --clean --noconfirm`` first.\n"
|
|
)
|
|
return 1
|
|
|
|
out_stem = DIST_DIR / f"DataTools-{version}-{platform}-portable"
|
|
# ``make_archive`` takes a base name (no extension) and produces
|
|
# ``<base>.zip``. ``root_dir`` = parent of what we want compressed,
|
|
# ``base_dir`` = the folder name inside the archive root. This
|
|
# combo yields a single top-level ``DataTools/`` directory inside
|
|
# the .zip rather than dumping its contents loose.
|
|
archive = shutil.make_archive(
|
|
base_name=str(out_stem),
|
|
format="zip",
|
|
root_dir=str(DIST_DIR),
|
|
base_dir="DataTools",
|
|
)
|
|
size_mb = Path(archive).stat().st_size / (1024 * 1024)
|
|
print(f"wrote {archive} ({size_mb:.1f} MB)")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|