#!/usr/bin/env python3 """ PyInstaller build script for drone simulation executables. Creates standalone executables that include PyBullet and dependencies. Usage: python build_exe.py # Build standalone_simulation python build_exe.py simulation_host # Build simulation_host python build_exe.py standalone # Build standalone_simulation python build_exe.py ardupilot # Build ArduPilot launcher python build_exe.py camera_viewer # Build camera feed viewer python build_exe.py all # Build all """ import argparse import os import platform import sys from pathlib import Path try: import PyInstaller.__main__ import pybullet_data except ImportError as e: print(f"Missing dependency: {e}") print("Install with: pip install pyinstaller pybullet") sys.exit(1) # Check for pymavlink (optional for ArduPilot builds) try: from pymavlink import mavutil PYMAVLINK_AVAILABLE = True except ImportError: PYMAVLINK_AVAILABLE = False def get_pybullet_data_path() -> str: return pybullet_data.getDataPath() def build_executable( source_name: str, output_name: str, console: bool = True, hidden_imports: list = None, collect_data: list = None ): """Build a single executable.""" script_dir = Path(__file__).parent source_file = script_dir / source_name if not source_file.exists(): print(f"Error: {source_file} not found!") return False print(f"\nBuilding: {source_name} -> {output_name}") print("-" * 40) system = platform.system().lower() pybullet_path = get_pybullet_data_path() if system == 'windows': separator = ';' else: separator = ':' data_spec = f"{pybullet_path}{separator}pybullet_data" build_args = [ str(source_file), '--onefile', '--clean', f'--name={output_name}', f'--add-data={data_spec}', ] # Add hidden imports if specified if hidden_imports: for imp in hidden_imports: build_args.append(f'--hidden-import={imp}') # Add data collection for packages if collect_data: for pkg in collect_data: build_args.append(f'--collect-data={pkg}') if console: build_args.append('--console') else: if system in ['windows', 'darwin']: build_args.append('--windowed') else: build_args.append('--console') try: PyInstaller.__main__.run(build_args) dist_dir = script_dir / "dist" if system == 'windows': exe_path = dist_dir / f"{output_name}.exe" elif system == 'darwin' and not console: exe_path = dist_dir / f"{output_name}.app" else: exe_path = dist_dir / output_name print(f" Created: {exe_path}") return True except Exception as e: print(f" Build failed: {e}") return False def main(): parser = argparse.ArgumentParser(description='Build simulation executables') parser.add_argument( 'target', nargs='?', default='standalone', choices=['standalone', 'simulation_host', 'ardupilot', 'mavlink_bridge', 'camera_viewer', 'all'], help='What to build (default: standalone)' ) args = parser.parse_args() print("=" * 60) print(" DRONE SIMULATION - BUILD EXECUTABLES") print("=" * 60) print(f"Platform: {platform.system()}") print(f"PyBullet data: {get_pybullet_data_path()}") print(f"pymavlink: {'Available' if PYMAVLINK_AVAILABLE else 'Not installed'}") success = True # Build standalone simulation if args.target in ['standalone', 'all']: success &= build_executable( 'standalone_simulation.py', 'drone_simulation', console=False ) # Build simulation host if args.target in ['simulation_host', 'all']: success &= build_executable( 'simulation_host.py', 'simulation_host', console=True ) # Build MAVLink bridge (requires pymavlink) if args.target in ['mavlink_bridge', 'all']: if not PYMAVLINK_AVAILABLE: print("\nWarning: pymavlink not installed, skipping mavlink_bridge build") print("Install with: pip install pymavlink") if args.target == 'mavlink_bridge': success = False else: success &= build_executable( 'mavlink_bridge.py', 'mavlink_bridge', console=True, hidden_imports=[ 'pymavlink', 'pymavlink.mavutil', 'pymavlink.dialects.v20.ardupilotmega', ], collect_data=['pymavlink'] ) # Build ArduPilot runner (requires pymavlink) if args.target in ['ardupilot', 'all']: if not PYMAVLINK_AVAILABLE: print("\nWarning: pymavlink not installed, skipping ardupilot build") print("Install with: pip install pymavlink") if args.target == 'ardupilot': success = False else: success &= build_executable( 'run_ardupilot.py', 'run_ardupilot', console=True, hidden_imports=[ 'pymavlink', 'pymavlink.mavutil', 'pymavlink.dialects.v20.ardupilotmega', 'mavlink_bridge', 'drone_controller', 'rover_controller', ], collect_data=['pymavlink'] ) # Build camera viewer (requires opencv) if args.target in ['camera_viewer', 'all']: try: import cv2 success &= build_executable( 'camera_viewer.py', 'camera_viewer', console=True, hidden_imports=[ 'cv2', 'numpy', ], collect_data=['cv2'] ) except ImportError: print("\nWarning: opencv-python not installed, skipping camera_viewer build") print("Install with: pip install opencv-python") if args.target == 'camera_viewer': success = False print() print("=" * 60) if success: print(" BUILD COMPLETE!") print(" Executables in: dist/") print() print(" Available executables:") dist_dir = Path(__file__).parent / "dist" if dist_dir.exists(): for exe in dist_dir.iterdir(): if exe.is_file() and not exe.name.startswith('.'): print(f" - {exe.name}") else: print(" BUILD FAILED!") sys.exit(1) print("=" * 60) if __name__ == '__main__': main()