247 lines
9.3 KiB
Python
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()
|