650 lines
24 KiB
Python
650 lines
24 KiB
Python
import sys
|
|
import os
|
|
import glob
|
|
import shutil
|
|
import subprocess
|
|
import logging
|
|
import time
|
|
from tqdm import tqdm
|
|
|
|
import requests
|
|
|
|
from cx_Freeze import setup, Executable
|
|
from setuptools import find_packages
|
|
from setuptools.command.build_ext import build_ext
|
|
from cx_Freeze.command.build_exe import build_exe as _build_exe
|
|
|
|
from setuptools import setup as cy_setup, Extension
|
|
from Cython.Build import cythonize
|
|
|
|
|
|
|
|
# 버전 및 메타데이터 가져오기
|
|
from updater.__version__ import (
|
|
__title__, __version__, __description__, __author__,
|
|
__author_email__, __license__, __install_requires__
|
|
)
|
|
|
|
# ============================================================================
|
|
# [Configuration] 0. Gokapi 서버 설정 (업로드를 위해 필수)
|
|
# ============================================================================
|
|
GOKAPI_URL = "https://go.wrmc.cc" # 사용자님의 Gokapi 주소로 변경하세요
|
|
GOKAPI_APIKEY = "Pncy6s7pyi61rtUsV3kUwfPtOvU93I " # Gokapi 관리자 페이지에서 발급받은 API 키
|
|
|
|
# ============================================================================
|
|
# [Configuration] 설정 및 상수 정의
|
|
# ============================================================================
|
|
|
|
# 1. Cython으로 컴파일할 모듈 목록 (최소화하여 테스트)
|
|
CYTHON_MODULES = [
|
|
# 최소한의 모듈만 테스트
|
|
"loggerModule",
|
|
# "modules/image_worker",
|
|
# "modules/tray_app",
|
|
]
|
|
|
|
# 2. 빌드 후 제거할 불필요한 파일/폴더 목록
|
|
CLEANUP_TARGETS = [
|
|
# 디렉토리
|
|
"lib/modules/ocr_backends", # OCR 백엔드들 (필요시 유지)
|
|
"lib/modules/old_modules", # 구 버전 모듈들
|
|
"lib/modules/test", # 테스트 파일들
|
|
"lib/modules/debug_images", # 디버그 이미지들
|
|
"lib/modules/img", # 샘플 이미지들
|
|
"lib/modules/output", # 출력 샘플들
|
|
"lib/modules/outputs", # 출력 샘플들
|
|
"lib/modules/user_data", # 사용자 데이터
|
|
"lib/modules/PP_Models", # Paddle 모델들 (이미 포함될 수 있음)
|
|
"lib/paddle", "lib/paddleocr", "lib/onnxruntime", "lib/skimage", "lib/scipy",
|
|
|
|
# 파일
|
|
"lib/modules/migan_traced.pt",
|
|
"lib/modules/*.pyc",
|
|
"lib/modules/*/*.pyc",
|
|
"lib/modules/*/*/*.pyc",
|
|
|
|
]
|
|
|
|
# 3. 소스 패키징에서 강제로 제외할 패키지들
|
|
BASE_EXCLUDES = [
|
|
'tkinter', 'PyQt4', 'PyQt5', 'AppKit', 'Foundation', 'IPython',
|
|
'OpenSSL', 'curses', 'test', 'matplotlib', 'asyncpg',
|
|
'importlib._bootstrap', 'importlib.machinery',
|
|
'pytest', 'hypothesis', 'mypy', 'coverage', 'tox',
|
|
'sympy', 'mpmath', 'gmpy2',
|
|
'paddle', 'paddleocr', 'paddlehub', 'onnxruntime', 'onnx',
|
|
'skimage', 'scikit-image', 'pyclipper', 'shapely',
|
|
'scipy', 'imgaug', 'albumentations', 'torch', 'tensorflow', 'keras',
|
|
"PySide6",
|
|
# 프로젝트 특정 제외
|
|
# 참고: CYTHON_MODULES에 포함된 모듈들은 get_cython_freeze_config()에서 자동으로 제외됨
|
|
]
|
|
|
|
# 4. 강제 포함 패키지
|
|
BASE_INCLUDES = [
|
|
# 정말 최소한의 모듈만 포함하여 테스트
|
|
'json', 'os', 'sys', 'time', 'threading',
|
|
]
|
|
|
|
|
|
# ============================================================================
|
|
# [Helper Functions] 로직 분리
|
|
# ============================================================================
|
|
|
|
def run_setup_clean():
|
|
"""
|
|
기존 빌드찌꺼기 제거
|
|
"""
|
|
print("\n>>> [Cleaner] 기존 빌드찌꺼기 제거 시작...")
|
|
# 자동으로 'y' 입력하여 확인 절차 생략
|
|
result = subprocess.run(["python", "-c", "import sys; sys.path.insert(0, 'tests'); from setup_clean import clean_files_oswalk; clean_files_oswalk()"])
|
|
if result.returncode == 0:
|
|
print(">>> [Cleaner] 기존 빌드찌꺼기 제거 완료!\n")
|
|
else:
|
|
print(">>> [Cleaner] 정리 중 오류 발생, 계속 진행합니다.\n")
|
|
|
|
|
|
def run_cython_build():
|
|
"""
|
|
setup_cython.py를 별도로 실행하지 않고,
|
|
여기서 직접 Cython 빌드를 수행합니다. (경로 꼬임 방지)
|
|
"""
|
|
print("\n>>> [Cython] 내부 빌드 프로세스 시작...")
|
|
|
|
extensions = []
|
|
|
|
# 전역변수 CYTHON_MODULES 리스트를 사용
|
|
for file_path in CYTHON_MODULES:
|
|
# 파일 확인 (.py가 없는 경우 붙여서 확인)
|
|
source_file = file_path
|
|
if not os.path.exists(source_file):
|
|
if os.path.exists(source_file + ".py"):
|
|
source_file += ".py"
|
|
else:
|
|
print(f" [Warning] 컴파일 대상 파일을 찾을 수 없음: {file_path}")
|
|
continue
|
|
|
|
# 모듈 이름 생성 (경로 -> 점 표기법)
|
|
# 예: modules/image_worker -> modules.image_worker
|
|
no_ext = os.path.splitext(source_file)[0]
|
|
module_name = no_ext.replace("/", ".").replace("\\", ".")
|
|
|
|
# Extension 객체 생성
|
|
ext = Extension(
|
|
name=module_name,
|
|
sources=[source_file]
|
|
)
|
|
extensions.append(ext)
|
|
|
|
if not extensions:
|
|
print(">>> [Cython] 컴파일할 대상이 없습니다.")
|
|
return
|
|
|
|
# setuptools의 setup을 직접 호출 (인자값으로 build_ext --inplace 전달)
|
|
try:
|
|
cy_setup(
|
|
name="CythonInternalBuild",
|
|
ext_modules=cythonize(
|
|
extensions,
|
|
compiler_directives={'language_level': "3"},
|
|
quiet=True
|
|
),
|
|
script_args=['build_ext', '--inplace']
|
|
)
|
|
print(">>> [Cython] 빌드 성공!\n")
|
|
except Exception as e:
|
|
print(f"\n>>> [Cython] 빌드 중 오류 발생: {e}")
|
|
sys.exit(1)
|
|
|
|
|
|
def get_cython_freeze_config(modules_list):
|
|
"""
|
|
Cython 모듈 리스트를 기반으로 cx_Freeze용 설정을 생성합니다.
|
|
Returns:
|
|
(excludes_list, include_files_list, include_packages_to_remove)
|
|
"""
|
|
excludes = [] # .py 소스 제외용
|
|
include_files = [] # .pyd 파일 포함용
|
|
module_names = set()
|
|
|
|
for module_path in modules_list:
|
|
# 모듈 이름 변환 (modules/image_worker -> modules.image_worker)
|
|
clean_path = os.path.splitext(module_path)[0]
|
|
module_name = clean_path.replace("/", ".").replace("\\", ".")
|
|
module_names.add(module_name)
|
|
|
|
excludes.append(module_name)
|
|
|
|
# .pyd 파일 찾기 및 매핑
|
|
dir_name = os.path.dirname(module_path)
|
|
base_name = os.path.basename(module_path)
|
|
pyd_pattern = os.path.join(dir_name, f"{base_name}*.pyd")
|
|
found_pyds = glob.glob(pyd_pattern)
|
|
|
|
if found_pyds:
|
|
src_pyd = found_pyds[0]
|
|
# 라이브러리 구조 유지: lib/modules/...
|
|
dest_pyd = os.path.join("lib", dir_name, f"{base_name}.pyd")
|
|
include_files.append((src_pyd, dest_pyd))
|
|
print(f" [Protect] {module_name}: .py 제외, .pyd 포함")
|
|
else:
|
|
print(f" [Warning] .pyd 없음: {module_path}. (원본 .py가 포함될 수 있음)")
|
|
|
|
return excludes, include_files, module_names
|
|
|
|
|
|
def collect_include_files():
|
|
"""DLL, 리소스 파일 등 기타 include_files 리스트를 생성합니다."""
|
|
# 디버그를 위해 최소한의 파일만 포함
|
|
files = []
|
|
|
|
# VC Runtime Files만 포함 (최소한으로)
|
|
vc_runtimes = [
|
|
('C:/Windows/System32/vcruntime140.dll', 'vcruntime140.dll'),
|
|
('C:/Windows/System32/msvcp140.dll', 'msvcp140.dll'),
|
|
]
|
|
|
|
# VC 런타임 파일 존재 여부 확인 후 추가
|
|
for src, dest in vc_runtimes:
|
|
if os.path.exists(src):
|
|
files.append((src, dest))
|
|
|
|
print(f"DEBUG: Including {len(files)} files")
|
|
return files
|
|
|
|
|
|
# ============================================================================
|
|
# [Main Logic] 실행 로직 (빌드 시에만 실행)
|
|
# ============================================================================
|
|
|
|
def prepare_build_options():
|
|
"""빌드 옵션을 준비하는 함수"""
|
|
global final_includes, final_excludes, final_include_files
|
|
|
|
# 0. 기존 빌드찌꺼기 제거
|
|
run_setup_clean()
|
|
|
|
# 1. Cython 빌드 실행
|
|
run_cython_build()
|
|
|
|
# 2. cx_Freeze 설정 준비
|
|
cy_excludes, cy_include_files, cy_module_names = get_cython_freeze_config(CYTHON_MODULES)
|
|
resource_include_files = collect_include_files()
|
|
|
|
# 3. 최종 옵션 조합
|
|
final_includes = [mod for mod in BASE_INCLUDES if mod not in cy_module_names] # 충돌 방지
|
|
final_excludes = BASE_EXCLUDES + cy_excludes
|
|
final_include_files = resource_include_files + cy_include_files
|
|
|
|
# 디버그: 문제가 되는 모듈들을 임시로 제외
|
|
if EXCLUDE_PROBLEMATIC_MODULES and DEBUG_MODE:
|
|
print("=== EXCLUDING PROBLEMATIC MODULES FOR DEBUG ===")
|
|
|
|
# 문제가 될 수 있는 모듈들 임시 제외
|
|
problematic_modules = [
|
|
'onnx_ocr_module', 'onnx_ocr_wrapper', 'onnx_ocr_module.src.onnx_ocr_wrapper',
|
|
'modules.onnx_ocr_module', 'modules.onnx_ocr_module.src.onnx_ocr_wrapper',
|
|
'paddle', 'paddleocr', 'onnxruntime', 'onnx'
|
|
]
|
|
|
|
original_count = len(final_includes)
|
|
final_includes = [mod for mod in final_includes if not any(pm in mod for pm in problematic_modules)]
|
|
|
|
# include_files에서도 제외
|
|
final_include_files = [f for f in final_include_files if not any(pm in str(f) for pm in problematic_modules)]
|
|
|
|
print(f"Excluded problematic modules. Includes: {original_count} -> {len(final_includes)}")
|
|
print(f"Excluded problematic files. Include files: {len(resource_include_files + cy_include_files)} -> {len(final_include_files)}")
|
|
print("=" * 50)
|
|
|
|
# 디버그 모드 설정
|
|
DEBUG_MODE = True
|
|
|
|
# 문제가 되는 큰 모듈들을 임시로 제외하여 테스트
|
|
EXCLUDE_PROBLEMATIC_MODULES = True
|
|
|
|
# 0. 기존 빌드찌꺼기 제거
|
|
run_setup_clean()
|
|
|
|
# 1. Cython 빌드 실행
|
|
run_cython_build()
|
|
|
|
# 2. cx_Freeze 설정 준비
|
|
cy_excludes, cy_include_files, cy_module_names = get_cython_freeze_config(CYTHON_MODULES)
|
|
resource_include_files = collect_include_files()
|
|
|
|
# 3. 최종 옵션 조합
|
|
final_includes = [mod for mod in BASE_INCLUDES if mod not in cy_module_names] # 충돌 방지
|
|
final_excludes = BASE_EXCLUDES + cy_excludes
|
|
final_include_files = resource_include_files + cy_include_files
|
|
|
|
# 디버그: 문제가 되는 모듈들을 임시로 제외
|
|
if EXCLUDE_PROBLEMATIC_MODULES and DEBUG_MODE:
|
|
print("=== EXCLUDING PROBLEMATIC MODULES FOR DEBUG ===")
|
|
|
|
# 문제가 될 수 있는 모듈들 임시 제외
|
|
problematic_modules = [
|
|
'onnx_ocr_module', 'onnx_ocr_wrapper', 'onnx_ocr_module.src.onnx_ocr_wrapper',
|
|
'modules.onnx_ocr_module', 'modules.onnx_ocr_module.src.onnx_ocr_wrapper',
|
|
'paddle', 'paddleocr', 'onnxruntime', 'onnx'
|
|
]
|
|
|
|
original_count = len(final_includes)
|
|
final_includes = [mod for mod in final_includes if not any(pm in mod for pm in problematic_modules)]
|
|
|
|
# include_files에서도 제외
|
|
final_include_files = [f for f in final_include_files if not any(pm in str(f) for pm in problematic_modules)]
|
|
|
|
print(f"Excluded problematic modules. Includes: {original_count} -> {len(final_includes)}")
|
|
print(f"Excluded problematic files. Include files: {len(resource_include_files + cy_include_files)} -> {len(final_include_files)}")
|
|
print("=" * 50)
|
|
|
|
build_options = {
|
|
'packages': [
|
|
'ctypes', 'asyncio', 'subprocess', 'pyperclip', 'numpy',
|
|
'requests', 'PIL', 'bs4', 'psutil',
|
|
'openai', 'httpx', 'pydantic', # fastapi, uvicorn 제외 (설치 안됨)
|
|
'pandas', 'supabase', 'translatepy', 'markdown',
|
|
'json', 'json.encoder', 'json.decoder', 'json.scanner',
|
|
'dotenv', 'pathlib', 'logging', 'threading', 'multiprocessing',
|
|
# 'cv2', # OpenCV 제외 (DLL 문제)
|
|
],
|
|
'includes': final_includes,
|
|
'excludes': final_excludes,
|
|
'include_files': final_include_files,
|
|
'zip_include_packages': [],
|
|
'optimize': 0,
|
|
'silent': False if DEBUG_MODE else True, # 디버그 모드에서는 silent 해제
|
|
'include_msvcr': True,
|
|
}
|
|
|
|
# 디버그 모드 추가 설정
|
|
if DEBUG_MODE:
|
|
print("=== DEBUG MODE ENABLED ===")
|
|
print(f"Final includes count: {len(final_includes)}")
|
|
print(f"Final excludes count: {len(final_excludes)}")
|
|
print(f"Final include_files count: {len(final_include_files)}")
|
|
|
|
# 문제가 될 수 있는 큰 모듈들을 임시로 제외해서 테스트
|
|
print("Checking for problematic modules...")
|
|
|
|
# 큰 모듈들을 확인
|
|
large_modules = [m for m in final_includes if any(x in m for x in ['onnx', 'paddle', 'torch', 'tensorflow'])]
|
|
if large_modules:
|
|
print(f"Large modules detected: {large_modules}")
|
|
|
|
# 포함 파일들 확인
|
|
print("Include files:")
|
|
for f in final_include_files[:10]: # 처음 10개만
|
|
print(f" {f}")
|
|
if len(final_include_files) > 10:
|
|
print(f" ... and {len(final_include_files) - 10} more")
|
|
print("=" * 50)
|
|
|
|
|
|
# ============================================================================
|
|
# [Custom Build Class] 빌드 프로세스 커스터마이징
|
|
# ============================================================================
|
|
|
|
class CustomBuildExe(_build_exe):
|
|
def run(self):
|
|
# 전체 진행률: 100
|
|
# 초기값 10: Cython 빌드는 이 클래스 실행 전(if __name__...)에 이미 완료됨
|
|
print("\n" + "="*60)
|
|
print(" ImgWorker 통합 빌드 시스템")
|
|
print("="*60 + "\n")
|
|
try:
|
|
# 총 6단계로 구성 (Cython은 이미 완료됨 -> 10%)
|
|
with tqdm(total=100, initial=10, unit="pct",
|
|
bar_format="{l_bar}{bar}| {n_fmt}/{total_fmt}% [{elapsed}]",
|
|
ncols=80, colour='green') as pbar:
|
|
|
|
# [Step 2] cx_Freeze 빌드 (10 -> 30%)
|
|
pbar.set_description("Step 2/6: cx_Freeze Packaging")
|
|
print(f"DEBUG: Starting cx_Freeze build with {len(build_options.get('includes', []))} includes")
|
|
print(f"DEBUG: {len(build_options.get('excludes', []))} excludes")
|
|
print(f"DEBUG: {len(build_options.get('include_files', []))} include_files")
|
|
print(f"DEBUG: {len(build_options.get('packages', []))} packages")
|
|
|
|
# cx_Freeze 실행 전 타임스탬프
|
|
import time
|
|
start_time = time.time()
|
|
print(f"DEBUG: Build start time: {start_time}")
|
|
|
|
_build_exe.run(self)
|
|
|
|
end_time = time.time()
|
|
print(f"DEBUG: Build end time: {end_time}")
|
|
print(f"DEBUG: Build duration: {end_time - start_time:.2f} seconds")
|
|
pbar.update(20)
|
|
|
|
|
|
# [Step 3] 빌드 결과물 정리 (30 -> 35%)
|
|
pbar.set_description("Step 3/6: Cleaning Build Output")
|
|
self._cleanup_build_output()
|
|
pbar.update(5)
|
|
|
|
# [Step 4] Inno Setup 컴파일 (35 -> 70%)
|
|
pbar.set_description("Step 4/6: Inno Setup Compiling")
|
|
self._run_inno_setup() # .iss 생성
|
|
self._compile_inno_installer() # .exe 생성
|
|
pbar.update(35)
|
|
|
|
|
|
# [Step 5] 파일명 변경 (버전 정보 추가) (70 -> 75%)
|
|
pbar.set_description("Step 5/6: Renaming Installer")
|
|
final_installer_path = self._rename_installer_with_version()
|
|
pbar.update(5)
|
|
|
|
# [Step 6] Gokapi 업로드 (75 -> 95%)
|
|
if final_installer_path and GOKAPI_URL != "https://gokapi.your-domain.com":
|
|
pbar.set_description("Step 6/6: Uploading to Gokapi")
|
|
self._upload_to_gokapi(final_installer_path)
|
|
else:
|
|
print("\n[Skip] Gokapi 설정이 없거나 파일이 없어 업로드를 건너뜁니다.")
|
|
pbar.update(20)
|
|
|
|
# [Final] 소스 정리 (95 -> 100%)
|
|
pbar.set_description("Finalizing: Cleaning Source")
|
|
self._cleanup_cython_artifacts()
|
|
pbar.update(5)
|
|
|
|
pbar.set_description("Build Complete!")
|
|
|
|
except KeyboardInterrupt:
|
|
print("\n\n>>> 빌드가 취소되었습니다.")
|
|
sys.exit(1)
|
|
except Exception as e:
|
|
print(f"\n\n>>> 빌드 중 치명적인 오류 발생: {e}")
|
|
# 에러 상세 내용을 보기 위해 traceback 출력
|
|
import traceback
|
|
traceback.print_exc()
|
|
sys.exit(1)
|
|
|
|
def _get_dir_size(self, path):
|
|
total = 0
|
|
for dirpath, _, filenames in os.walk(path):
|
|
for f in filenames:
|
|
total += os.path.getsize(os.path.join(dirpath, f))
|
|
return total
|
|
|
|
def _rename_installer_with_version(self):
|
|
"""생성된 설치 파일 이름에 버전을 추가합니다."""
|
|
# Inno Setup의 기본 출력 경로 (dist/installer)
|
|
dist_dir = os.path.join(os.path.dirname(__file__), "dist", "installer")
|
|
|
|
# 원래 생성되는 파일명 (generate_iss.py 설정에 따름, 보통 Setup.exe)
|
|
# 로그에 찍힌 이름을 기준으로 찾습니다.
|
|
original_name = "ImgWorker Setup.exe"
|
|
original_path = os.path.join(dist_dir, original_name)
|
|
|
|
if not os.path.exists(original_path):
|
|
print(f"\n[Error] 원본 설치 파일을 찾을 수 없습니다: {original_path}")
|
|
# 혹시 다른 이름일 수도 있으니 exe 파일을 검색해봅니다.
|
|
exe_files = glob.glob(os.path.join(dist_dir, "*.exe"))
|
|
if exe_files:
|
|
original_path = max(exe_files, key=os.path.getctime) # 가장 최신 파일
|
|
else:
|
|
return None
|
|
|
|
# 새 파일명: ImgWorker Setup_V3.12.15.exe
|
|
new_name = f"ImgWorker Setup_V{__version__}.exe"
|
|
new_path = os.path.join(dist_dir, new_name)
|
|
|
|
try:
|
|
# 기존에 같은 버전 파일이 있으면 삭제
|
|
if os.path.exists(new_path):
|
|
os.remove(new_path)
|
|
|
|
os.rename(original_path, new_path)
|
|
print(f"\n[Rename] 파일명이 변경되었습니다:\n -> {new_name}")
|
|
return new_path
|
|
except Exception as e:
|
|
print(f"\n[Error] 파일명 변경 실패: {e}")
|
|
return original_path
|
|
|
|
def _upload_to_gokapi(self, file_path):
|
|
"""Gokapi 서버로 파일을 업로드합니다."""
|
|
print("\n[Upload] Gokapi 서버로 업로드를 시작합니다...")
|
|
|
|
if not os.path.exists(file_path):
|
|
print(" [Error] 업로드할 파일이 없습니다.")
|
|
return
|
|
|
|
try:
|
|
url = f"{GOKAPI_URL}/api/v1/upload"
|
|
headers = {
|
|
"Authorization": f"Bearer {GOKAPI_APIKEY}"
|
|
}
|
|
|
|
|
|
with open(file_path, "rb") as f:
|
|
# 멀티파트 업로드
|
|
files = {"file": (os.path.basename(file_path), f)}
|
|
data = {
|
|
"expire": 0 # ✅ 영구 저장
|
|
} # 만료일 설정 (예: 0=무제한, 7d=7일) - 필요시 data={'expiry': '7d'} 추가
|
|
|
|
response = requests.post(
|
|
url,
|
|
headers=headers,
|
|
files=files,
|
|
data=data,
|
|
timeout=30
|
|
)
|
|
|
|
if response.status_code in (200, 201):
|
|
data = response.json()
|
|
|
|
# Gokapi 응답 구조
|
|
hotlink = data.get("DownloadUrl") or data.get("File", {}).get("Url")
|
|
|
|
print("\n" + "#" * 50)
|
|
print("업로드 성공!")
|
|
print(f"다운로드 링크: {hotlink}")
|
|
print("#" * 50 + "\n")
|
|
else:
|
|
print(f"\n[Error] 업로드 실패 (Status: {response.status_code})")
|
|
print("Server Response:", response.text)
|
|
|
|
except Exception as e:
|
|
print(f"\n[Error] 업로드 중 예외 발생: {e}")
|
|
|
|
def _cleanup_build_output(self):
|
|
"""빌드된 exe 폴더 내부의 불필요한 파일 삭제"""
|
|
print("\n[Cleaner] 빌드 결과물 정리 중...")
|
|
build_dir = os.path.join(os.path.dirname(__file__), "build")
|
|
|
|
if not os.path.exists(build_dir): return
|
|
|
|
removed_count = 0
|
|
total_saved = 0
|
|
|
|
for item in os.listdir(build_dir):
|
|
if not item.startswith("exe."): continue
|
|
|
|
exe_dir = os.path.join(build_dir, item)
|
|
for unnecessary in CLEANUP_TARGETS:
|
|
target_path = os.path.join(exe_dir, unnecessary)
|
|
try:
|
|
if os.path.isdir(target_path):
|
|
size = self._get_dir_size(target_path)
|
|
shutil.rmtree(target_path)
|
|
total_saved += size
|
|
print(f" ✓ 디렉토리 삭제: {unnecessary}")
|
|
elif os.path.isfile(target_path):
|
|
size = os.path.getsize(target_path)
|
|
os.remove(target_path)
|
|
total_saved += size
|
|
print(f" ✓ 파일 삭제: {unnecessary}")
|
|
removed_count += 1
|
|
except Exception:
|
|
pass # 없는 파일은 무시
|
|
|
|
print(f"[Cleaner] 정리 완료: {total_saved / (1024*1024):.1f} MB 절약\n")
|
|
|
|
def _cleanup_cython_artifacts(self):
|
|
"""소스 폴더의 .pyd, .c 임시 파일 삭제"""
|
|
print("[Cleaner] 소스 폴더 Cython 임시 파일 정리 중...")
|
|
deleted = 0
|
|
for module_path in CYTHON_MODULES:
|
|
dir_name = os.path.dirname(module_path)
|
|
base_name = os.path.basename(module_path)
|
|
patterns = [
|
|
os.path.join(dir_name, f"{base_name}*.pyd"),
|
|
os.path.join(dir_name, f"{base_name}.c")
|
|
]
|
|
for pattern in patterns:
|
|
for f in glob.glob(pattern):
|
|
try:
|
|
os.remove(f)
|
|
deleted += 1
|
|
except Exception: pass
|
|
print(f"[Cleaner] 소스 정리 완료: {deleted}개 파일 삭제\n")
|
|
|
|
def _run_inno_setup(self):
|
|
"""Inno Setup 스크립트 생성기 실행 (현재 프로젝트에는 없으므로 생략)"""
|
|
print("[Inno Setup] 설치 스크립트 생성 생략 (generate_iss.py 없음)...")
|
|
|
|
def _compile_inno_installer(self):
|
|
"""Inno Setup 컴파일 생략 (현재 프로젝트에는 설치 스크립트 없음)"""
|
|
print("[Inno Setup] 설치 파일 컴파일 생략...")
|
|
|
|
|
|
# ============================================================================
|
|
# [Setup] 최종 실행
|
|
# ============================================================================
|
|
|
|
# 디버그용 간단한 테스트 함수
|
|
def test_build():
|
|
"""빌드 전 문제점을 미리 테스트"""
|
|
print("=== BUILD TEST ===")
|
|
|
|
# 1. 모듈 import 테스트
|
|
print("Testing module imports...")
|
|
test_modules = ['fastapi', 'uvicorn', 'pydantic', 'cv2', 'numpy', 'PIL']
|
|
for mod in test_modules:
|
|
try:
|
|
__import__(mod)
|
|
print(f" OK {mod}")
|
|
except ImportError as e:
|
|
print(f" FAIL {mod}: {e}")
|
|
|
|
# 2. 큰 파일들 확인
|
|
print("\nChecking large files...")
|
|
large_files = []
|
|
for root, dirs, dirs[:] in os.walk('.'):
|
|
for file in dirs:
|
|
if file in ['onnx_ocr_module', 'PP_Models', 'rembg_models', 'migan_onnx']:
|
|
path = os.path.join(root, file)
|
|
try:
|
|
size = sum(os.path.getsize(os.path.join(dirpath, f))
|
|
for dirpath, _, files in os.walk(path)
|
|
for f in files)
|
|
large_files.append((path, size))
|
|
except:
|
|
pass
|
|
|
|
for path, size in sorted(large_files, key=lambda x: x[1], reverse=True):
|
|
print(f" {path}: {size / (1024*1024):.1f} MB")
|
|
|
|
print("=" * 50)
|
|
|
|
base = 'Win32GUI' if sys.platform == 'win32' else None
|
|
|
|
# 명령행 인자 처리
|
|
if __name__ == "__main__":
|
|
import sys
|
|
if len(sys.argv) > 1 and sys.argv[1] == 'test':
|
|
test_build()
|
|
sys.exit(0)
|
|
|
|
# 빌드 옵션 준비 (빌드 시에만)
|
|
prepare_build_options()
|
|
|
|
setup(
|
|
name=__title__,
|
|
version=__version__,
|
|
description=__description__,
|
|
author=__author__,
|
|
author_email=__author_email__,
|
|
license=__license__,
|
|
packages=find_packages(),
|
|
install_requires=__install_requires__,
|
|
python_requires='>=3.11',
|
|
include_package_data=True,
|
|
zip_safe=False,
|
|
options={'build_exe': build_options},
|
|
executables=[
|
|
Executable(
|
|
'main.py',
|
|
base=base,
|
|
target_name='ImgWorker.exe'
|
|
# icon="Edit_PartTimer3.ico" # 아이콘 파일이 없으므로 기본 아이콘 사용
|
|
)
|
|
],
|
|
cmdclass={
|
|
"build_exe": CustomBuildExe,
|
|
},
|
|
)
|