211 lines
6.8 KiB
Python
211 lines
6.8 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"
|
|
|
|
# 버전 정보 로드
|
|
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,
|
|
)
|