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/papago_translator", "src/AI_Module/ai_client", "src/AI_Module/baseAIProvider", "src/AI_Module/gpt_provider", "src/AI_Module/grok_provider", "src/AI_Module/openRouter_provider", "src/AI_Module/gemini_provider", ] # 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", "lib/src/modules/gemma_client.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", "lib/src/modules/gemma_client.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 = [ '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.gemma_client', 'src.modules.onnx_ocr_module', ] # 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/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] 설치 스크립트 생성 시작...") 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, }, )