AutoPercenty3/setup_ob2.py

714 lines
29 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 updateManager.__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 = [
# "browser_control",
"locatorManager_by_SP",
"src/contents/details",
"src/contents/option",
"src/contents/price",
"src/contents/tags",
"src/contents/thumb",
"src/contents/titleGenerator",
"src/img_module/image_processor_dialog",
"src/img_module/image_processor_manager",
"src/modules/image_worker_client",
"src/lens/aliprice_lens_client",
"src/lens/naver_lens_adapter",
"src/lens/naver_lens_client",
"src/lens/naver_lens_parser",
"src/sp_manager",
"src/titleManager/gpt_client",
"src/titleManager/grok_client",
"src/titleManager/naverAPI",
"src/titleManager/naver_parser",
"src/titleManager/sp_ForbiddenM",
"src/translator/chinese_dict_manager",
"src/translator/chinese_preprocessor",
"src/translator/papago_translator",
"src/AI_Module/ai_client",
"src/AI_Module/baseAIProvider",
"src/AI_Module/gpt_provider",
"src/AI_Module/grok_provider",
"src/AI_Module/gemini_provider",
"src/AI_Module/openRouter_provider",
"src/AI_Module/local_provider",
]
# Cython 모듈들을 점 표기법으로 변환 (자동 생성)
CYTHON_MODULE_NAMES = set()
for module_path in CYTHON_MODULES:
clean_path = os.path.splitext(module_path)[0]
module_name = clean_path.replace("/", ".").replace("\\", ".")
CYTHON_MODULE_NAMES.add(module_name)
# 2. 빌드 후 제거할 불필요한 파일/폴더 목록 (ImageProcessor3 관련 등)
CLEANUP_TARGETS = [
# 디렉토리
"lib/src/modules/briaaiModel", "lib/src/modules/migan_onnx", "lib/src/modules/modules",
"lib/src/modules/ocr_backends", "lib/src/modules/onnx_ocr_module", "lib/src/modules/output",
"lib/src/modules/outputs", "lib/src/modules/PP_Models", "lib/src/modules/rembg_models",
"lib/src/PP_Models", "lib/paddle", "lib/paddleocr", "lib/onnxruntime", "lib/skimage", "lib/scipy",
# 파일
"lib/src/modules/migan_traced.pt",
"lib/src/modules/image_processor3.py", "lib/src/modules/image_processor4.py",
"lib/src/modules/image_worker.py", "lib/src/modules/image_worker_manager.py",
"lib/src/modules/ocr_module.py", "lib/src/modules/mask_module_for_paddle.py",
"lib/src/modules/text_rendering_module.py", "lib/src/modules/postImageManager.py",
"lib/src/modules/request_inpaint.py", "lib/src/modules/migan_module.py",
"lib/src/modules/background_removal_module.py", "lib/src/modules/bria_background_removal_module.py",
# 컴파일된 잔재 (.pyc)
"lib/src/modules/image_processor3.pyc", "lib/src/modules/image_processor4.pyc",
"lib/src/modules/image_worker.pyc", "lib/src/modules/image_worker_manager.pyc",
"lib/src/modules/ocr_module.pyc", "lib/src/modules/mask_module_for_paddle.pyc",
"lib/src/modules/text_rendering_module.pyc", "lib/src/modules/postImageManager.pyc",
"lib/src/modules/request_inpaint.pyc", "lib/src/modules/migan_module.pyc",
"lib/src/modules/background_removal_module.pyc", "lib/src/modules/bria_background_removal_module.pyc",
# [PySide6 다이어트]
# 1. 번역 파일 (한국어/영어 외 제거하려면 통째로 지우거나 선별)
# 보통 프로그램 내 텍스트가 한글이라면 Qt 자체 번역 파일은 없어도 됨
"lib/PySide6/translations",
# 2. 불필요한 플러그인 (사용 안 하는 기능)
# 주의: 'platforms', 'styles', 'imageformats'는 지우면 안 됩니다!
"lib/PySide6/plugins/sqldrivers", # DB 안 쓰면 삭제
"lib/PySide6/plugins/multimedia", # 오디오/비디오 안 쓰면 삭제
"lib/PySide6/plugins/positioning", # GPS 안 쓰면 삭제
"lib/PySide6/plugins/sensors", # 센서 안 쓰면 삭제
"lib/PySide6/plugins/texttospeech", # TTS 안 쓰면 삭제
"lib/PySide6/plugins/webview", # WebView 안 쓰면 삭제 (중요)
# 3. Qt Quick / QML 관련 (위젯 기반 앱이면 필요 없음)
"lib/PySide6/qml",
"lib/PySide6/Qt/qml",
# 4. 개발용/디자이너용 도구 (배포 시 불필요)
"lib/PySide6/designer",
"lib/PySide6/scripts",
"lib/PySide6/examples",
"lib/PySide6/glue",
"lib/PySide6/include",
# 5. 대용량 DLL (사용 여부 확인 필요)
# OpenGL 소프트웨어 렌더러 (그래픽 카드 없는 PC용, 보통 없어도 됨)
"lib/PySide6/opengl32sw.dll",
# QtWebEngine (크롬 브라우저 내장). 만약 Selenium/Playwright만 쓰고
# Qt 위젯 내에서 브라우저를 띄우지 않는다면 지워도 됨 (엄청 큼!)
"lib/PySide6/Qt6WebEngineCore.dll",
"lib/PySide6/Qt6WebEngineCore.pyd",
"lib/PySide6/Qt6WebEngineWidgets.dll",
]
# 3. 소스 패키징에서 강제로 제외할 패키지들
BASE_EXCLUDES_RAW = [
'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',
'src.modules.image_processor3', 'src.modules.image_processor4',
'src.modules.image_worker', 'src.modules.image_worker_manager',
'src.modules.ocr_module', 'src.modules.mask_module_for_paddle',
'src.modules.text_rendering_module', 'src.modules.postImageManager',
'src.modules.request_inpaint', 'src.modules.migan_module',
'src.modules.background_removal_module', 'src.modules.bria_background_removal_module',
'src.modules.onnx_ocr_module',
]
# CYTHON_MODULES에 포함된 모듈들은 BASE_EXCLUDES에서 자동 제외
BASE_EXCLUDES = [pkg for pkg in BASE_EXCLUDES_RAW if pkg not in CYTHON_MODULE_NAMES]
# 4. 강제 포함 패키지
BASE_INCLUDES = [
'browser_control',
'loggerModule', 'toggleSwitch',
'src.cmdDiag', 'src.inputDiag', 'src.keyword', 'src.priceSetDiag',
# 'src.titleManager',
# 'src.titleManager.naver_parser', 'src.titleManager.naverAPI', 'src.titleManager.gpt_client', 'src.titleManager.grok_client',
# 'src.translator',
# 'src.translator.chinese_dict_manager', 'src.translator.papago_translator',
'src.discord_manager', 'src.unwantedDiag', 'src.unwantedDiag.unwanted_words_dialog',
'src.logDialog', 'src.logDialog.log_dialog', 'src.logDialog.log_filter',
'src.modules.settings_manager',
'src.modules.gpu_status_checker', 'src.modules.gpu_utils', 'src.gpuDiag',
# 'src.modules.image_worker_client', 'src.img_module.image_processor_manager', 'src.img_module.image_processor_dialog',
'src.modules.fonts.fontSelectDialog',
# 'src.lens.naver_lens_client', 'src.lens.naver_lens_parser', 'src.lens.naver_lens_adapter', 'src.lens.aliprice_lens_client',
'translatepy', 'translatepy.translators', 'translatepy.translators.google',
'PIL', 'PIL.Image', 'PIL.ImageOps', 'PIL.ImageEnhance', 'PIL.ImageFilter', 'PIL.features', 'cv2',
'numpy', 'numpy.core', 'numpy.random',
'supabase', 'gotrue', 'storage3', 'postgrest', 'supafunc', 'realtime',
'pydantic', 'pydantic_core',
'json', 'json.encoder', 'json.decoder', 'json.scanner',
'httpx', 'httpx.__version__', 'httpx._models', 'httpx._client', 'httpx._config',
'curl_cffi', 'curl_cffi.requests',
'pathlib',
'shiboken6',
'greenlet',
# 'playwright','playwright.async_api', 'playwright._impl',
'PySide6.QtCore', 'PySide6.QtGui', 'PySide6.QtWidgets', 'PySide6.QtNetwork',
'requests',
'pandas',
'sqlite3',
'packaging',
'markdown',
'pyperclip',
'psutil',
'comtypes',
'comtypes.stream',
'win32com',
'win32com.client',
'win32com.server',
'pythoncom',
]
# ============================================================================
# [Helper Functions] 로직 분리
# ============================================================================
def run_setup_clean():
"""
기존 빌드찌꺼기 제거
"""
print("\n>>> [Cleaner] 기존 빌드찌꺼기 제거 시작...")
subprocess.run(["python", "setup_clean.py"])
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
# 모듈 이름 생성 (경로 -> 점 표기법)
# 예: src/contents/option -> src.contents.option
# 이렇게 해야 'src' 패키지 안의 모듈로 정확히 인식됩니다.
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:
# 모듈 이름 변환 (src/contents/price -> src.contents.price)
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/src/...
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 리스트를 생성합니다."""
base_dir = os.path.dirname(__file__)
updater_file = os.path.join(base_dir, 'updateManager', 'updater.exe')
# 기본 리소스 파일들
files = [
('src/Edit_PartTimer3.ico', 'lib/src/Edit_PartTimer3.ico'),
('kiprisCategories.json', 'kiprisCategories.json'),
('src/keyword/kiprisCategories.json', 'lib/src/keyword/kiprisCategories.json'),
('src/modules/fonts', 'lib/src/modules/fonts'),
(updater_file, 'updater.exe'),
('퍼센티 다양한 카테고리 엑셀 수집(스스 기준).xlsx', '퍼센티 다양한 카테고리 엑셀 수집(스스 기준).xlsx'),
('src/Percenty_SS_Code.json', 'lib/src/Percenty_SS_Code.json'),
('src/browsers/chromium-1200', 'lib/src/browsers/chromium-1200'),
('src/browsers/extensions', 'lib/src/browsers/extensions'),
('C:/Windows/System32/vcomp140.dll', 'vcomp140.dll'),
]
# VC Runtime Files
vc_runtimes = [
('C:/Windows/System32/vcruntime140.dll', 'vcruntime140.dll'),
('C:/Windows/System32/vcruntime140_1.dll', 'vcruntime140_1.dll'),
('C:/Windows/System32/msvcp140.dll', 'msvcp140.dll'),
('C:/Windows/System32/msvcp140_1.dll', 'msvcp140_1.dll'),
('C:/Windows/System32/msvcp140_2.dll', 'msvcp140_2.dll'),
('C:/Windows/System32/concrt140.dll', 'concrt140.dll'),
('C:/Windows/System32/vcomp140.dll', 'vcomp140.dll'),
]
files.extend(vc_runtimes)
dll_files = [
# "LIBPQ.dll",
# "MIMAPI64.dll",
# "Qt63DQuickScene3D.dll",
"api-ms-win-core-com-l1-1-0.dll",
"api-ms-win-core-debug-l1-1-0.dll",
"api-ms-win-core-errorhandling-l1-1-0.dll",
"api-ms-win-core-handle-l1-1-0.dll",
"api-ms-win-core-heap-l1-1-0.dll",
# "api-ms-win-core-heap-l2-1-0.dll",
"api-ms-win-core-interlocked-l1-1-0.dll",
# "api-ms-win-core-libraryloader-l1-2-0.dll",
# "api-ms-win-core-libraryloader-l1-2-1.dll",
"api-ms-win-core-localization-l1-2-0.dll",
# "api-ms-win-core-path-l1-1-0.dll",
"api-ms-win-core-processthreads-l1-1-0.dll",
"api-ms-win-core-processthreads-l1-1-1.dll",
"api-ms-win-core-profile-l1-1-0.dll",
# "api-ms-win-core-realtime-l1-1-1.dll",
"api-ms-win-core-rtlsupport-l1-1-0.dll",
"api-ms-win-core-synch-l1-1-0.dll",
"api-ms-win-core-synch-l1-2-0.dll",
"api-ms-win-core-sysinfo-l1-1-0.dll",
# "api-ms-win-core-winrt-error-l1-1-0.dll",
# "api-ms-win-core-winrt-l1-1-0.dll",
# "api-ms-win-core-winrt-string-l1-1-0.dll",
"api-ms-win-crt-conio-l1-1-0.dll",
"api-ms-win-crt-environment-l1-1-0.dll",
"api-ms-win-crt-filesystem-l1-1-0.dll",
"api-ms-win-crt-multibyte-l1-1-0.dll",
"api-ms-win-crt-private-l1-1-0.dll",
"api-ms-win-crt-process-l1-1-0.dll",
"api-ms-win-crt-time-l1-1-0.dll",
"api-ms-win-crt-utility-l1-1-0.dll",
# "api-ms-win-power-base-l1-1-0.dll",
# "api-ms-win-power-setting-l1-1-0.dll",
# "api-ms-win-shcore-scaling-l1-1-1.dll",
]
# DLL 파일 처리
system32_path = "C:/Windows/System32/downlevel"
dll_include_files = [(os.path.join(system32_path, dll), dll) for dll in dll_files]
files.extend(dll_include_files)
# 경로 존재 확인
final_files = []
for src, dest in files:
if os.path.exists(src):
final_files.append((src, dest))
else:
print(f" [Error] 경로 없음(Skip): {src}")
return final_files
# ============================================================================
# [Main Logic] 실행 로직
# ============================================================================
# 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
build_options = {
'packages': [
'ctypes', 'asyncio', 'subprocess', 'pyperclip', 'numpy',
'requests', 'PIL', 'bs4', 'psutil',
'openai',
'pandas', 'supabase', 'translatepy', 'markdown',
'json', 'json.encoder', 'json.decoder', 'json.scanner',
'playwright',
# 'PySide6',
],
'includes': final_includes,
'excludes': final_excludes,
'include_files': final_include_files,
'zip_include_packages': [],
'optimize': 0,
'silent': True,
'include_msvcr': True,
}
# ============================================================================
# [Custom Build Class] 빌드 프로세스 커스터마이징
# ============================================================================
class CustomBuildExe(_build_exe):
def run(self):
# 전체 진행률: 100
# 초기값 10: Cython 빌드는 이 클래스 실행 전(if __name__...)에 이미 완료됨
print("\n" + "="*60)
print(" AutoPercenty3 통합 빌드 시스템 시작")
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")
_build_exe.run(self)
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 = "Edit_PartTimer 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
# 새 파일명: Edit_PartTimer Setup_V3.12.15.exe
new_name = f"Edit_PartTimer 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/files/add"
headers = {
"apikey": GOKAPI_APIKEY
}
with open(file_path, "rb") as f:
# 멀티파트 업로드 (Content-Type 명시 필요)
files = {"file": (os.path.basename(file_path), f, "application/octet-stream")}
data = {
"expiryDays": 0 # ✅ 영구 저장
}
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] 설치 스크립트 생성 시작...")
try:
result = subprocess.run([sys.executable, "generate_iss.py"], capture_output=True, text=True)
if result.returncode == 0:
print("Inno Setup 스크립트 생성 완료!")
else:
print(f"Error: {result.stderr}")
except Exception as e:
print(f"Exception: {e}")
def _compile_inno_installer(self):
"""
생성된 .iss 파일을 ISCC.exe로 컴파일하여 최종 Setup.exe를 만듭니다.
"""
print("\n[Inno Setup] 최종 설치 파일(Setup.exe) 컴파일 시작...")
# 1. ISCC.exe 경로 설정 (Inno Setup 6 기준 기본 경로)
# 만약 설치 경로가 다르다면 이 부분을 수정해주세요.
iscc_path = r"C:\Program Files (x86)\Inno Setup 6\ISCC.exe"
if not os.path.exists(iscc_path):
print(f" [Error] Inno Setup 컴파일러를 찾을 수 없습니다: {iscc_path}")
print(" Inno Setup 6가 설치되어 있는지 확인하거나, iscc_path를 수정하세요.")
return
# 2. 가장 최신 .iss 파일 찾기 (AutoPercenty_날짜.iss 패턴)
# generate_iss.py가 매번 새로운 이름으로 만들기 때문에 최신 파일을 찾아야 함
iss_files = glob.glob("AutoPercenty_*.iss")
if not iss_files:
print(" [Error] 컴파일할 .iss 파일을 찾을 수 없습니다.")
return
# 파일 생성 시간 기준으로 정렬하여 가장 마지막(최신) 파일 선택
latest_iss = max(iss_files, key=os.path.getctime)
print(f" Target Script: {latest_iss}")
# 3. 컴파일 명령어 실행
try:
# subprocess로 ISCC 실행
print(" Compiling... (시간이 조금 걸릴 수 있습니다)")
subprocess.run([iscc_path, latest_iss], check=True)
print(" [Success] ★★★ 모든 작업 완료! 설치 파일이 생성되었습니다. ★★★\n")
except subprocess.CalledProcessError as e:
print(f" [Error] Inno Setup 컴파일 실패: {e}")
# ============================================================================
# [Setup] 최종 실행
# ============================================================================
base = 'Win32GUI' if sys.platform == 'win32' else None
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='Edit_PartTimer3.exe',
icon="Edit_PartTimer3.ico"
)
],
cmdclass={
"build_exe": CustomBuildExe,
},
)