#!/usr/bin/env python3 """ Build script for bundling GlowPath backend with PyInstaller. This script handles the entire build process including dependency installation and cleanup. """ import os import sys import subprocess import shutil import argparse from pathlib import Path def run_command(cmd, cwd=None, check=True): """Run a command and return the result.""" print(f"Running: {' '.join(cmd)}") result = subprocess.run(cmd, cwd=cwd, check=check, capture_output=True, text=True) if result.stdout: print(result.stdout) if result.stderr: print(result.stderr, file=sys.stderr) return result def clean_build(): """Clean previous build artifacts.""" print("Cleaning previous build artifacts...") dirs_to_clean = ["build", "dist", "__pycache__"] files_to_clean = ["*.pyc"] for dir_name in dirs_to_clean: if os.path.exists(dir_name): print(f"Removing {dir_name}") shutil.rmtree(dir_name) # Clean .pyc files for root, dirs, files in os.walk("."): for file in files: if file.endswith(".pyc"): os.remove(os.path.join(root, file)) def install_dependencies(): """Install build dependencies.""" print("Installing build dependencies...") try: # Try uv first (faster) - sync dev dependencies run_command(["uv", "sync", "--group", "dev"]) except (subprocess.CalledProcessError, FileNotFoundError): # Fallback to pip run_command( [ sys.executable, "-m", "pip", "install", "pyinstaller", "pyinstaller-hooks-contrib", ] ) def build_executable(debug=False): """Build the executable using PyInstaller.""" print("Building executable with PyInstaller...") cmd = ["pyinstaller"] if debug: cmd.extend(["--debug", "all"]) cmd.extend(["--clean", "--noconfirm", "glowpath-backend.spec"]) run_command(cmd) def create_distribution(): """Create a distribution package.""" print("Creating distribution package...") dist_dir = Path("dist") if not dist_dir.exists(): print("No dist directory found. Build may have failed.") return False # Create a distribution folder with all necessary files app_name = "glowpath-backend" bundle_dir = dist_dir / f"{app_name}-bundle" if bundle_dir.exists(): shutil.rmtree(bundle_dir) bundle_dir.mkdir() # Copy the executable exe_path = dist_dir / app_name if exe_path.exists(): if sys.platform == "win32": exe_path = exe_path.with_suffix(".exe") shutil.copy2(exe_path, bundle_dir) # Create a simple startup script if sys.platform != "win32": startup_script = bundle_dir / "start.sh" startup_script.write_text( f"""#!/bin/bash cd "$(dirname "$0")" ./{app_name} "$@" """ ) startup_script.chmod(0o755) else: startup_script = bundle_dir / "start.bat" startup_script.write_text( f"""@echo off cd /d "%~dp0" {app_name}.exe %* """ ) # Create README readme = bundle_dir / "README.txt" readme.write_text( f"""GlowPath Backend =============== This is a bundled version of the GlowPath backend application. To run the application: - On Unix/Linux/macOS: ./start.sh - On Windows: start.bat - Or run the executable directly: ./{app_name}{"" if sys.platform != "win32" else ".exe"} The application will start a web server. Check the console output for the URL to access the interface. """ ) print(f"Distribution package created in: {bundle_dir}") return True def main(): parser = argparse.ArgumentParser(description="Build GlowPath backend executable") parser.add_argument("--debug", action="store_true", help="Build in debug mode") parser.add_argument( "--no-clean", action="store_true", help="Skip cleaning build artifacts" ) parser.add_argument( "--no-deps", action="store_true", help="Skip installing dependencies" ) args = parser.parse_args() try: if not args.no_clean: clean_build() if not args.no_deps: install_dependencies() build_executable(debug=args.debug) if create_distribution(): print("\n✅ Build completed successfully!") print(f"Executable and bundle available in the 'dist' directory") else: print("\n❌ Build failed!") sys.exit(1) except subprocess.CalledProcessError as e: print(f"\n❌ Build failed with error: {e}") sys.exit(1) except KeyboardInterrupt: print("\n⚠️ Build interrupted by user") sys.exit(1) if __name__ == "__main__": main()