#!/usr/bin/env python3 """ Build script — creates platform-specific executables via PyInstaller. Run on the target OS to build for that platform. Usage: python build.py # build for current platform python build.py --clean # clean build artifacts first """ import os import sys import shutil import platform # Add project root sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from version import __version__, __app_name__ PROJECT_DIR = os.path.dirname(os.path.abspath(__file__)) DIST_DIR = os.path.join(PROJECT_DIR, "dist") BUILD_DIR = os.path.join(PROJECT_DIR, "build") RELEASES_DIR = os.path.join(PROJECT_DIR, "releases") def get_platform_tag() -> str: system = platform.system().lower() machine = platform.machine().lower() arch_map = { "x86_64": "x64", "amd64": "x64", "x86": "x32", "i686": "x32", "i386": "x32", "aarch64": "arm64", "arm64": "arm64", "armv7l": "arm", } arch = arch_map.get(machine, machine) os_map = {"windows": "win", "linux": "linux", "darwin": "mac"} os_tag = os_map.get(system, system) return f"{os_tag}-{arch}" def clean(): for d in [DIST_DIR, BUILD_DIR]: if os.path.exists(d): shutil.rmtree(d) for f in os.listdir(PROJECT_DIR): if f.endswith(".spec"): os.remove(os.path.join(PROJECT_DIR, f)) print("Cleaned build artifacts") def build(): tag = get_platform_tag() print(f"Building {__app_name__} v{__version__} for {tag}...") system = platform.system().lower() # PyInstaller command cmd_parts = [ sys.executable, "-m", "PyInstaller", "--onefile", "--windowed", f"--name={__app_name__}", "--add-data", f"config/servers.example.json{os.pathsep}config", "--add-data", f"tools/ssh.py{os.pathsep}tools", "--add-data", f"tools/skill-ssh.md{os.pathsep}tools", "--add-data", f"core/encryption.py{os.pathsep}core", ] # Icon icon_path = os.path.join(PROJECT_DIR, "assets", "icon.ico") if os.path.exists(icon_path): cmd_parts.extend(["--icon", icon_path]) # Hidden imports for customtkinter cmd_parts.extend([ "--hidden-import", "customtkinter", "--hidden-import", "PIL", "--hidden-import", "pyotp", "--hidden-import", "pyte", "--hidden-import", "psutil", "--collect-all", "customtkinter", ]) cmd_parts.append("main.py") os.chdir(PROJECT_DIR) ret = os.system(" ".join(f'"{p}"' if " " in p else p for p in cmd_parts)) if ret != 0: print(f"Build failed with code {ret}") sys.exit(1) # Move to releases os.makedirs(RELEASES_DIR, exist_ok=True) if system == "windows": src = os.path.join(DIST_DIR, f"{__app_name__}.exe") dst = os.path.join(RELEASES_DIR, f"{__app_name__}-v{__version__}-{tag}.exe") elif system == "darwin": src = os.path.join(DIST_DIR, __app_name__) dst = os.path.join(RELEASES_DIR, f"{__app_name__}-v{__version__}-{tag}") else: src = os.path.join(DIST_DIR, __app_name__) dst = os.path.join(RELEASES_DIR, f"{__app_name__}-v{__version__}-{tag}") if os.path.exists(src): shutil.copy2(src, dst) size_mb = os.path.getsize(dst) / (1024 * 1024) print(f"\nBuild complete: {dst} ({size_mb:.1f} MB)") else: print(f"Build output not found: {src}") sys.exit(1) if __name__ == "__main__": if "--clean" in sys.argv: clean() build()