# PyInstaller spec: `pip install -e ".[bundle]"` then `pyinstaller applepy.spec` # Produces dist/applepy (single self-contained binary) with bundled applepy data (JSON, optional compliance trees). # Run scripts/vendor_compliance_assets.sh before building to embed mSCP + Lynis (not in git). from pathlib import Path from PyInstaller.utils.hooks import collect_data_files # Root of the repo when building; PyInstaller defines SPECPATH (not __file__) for .spec evaluation. _SPEC_DIR = Path(SPECPATH) def _mscp_data_files_excluding_generated(mscp: Path) -> list[tuple[str, str]]: """Per-file datas for mSCP: omit ``build/`` (generate_guidance output) and ``.git``.""" prefix = Path("applepy/data/macos_security") out: list[tuple[str, str]] = [] for p in mscp.rglob("*"): if not p.is_file(): continue try: rel = p.relative_to(mscp) except ValueError: continue parts = rel.parts if not parts: continue if parts[0] == "build" or parts[0] == ".git": continue if ".git" in parts: continue dest_dir = str(prefix / rel.parent) out.append((str(p), dest_dir)) return out def _applepy_datas_excluding_mscp_tree() -> list[tuple[str, str]]: """Package data from ``collect_data_files`` minus ``macos_security`` (re-added below, with filters).""" return [(s, d) for s, d in collect_data_files("applepy") if "macos_security" not in Path(s).parts] def _optional_mscp_datas() -> list[tuple[str, str]]: mscp = _SPEC_DIR / "applepy" / "data" / "macos_security" if not (mscp / "scripts" / "generate_guidance.py").is_file(): return [] return _mscp_data_files_excluding_generated(mscp) # Lynis and other ``applepy/data`` trees stay on the collect_data_files side; mSCP is merged once here # without ``.git`` or ``build/`` (avoids bloated bundles and root-owned build dirs after sudo runs). datas = _applepy_datas_excluding_mscp_tree() + _optional_mscp_datas() a = Analysis( ["applepy/__main__.py"], pathex=[], binaries=[], datas=datas, hiddenimports=[ "openpyxl", "yaml", "_yaml", "xlwt", # mSCP generate_guidance.py (run via runpy in frozen binary): explicit stdlib names as well as the hook module. "uuid", "zipfile", "applepy.pyi_mscp_stdlib", "applepy.check_progress", "applepy.checks", "applepy.checks.mdm.jamf", "applepy.checks.mdm.kandji", "applepy.catalog_cache", "applepy.mscp_audit_parse", "applepy.checks.fs_posture", "applepy.checks.plan_extras", "applepy.checks.privesc", "applepy.checks.pyobjc_surface", "applepy.checks.listening_ports", "plistlib", "sqlite3", "objc", "Foundation", "AppKit", ], hookspath=[], hooksconfig={}, runtime_hooks=[], excludes=[], noarchive=False, ) pyz = PYZ(a.pure) exe = EXE( pyz, a.scripts, a.binaries, a.datas, [], exclude_binaries=False, name="applepy", debug=False, bootloader_ignore_signals=False, strip=False, upx=False, console=True, disable_windowed_traceback=False, argv_emulation=False, target_arch=None, codesign_identity=None, entitlements_file=None, )