IMG_Worker/setup.py

205 lines
6.7 KiB
Python

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"
APP_VERSION = "1.0.0"
# 기본 포함 패키지 목록(동적 로딩되는 많은 모듈을 고려하여 넉넉히 포함)
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,
)