#!/usr/bin/env python3 """ build_app.py — DBClient packaging helper Run this script to build a distributable bundle of DBClient using PyInstaller. Usage ----- python build_app.py [--onefile] [--clean] [--no-upx] Options ------- --onefile Build a single .exe instead of a one-folder bundle --clean Delete build/ and dist/ before building --no-upx Disable UPX compression (useful if UPX is not installed) Output ------ dist/DBClient/ ← one-folder bundle (default) dist/DBClient.exe ← single-file bundle (--onefile) """ import argparse import os import shutil import subprocess import sys def parse_args(): p = argparse.ArgumentParser(description="Build DBClient with PyInstaller") p.add_argument("--onefile", action="store_true", help="Build a single .exe (instead of one-folder)") p.add_argument("--clean", action="store_true", help="Remove build/ and dist/ directories first") p.add_argument("--no-upx", action="store_true", help="Disable UPX compression") return p.parse_args() def check_pyinstaller(): try: import PyInstaller # noqa: F401 except ImportError: print("❌ PyInstaller is not installed.") print(" Run: pip install pyinstaller") sys.exit(1) print(f"✅ PyInstaller found: {PyInstaller.__version__}") def clean_dirs(): for d in ("build", "dist", "__pycache__"): if os.path.exists(d): print(f"🗑 Removing {d}/") shutil.rmtree(d) def build(args): cmd = [sys.executable, "-m", "PyInstaller", "DBClient.spec", "--noconfirm"] if args.onefile: # Patch the spec to use the single-file EXE (quick-and-dirty approach: # just pass --onefile to PyInstaller alongside the spec — PyInstaller # will override the COLLECT step). cmd.append("--onefile") print("📦 Building single-file executable…") else: print("📦 Building one-folder bundle…") if args.no_upx: cmd.append("--noupx") print(" Command:", " ".join(cmd)) print() result = subprocess.run(cmd, check=False) return result.returncode def report(args, returncode): print() if returncode == 0: if args.onefile: out = os.path.join("dist", "DBClient.exe") else: out = os.path.join("dist", "DBClient") print(f"✅ Build succeeded!") print(f" Output: {os.path.abspath(out)}") if not args.onefile: print(f" Run: {os.path.join(out, 'DBClient.exe')}") else: print(f"❌ Build failed with exit code {returncode}.") print(" Check the output above for errors.") print() print("Common fixes:") print(" • pyodbc errors → ensure the ODBC runtime is installed") print(" • Missing module → add it to hiddenimports in DBClient.spec") print(" • UPX errors → run with --no-upx") def main(): args = parse_args() # Make sure we're running from the repo root spec = "DBClient.spec" if not os.path.exists(spec): print(f"❌ {spec} not found. Run this script from the DBClient root directory.") sys.exit(1) check_pyinstaller() if args.clean: clean_dirs() returncode = build(args) report(args, returncode) sys.exit(returncode) if __name__ == "__main__": main()