# -*- mode: python ; coding: utf-8 -*- """PyInstaller spec for the Behavision engine. One-folder, not one-file. A onefile build of this is ~200 MB and extracts the whole thing to a temp directory on **every** start, which on a store PC means a multi-second delay and an antivirus scan each time the service restarts. Models are NOT bundled. They are ~200 MB on their own and `setup-models` already downloads them with a resumable `.part`-then-rename; bundling them would triple the installer and force a re-sign for a model change. They land in the writable state root (see behavision/paths.py), not next to the code. Build: pyinstaller behavision.spec --noconfirm Output: dist/behavision/behavision.exe """ import sys from PyInstaller.utils.hooks import collect_dynamic_libs, collect_submodules block_cipher = None # The dashboard and the default config are read from disk at runtime, so they # have to travel with the code. They go to the *install* root; anything the app # writes goes to the state root instead. datas = [ ("behavision/static", "behavision/static"), ("config/default.yaml", "config"), ] # onnxruntime and cv2 load native libraries that PyInstaller's static analysis # cannot see through. Missing these is the classic "works in the venv, dies in # the bundle" failure. binaries = [] for pkg in ("onnxruntime", "cv2"): try: binaries += collect_dynamic_libs(pkg) except Exception: pass hiddenimports = [ # Imported lazily inside functions, so the graph never sees them. "dotenv", "uvicorn.logging", "uvicorn.loops.auto", "uvicorn.protocols.http.auto", "uvicorn.protocols.websockets.auto", "uvicorn.lifespan.on", ] for pkg in ("onnxruntime", "faiss"): try: hiddenimports += collect_submodules(pkg) except Exception: # faiss is optional - the numpy fallback is exact and identical, just # slower, so its absence must not fail the build. pass a = Analysis( ["behavision/__main__.py"], pathex=[], binaries=binaries, datas=datas, hiddenimports=hiddenimports, hookspath=[], runtime_hooks=[], # Nothing here draws a window; matplotlib/tkinter would add ~40 MB of # payload that no code path can reach. excludes=["tkinter", "matplotlib", "PyQt5", "PySide2", "IPython", "notebook", "pytest"], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=block_cipher, noarchive=False, ) pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) exe = EXE( pyz, a.scripts, [], exclude_binaries=True, name="behavision", debug=False, bootloader_ignore_signals=False, strip=False, upx=False, # UPX-packed binaries are a common AV false positive console=True, # the tray app is the GUI; this is the engine disable_windowed_traceback=False, target_arch=None, codesign_identity=None, entitlements_file=None, ) coll = COLLECT( exe, a.binaries, a.zipfiles, a.datas, strip=False, upx=False, name="behavision", )