# -*- mode: python ; coding: utf-8 -*- """PyInstaller Spec-Datei für den Tätigkeitsnachweis-Generator. Baut eine standalone .exe aus run.py, die Streamlit, openpyxl und PyMuPDF (fitz) enthält. Verwendung: pyinstaller build.spec """ import os import sys from pathlib import Path # Pfade relativ zum Speicherort dieser Spec-Datei # PyInstaller stellt SPECPATH bereit (__file__ existiert in exec()-Kontext nicht) _SPEC_DIR = Path(SPECPATH).resolve() _PROJECT_ROOT = _SPEC_DIR.parent _APP_DIR = _PROJECT_ROOT / "0_application" # ── Streamlit & PyMuPDF Hidden Imports ────────────────────────── # Streamlit hat viele dynamische Imports; collect_all holt alles. # PyMuPDF (fitz) braucht die native DLL. hiddenimports = [ # Streamlit-Komponenten "streamlit", "streamlit.web.bootstrap", "streamlit.runtime", "streamlit.runtime.scriptrunner", "streamlit.runtime.state", "streamlit.runtime.caching", "streamlit.watcher", "streamlit.watcher.local_sources_watcher", "streamlit.watcher.poll_path_watcher", "streamlit.watcher.event_based_path_watcher", "streamlit.elements", "streamlit.elements.arrow", "streamlit.elements.pyplot", "streamlit.elements.altair", "streamlit.elements.plotly", "streamlit.proto", # openpyxl "openpyxl", "openpyxl.cell", "openpyxl.styles", # PyMuPDF "fitz", # Standard-Libs die manchmal übersehen werden "tornado", "watchdog", "pandas", "numpy", "PIL", "altair", "pyarrow", "git", "blinker", "rich", "pydeck", ] # ── Binaries (PyMuPDF DLL) ────────────────────────────────────── binaries = [] # Finde die PyMuPDF-DLL try: import fitz fitz_dir = Path(fitz.__file__).parent for f in fitz_dir.iterdir(): if f.suffix.lower() in (".dll", ".pyd", ".so", ".dylib"): binaries.append((str(f), "fitz")) except ImportError: pass # ── Data Files ────────────────────────────────────────────────── datas = [ (str(_APP_DIR / "app.py"), "."), (str(_APP_DIR / "src"), "src"), (str(_APP_DIR / "config"), "config"), ] # ── Alle Streamlit-Abhängigkeiten & Metadaten VOR Analysis() sammeln ─ from PyInstaller.utils.hooks import collect_all, copy_metadata streamlit_datas, streamlit_binaries, streamlit_hidden = collect_all("streamlit") datas.extend(streamlit_datas) binaries.extend(streamlit_binaries) hiddenimports.extend(streamlit_hidden) # Package-Metadaten: streamlit & Co. nutzen importlib.metadata.version(), # das die METADATA-Dateien aus dem dist-info-Ordner benötigt. for pkg in ["streamlit", "pyarrow", "numpy", "pandas", "altair", "openpyxl"]: try: datas += copy_metadata(pkg) except Exception: pass # Deduplizieren (Datas und Hiddenimports) hiddenimports = list(set(hiddenimports)) seen = set() _unique_datas = [] for src, dst in datas: key = (src, dst) if key not in seen: seen.add(key) _unique_datas.append((src, dst)) datas = _unique_datas # ── Analyse ───────────────────────────────────────────────────── a = Analysis( [str(_SPEC_DIR / "run.py")], pathex=[str(_APP_DIR)], binaries=binaries, datas=datas, hiddenimports=hiddenimports, hookspath=[], hooksconfig={}, runtime_hooks=[], excludes=[ "tkinter", "matplotlib", "scipy", "IPython", "jupyter", "notebook", "sqlalchemy", ], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=None, noarchive=False, ) # ── PyZ ───────────────────────────────────────────────────────── pyz = PYZ(a.pure, a.zipped_data, cipher=None) # ── EXE ───────────────────────────────────────────────────────── exe = EXE( pyz, a.scripts, a.binaries, a.zipfiles, a.datas, [], name="Tätigkeitsnachweis-Generator", debug=False, bootloader_ignore_signals=False, strip=False, upx=True, upx_exclude=[], runtime_tmpdir=None, console=True, # True = Konsolenfenster sichtbar (zeigt Streamlit-Logs) disable_windowed_traceback=False, argv_emulation=False, target_arch=None, codesign_identity=None, entitlements_file=None, )