AutoPercenty3/build_script.py

247 lines
9.3 KiB
Python

import os
import shutil
import subprocess
import sys
# ==========================================
# 1. 설정 및 경로 정의
# ==========================================
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
BUILD_DIR = os.path.join(BASE_DIR, "dist_cython_build") # 빌드 작업 공간
TEMP_CYTHON_SETUP = "temp_setup_cython.py"
# 보호할 핵심 파일 (나머지는 일반 py로 배포)
CORE_LOGIC = [
"src/sp_manager.py",
"browser_control.py",
"mainUI_SP.py",
"src/limited_gui.py",
"src/contents/option.py",
"src/contents/price.py",
"src/contents/details.py",
"src/contents/titleGenerator.py",
"src/contents/thumb.py",
"src/contents/tags.py",
"src/titleManager/naverAPI.py",
"src/gpuDiag/gpu_status_dialog.py",
"src/img_module/image_processor_dialog.py",
"src/img_module/image_processor_manager.py",
"src/lens/naver_lens_client.py",
"src/lens/naver_lens_parser.py",
"src/lens/naver_lens_adapter.py",
"src/lens/aliprice_lens_client.py",
"src/translator/papago_translator.py",
"src/discord_manager/discord_manager.py",
"src/details_input_diag/detail_input_diag.py",
"src/keyword/db_manager.py",
"src/keyword/keyword_manager.py",
"src/keyword/Kipris_for_web.py",
"src/keyword/progressDialog.py",
"src/keyword/worker_for_keyword_loading.py",
"src/keyword/ImportWorker.py",
"src/limited_contents/bizDBManager.py",
"src/limited_contents/business_dialog.py",
"src/logDialog/log_filter.py",
"src/logDialog/log_dialog.py",
"src/logDialog/log_service.py",
"src/logDialog/log_uploader.py",
"src/logs/dict.py",
"src/modules/gpu_status_checker.py",
"src/modules/gpu_status_dialog.py",
"src/modules/gpu_utils.py",
"src/modules/image_processor3.py",
"src/modules/image_worker.py",
"src/modules/image_worker_client.py",
"src/modules/image_worker_manager.py",
"src/modules/priceSetDiag.py",
"src/modules/priceSettingManager.py",
"src/modules/titleManager/sp_ForbiddenM.py",
"src/modules/titleManager/gpt_client.py",
"src/modules/titleManager/grok_client.py",
"src/modules/titleManager/gemma_client.py",
"src/modules/titleManager/forbiddenWordManager.py",
"src/modules/titleManager/titleGenerator.py",
"src/modules/titleManager/naver_parser.py",
"src/modules/titleManager/naverAPI.py",
"src/modules/titleManager/KiprisAPI.py",
"src/modules/titleManager/mongoDBManager.py",
"src/modules/titleManager/openrouter_client.py",
"src/modules/translator/papago_translator.py",
"src/modules/translator/chinese_dict_manager.py",
"src/discord_manager.py",
"src/sp_manager.py",
"updateManager/__version__.py",
"updateManager/update_types.py",
"updateManager/updater.py",
"updateManager/update_history.py",
"updateManager/version_manager.py",
"updateManager/version_manager_multi.py",
"login_dialog.py",
"signup_dialog.py",
"password_change_dialog.py",
"announcement_widget.py",
"settings_dialog.py",
]
# 경로 정규화
CORE_LOGIC = [os.path.normpath(p) for p in CORE_LOGIC]
# [링크] 복사하지 않고 바로가기(Junction)로 연결할 대용량 폴더
LARGE_RESOURCES = [
os.path.normpath("src/browsers"),
os.path.normpath("src/modules/fonts"),
]
# [복사 제외] 빌드 폴더로 복사할 때 제외할 패턴
IGNORE_PATTERNS = shutil.ignore_patterns(
"dist", "build", "dist_cython_build", ".git", ".idea", "__pycache__", "*.pyc", "*.c", "*.spec"
)
# ==========================================
# 2. 유틸리티 함수
# ==========================================
def create_junction(source, link_name):
"""Windows 폴더 심볼릭 링크(Junction) 생성"""
if os.path.exists(link_name):
subprocess.call(f'rmdir "{link_name}"', shell=True)
os.makedirs(os.path.dirname(link_name), exist_ok=True)
cmd = f'mklink /J "{link_name}" "{source}"'
subprocess.check_call(cmd, shell=True)
print(f"🔗 [링크] {link_name} <==> {source}")
def run_command(cmd, cwd=None):
print(f"🚀 [실행] {cmd}")
subprocess.check_call(cmd, shell=True, cwd=cwd)
def create_cython_setup_in_build_dir(target_files):
"""빌드 폴더 내부에 Cython setup 파일을 생성합니다."""
targets_repr = [p.replace("\\", "/") for p in target_files]
content = f"""
from setuptools import setup
from Cython.Build import cythonize
targets = {targets_repr}
setup(
ext_modules=cythonize(targets, compiler_directives={{'language_level': "3"}}),
)
"""
setup_path = os.path.join(BUILD_DIR, TEMP_CYTHON_SETUP)
with open(setup_path, "w", encoding="utf-8") as f:
f.write(content)
return setup_path
def cleanup_source_py_after_compile():
"""컴파일 성공 후 원본 .py 파일과 생성된 .c 파일 삭제"""
print("🧹 보안 정리: 원본 소스(.py) 및 중간 파일(.c) 삭제 중...")
for rel_path in CORE_LOGIC:
target_py = os.path.join(BUILD_DIR, rel_path)
target_c = target_py.replace(".py", ".c")
# .c 파일 삭제
if os.path.exists(target_c):
os.remove(target_c)
# .pyd 파일이 존재하는지 확인 후 원본 .py 삭제
# (파일 이름이 바뀌었을 수 있으므로 디렉토리 스캔)
dir_path = os.path.dirname(target_py)
base_name = os.path.basename(target_py).replace(".py", "")
has_pyd = False
if os.path.exists(dir_path):
for f in os.listdir(dir_path):
if f.startswith(base_name) and f.endswith(".pyd"):
has_pyd = True
break
if has_pyd and os.path.exists(target_py):
os.remove(target_py) # 원본 소스 삭제
def rename_pyd_files_in_build():
"""빌드 폴더 내의 .cp311-win...pyd 파일명을 .pyd로 정규화"""
print("🏷️ PYD 파일 이름 정규화 중...")
for root, dirs, files in os.walk(BUILD_DIR):
for file in files:
if file.endswith(".pyd") and ".cp" in file:
new_name = file.split(".")[0] + ".pyd"
old_path = os.path.join(root, file)
new_path = os.path.join(root, new_name)
if os.path.exists(new_path):
os.remove(new_path)
os.rename(old_path, new_path)
# ==========================================
# 3. 메인 로직
# ==========================================
def main():
try:
print("=== 🏗️ 통합 빌드 프로세스 시작 (Cython -> cx_Freeze -> ISS) ===")
# 1. 빌드 디렉토리 초기화
if os.path.exists(BUILD_DIR):
try:
shutil.rmtree(BUILD_DIR)
except Exception as e:
print(f"⚠️ 기존 빌드 폴더 삭제 실패 (파일 사용 중?): {e}")
return
os.makedirs(BUILD_DIR)
# 2. 소스 미러링 (복사 + 링크)
print("\n[1/4] 개발 환경을 빌드 폴더로 복제 중...")
def copy_filter(src, names):
ignored = []
for name in names:
full_path = os.path.join(src, name)
rel_path = os.path.relpath(full_path, BASE_DIR)
if os.path.normpath(rel_path) in LARGE_RESOURCES:
ignored.append(name)
base_ignored = IGNORE_PATTERNS(src, names)
return ignored + list(base_ignored)
shutil.copytree(BASE_DIR, BUILD_DIR, ignore=copy_filter, dirs_exist_ok=True)
# Junction 링크 생성
for folder in LARGE_RESOURCES:
src_path = os.path.join(BASE_DIR, folder)
dst_path = os.path.join(BUILD_DIR, folder)
if os.path.exists(src_path):
create_junction(src_path, dst_path)
# 3. Cython 컴파일 (빌드 폴더 내부에서)
print("\n[2/4] Cython 컴파일 및 난독화...")
setup_script = create_cython_setup_in_build_dir(CORE_LOGIC)
# setup 파일 실행
run_command(f'"{sys.executable}" {os.path.basename(setup_script)} build_ext --inplace', cwd=BUILD_DIR)
rename_pyd_files_in_build()
cleanup_source_py_after_compile() # .pyd 생성 확인 후 .py 삭제
# 4. cx_Freeze 실행 (setup_non_img.py 사용)
print("\n[3/4] cx_Freeze 패키징 및 ISS 생성...")
# setup_non_img.py는 이 스크립트에 의해 이미 BUILD_DIR로 복사되어 있음
freeze_script = "setup_non_img.py"
if not os.path.exists(os.path.join(BUILD_DIR, freeze_script)):
print(f"❌ 에러: {freeze_script}가 빌드 폴더에 없습니다.")
return
# setup_non_img.py 실행 -> 내부에서 빌드 완료 후 generate_iss.py까지 호출함
run_command(f'"{sys.executable}" {freeze_script} build_exe', cwd=BUILD_DIR)
print(f"\n✅ [4/4] 모든 공정 완료!")
print(f" 📂 실행 파일: {os.path.join(BUILD_DIR, 'build')}")
print(f" 📜 ISS 스크립트: {os.path.join(BUILD_DIR)}")
print(" -> 개발 소스 폴더(BASE_DIR)는 안전하게 보존되었습니다.")
except Exception as e:
print(f"\n❌ 빌드 중 에러 발생: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
main()