"""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 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---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 \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 # ``.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())