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>
This commit is contained in:
69
build/build_portable_zip.py
Normal file
69
build/build_portable_zip.py
Normal file
@@ -0,0 +1,69 @@
|
||||
"""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())
|
||||
Reference in New Issue
Block a user