from cx_Freeze import setup, Executable from cx_Freeze.command.build_exe import build_exe as _build_exe import os import sys # 프로젝트 루트 ROOT_DIR = os.path.abspath(os.path.dirname(__file__)) # 앱 메타 APP_NAME = "ImgWorker" # 버전 정보 로드 about = {} with open(os.path.join(ROOT_DIR, "updater", "__version__.py"), "r", encoding="utf-8") as f: exec(f.read(), about) APP_VERSION = about["__version__"] # 기본 포함 패키지 목록(동적 로딩되는 많은 모듈을 고려하여 넉넉히 포함) INCLUDES = [ # 표준 라이브러리에서 동적 사용하는 것들 "asyncio", "multiprocessing", "logging", "json", "queue", "ctypes", "zipfile", "urllib", "socket", "signal", # 서드파티 "fastapi", "uvicorn", "pydantic", "dotenv", "psutil", "requests", "numpy", "PIL", "cv2", "onnx", "onnxruntime", # fastapi 런타임 의존 "h11", "anyio", "starlette", # 기타 사용 중인 것들 "albumentations", "imageio", "scipy", "networkx", "pystray", "PIL", # pystray 아이콘용 "modules.tray_app", # 동적 import 대응을 위해 강제 포함 'pyclipper', 'shapely', 'skimage', # package import name (not scikit-image) 'lmdb', ] EXCLUDES = [ # 사용하지 않는 대형 모듈 제외 예시(필요 시 조정) "tkinter", ] # 데이터 파일 매핑 DATA_FILES = [] # ProgramData 기준 런타임 파일은 실행 시 생성되지만, 모델/폰트/onnx 등 리소스 포함 MODULES_DIR = os.path.join(ROOT_DIR, "modules") # 모델/리소스 디렉토리들(필수 데이터만 포함; 테스트/샘플 이미지/소스 코드는 제외) # RESOURCE_DIRS = [ # os.path.join(MODULES_DIR, "fonts"), # os.path.join(MODULES_DIR, "rembg_models"), # os.path.join(MODULES_DIR, "migan_onnx"), # # os.path.join(MODULES_DIR, "PP_Models"), # os.path.join(MODULES_DIR, "user_data"), # os.path.join(MODULES_DIR, "scripts"), # ] # for d in RESOURCE_DIRS: # if os.path.isdir(d): # # cx_Freeze의 include_files에 (src, dst상대경로)로 추가 # rel = os.path.relpath(d, ROOT_DIR) # DATA_FILES.append((d, rel)) # ONNX OCR: 전체 모듈 포함(src/tools/ppocr 등 포함) ONNX_MODULE_DIR = os.path.join(MODULES_DIR, "onnx_ocr_module") if os.path.isdir(ONNX_MODULE_DIR): # onnx_ocr_module 전체 트리를 lib/modules/onnx_ocr_module로 포함 DATA_FILES.append((ONNX_MODULE_DIR, os.path.join("lib", "modules", "onnx_ocr_module"))) class BuildWithBytecode(_build_exe): def run(self): # 0) 빌드 시작 전 기존 빌드 폴더 정리 try: import shutil build_root = os.path.join(ROOT_DIR, "build") if os.path.isdir(build_root): shutil.rmtree(build_root, ignore_errors=True) if os.path.isdir(self.build_exe): shutil.rmtree(self.build_exe, ignore_errors=True) except Exception: pass # 1) 본 빌드 수행 super().run() # onnx_ocr_module 전체를 바이트코드 컴파일(호환성 위해 .py는 유지) try: import compileall module_root = os.path.join(self.build_exe, "lib", "modules", "onnx_ocr_module") if os.path.isdir(module_root): compileall.compile_dir(module_root, force=True, optimize=2, quiet=1) except Exception: pass # 2) modules 아래 불필요 폴더/이미지 제거 (빌드 루트 및 lib/modules 모두) try: import shutil exclude_dirs = [ "test", "tests", "output", "outputs", "ocr_backend", "ocr_backends", "nssm", "img", "client", "debug_images", "old_modules", "PP_Models", "user_data" ] bases = [ os.path.join(self.build_exe, "modules"), os.path.join(self.build_exe, "lib", "modules"), ] for base in bases: if not os.path.isdir(base): continue # 폴더 제거 for name in exclude_dirs: p = os.path.join(base, name) if os.path.exists(p): try: shutil.rmtree(p, ignore_errors=True) except Exception: pass # 이미지 파일 제거(onnx_ocr_module은 제외) for root, dirs, files in os.walk(base): # onnx_ocr_module 경로는 건너뜀 (모델/데이터 보존) if os.path.normpath(os.path.join(base, "onnx_ocr_module")) in os.path.normpath(root): continue for name in files: ext = os.path.splitext(name)[1].lower() if ext in (".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"): try: os.remove(os.path.join(root, name)) except Exception: pass except Exception: pass # 추가 파일들: .env가 있으면 실행 파일과 같은 폴더에 포함 ENV_FILE = os.path.join(ROOT_DIR, ".env") if os.path.isfile(ENV_FILE): # exe가 위치한 루트에 배치 → main.py의 load_dotenv(ROOT_DIR/.env)와 일치 DATA_FILES.append((ENV_FILE, ".env")) # PowerShell 스크립트 포함 # 배포 편의를 위해 modules/scripts 경로 사용 SCRIPTS_DIR = os.path.join(MODULES_DIR, "scripts") if os.path.isdir(SCRIPTS_DIR): for name in ("install_service.ps1", "uninstall_service.ps1"): p = os.path.join(SCRIPTS_DIR, name) if os.path.isfile(p): DATA_FILES.append((p, os.path.join("modules", "scripts", name))) # 빌드 옵션 build_exe_options = { "packages": INCLUDES, "excludes": EXCLUDES, "include_files": DATA_FILES, "include_msvcr": True, # MSVCRT 포함(윈도우 배포 편의) # 패키지 압축 정책: 코드 패키지는 zip 포함, 대형 네이티브 패키지는 풀어서 포함 "zip_include_packages": [ "fastapi", "starlette", "pydantic", "anyio", "h11" ], "zip_exclude_packages": [ "numpy", "PIL", "cv2", "onnx", "onnxruntime", "scipy" ], } # 실행 파일 설정 executables = [ Executable( script=os.path.join(ROOT_DIR, "main.py"), base='Win32GUI', target_name=f"{APP_NAME}.exe", icon=None, ), Executable( script=os.path.join(ROOT_DIR, "modules", "tray_app.py"), base='Win32GUI', target_name=f"{APP_NAME}Tray.exe", icon=None, ) ] setup( name=APP_NAME, version=APP_VERSION, description="Image Worker API", options={"build_exe": build_exe_options}, cmdclass={"build_exe": BuildWithBytecode}, executables=executables, )