Ctrl K
ComunidadGuías…

medikeep.spec

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# -*- mode: python ; coding: utf-8 -*-
"""
PyInstaller spec file for MediKeep Windows EXE.

This spec file builds a standalone Windows executable with:
- FastAPI backend bundled
- SQLite database support
- PDF text extraction (Poppler + Tesseract)
- All dependencies included
- Frontend static files

Build command:
    pyinstaller medikeep.spec

Output:
    dist/MediKeep.exe - Standalone executable
    dist/_internal/    - Required libraries and data files
"""

import os
import sys
from pathlib import Path

# Get the project root directory
project_root = Path(SPECPATH).resolve()

# Define data files to include
datas = []

# Add the app package as data files (CRITICAL: Must be included for imports to work)
if (project_root / 'app').exists():
    datas.append(('app', 'app'))
    print("[OK] Including app package")

# Add system tray icon for the packaged EXE
tray_icon = project_root / 'frontend' / 'public' / 'icon-64.png'
if tray_icon.exists():
    datas.append(('frontend/public/icon-64.png', 'frontend/build'))
    print("[OK] Including tray icon (icon-64.png)")

# Add frontend build if it exists (Create React App builds to 'build' directory)
if (project_root / 'frontend' / 'build').exists():
    datas.append(('frontend/build', 'frontend/build'))
    print("[OK] Including frontend build")
else:
    print("[SKIP] Frontend not built - run 'npm run build' in frontend/ to include")

# Add Alembic migrations if they exist
if (project_root / 'alembic').exists():
    datas.append(('alembic', 'alembic'))
    print("[OK] Including Alembic migrations")

# Add .env.example if it exists
if (project_root / '.env.example').exists():
    datas.append(('.env.example', '.'))
    print("[OK] Including .env.example")

# Define hidden imports (modules not automatically detected)
hiddenimports = [
    # MediKeep app modules (CRITICAL: Must be explicitly imported)
    'app.main',
    'app.core.config',
    'app.core.database',
    'app.core.logging_config',
    'app.core.windows_config',
    'app.core.external_binaries',
    'app.core.system_tray',

    # Alembic (for database migrations)
    'alembic',
    'alembic.command',
    'alembic.config',
    'alembic.script',
    'alembic.runtime',
    'alembic.runtime.migration',
    'alembic.runtime.environment',

    # FastAPI and dependencies
    'uvicorn.logging',
    'uvicorn.loops',
    'uvicorn.loops.auto',
    'uvicorn.protocols',
    'uvicorn.protocols.http',
    'uvicorn.protocols.http.auto',
    'uvicorn.protocols.websockets',
    'uvicorn.protocols.websockets.auto',
    'uvicorn.lifespan',
    'uvicorn.lifespan.on',

    # SQLAlchemy and database
    'sqlalchemy.ext.baked',
    'sqlalchemy.sql.default_comparator',

    # Pydantic
    'pydantic.deprecated.decorator',

    # PDF processing
    'pdfplumber',
    'pdf2image',
    'pytesseract',
    'PIL._imagingtk',
    'PIL._tkinter_finder',

    # System tray
    'pystray',
    'pystray._win32',

    # Other critical imports
    'python_dotenv',
]

# Binary files to exclude (reduce size)
excludes = [
    # Exclude development/testing tools
    'pytest',
    'coverage',
    'black',
    'pylint',
    'mypy',

    # Exclude unused modules
    'tkinter',
    'matplotlib',
    'IPython',
    'jupyter',

    # Exclude server-only databases
    'psycopg2',  # PostgreSQL - not needed in Windows EXE (uses SQLite)
    'asyncpg',
]

# Analysis: Collect all Python files and dependencies
a = Analysis(
    ['run.py'],  # Entry point
    pathex=[str(project_root)],
    binaries=[],
    datas=datas,
    hiddenimports=hiddenimports,
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=excludes,
    win_no_prefer_redirects=False,
    win_private_assemblies=False,
    cipher=None,
    noarchive=False,
)

# PYZ: Create Python archive
pyz = PYZ(
    a.pure,
    a.zipped_data,
    cipher=None,
)

# EXE: Create executable
exe = EXE(
    pyz,
    a.scripts,
    [],
    exclude_binaries=True,  # Use COLLECT for multi-file distribution
    name='MediKeep',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,  # Compress with UPX
    console=False,  # Windowed mode - runs in background with system tray
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
    icon='medikeep.ico',
    version_file=None,  # TODO: Add version info when available
)

# Get version directly from config.py file without importing
# This avoids circular import issues during build
import re
config_file = project_root / 'app' / 'core' / 'config.py'
with open(config_file, 'r') as f:
    config_content = f.read()
    version_match = re.search(r'VERSION:\s*str\s*=\s*["\']([^"\']+)["\']', config_content)
    if version_match:
        APP_VERSION = version_match.group(1)
    else:
        APP_VERSION = "0.0.0"  # Fallback
        print("WARNING: Could not extract version from config.py")

print(f"Building MediKeep version {APP_VERSION}")

# COLLECT: Gather all files into distribution folder with version in folder name
coll = COLLECT(
    exe,
    a.binaries,
    a.zipfiles,
    a.datas,
    strip=False,
    upx=True,
    upx_exclude=[],
    name=f'MediKeep-v{APP_VERSION}',
)

# Post-build notes:
# After building, you need to manually add to dist/MediKeep/:
#
# 1. External binaries (PDF text extraction):
#    - poppler/bin/        (pdftotext.exe, pdfimages.exe, etc.)
#    - tesseract/          (tesseract.exe)
#    - tesseract/tessdata/ (eng.traineddata, osd.traineddata)
#
# 2. Frontend (if not already included):
#    - frontend/dist/      (React build output)
#
# Directory structure should be:
# dist/
#   MediKeep/
#     MediKeep.exe
#     _internal/
#       (Python libraries, dependencies)
#       poppler/
#         bin/
#           pdftotext.exe
#           pdfimages.exe
#           pdfinfo.exe
#       tesseract/
#         tesseract.exe
#         tessdata/
#           eng.traineddata
#           osd.traineddata
#       frontend/
#         dist/
#           (React build files)