# -*- coding: utf-8 -*- """ DirectML 기반 ONNX Runtime 호환성을 위한 의존성 버전 체크 및 교체 스크립트 """ import subprocess import sys import logging import importlib.util # 설정: DirectML 기반 ONNX Runtime 호환성을 위한 고정 버전 REQUIRED_VERSIONS = { 'numpy': '1.26.4', 'scipy': '1.11.4', # 안정 버전 'onnxruntime-directml': '1.22.0', # DirectML 지원 ONNX Runtime 'opencv-python': '4.6.0.66', # OpenCV 호환 버전 } # 프로세스가 사용 중일 때 건너뛸 패키지들 SKIP_IF_IN_USE = ['onnxruntime-directml'] def check_package_version(package_name): """패키지의 현재 설치된 버전을 확인합니다.""" try: if package_name == 'numpy': import numpy return numpy.__version__ elif package_name == 'scipy': # scipy는 직접 import해서 버전 확인 import scipy return scipy.__version__ elif package_name == 'opencv-python': # opencv-contrib-python과 충돌을 피하기 위해 pip show로 직접 확인 try: result = subprocess.run([sys.executable, '-m', 'pip', 'show', package_name], capture_output=True, text=True, timeout=30) if result.returncode == 0: for line in result.stdout.split('\n'): if line.startswith('Version:'): return line.split('Version:')[1].strip() return None except: # fallback to cv2 version import cv2 return cv2.__version__ elif package_name == 'onnxruntime-directml': # onnxruntime-directml은 onnxruntime 모듈로 import됨 import onnxruntime return onnxruntime.__version__ else: # 기본 방법 spec = importlib.util.find_spec(package_name) if spec is None: return None module = importlib.import_module(package_name) return getattr(module, '__version__', None) except ImportError: return None except Exception as e: # import 오류가 발생하면 패키지가 깨진 것으로 간주 print(f"[경고] {package_name} import 오류 (깨진 설치): {str(e)[:100]}...") return "BROKEN" def parse_version(version_str): """버전 문자열을 파싱하여 비교 가능한 튜플로 변환합니다.""" try: # 버전 문자열에서 숫자와 점만 추출 import re clean_version = re.sub(r'[^\d\.]', '', version_str) parts = clean_version.split('.') # 각 부분을 정수로 변환 (빈 문자열은 0으로 처리) return tuple(int(part) if part.isdigit() else 0 for part in parts) except Exception: return (0,) def is_version_compatible(current_version, required_version): """현재 버전이 요구 버전과 호환되는지 확인합니다.""" if current_version is None: return False try: # 정확한 버전 매치 확인 current_tuple = parse_version(current_version) required_tuple = parse_version(required_version) return current_tuple == required_tuple except Exception as e: print(f"버전 비교 중 오류: {e}") return False def install_package(package_name, package_version, force=False): """특정 버전의 패키지를 설치합니다.""" try: package_spec = f"{package_name}=={package_version}" if force: # 강제 재설치 print(f"[새로고침] {package_spec} 강제 재설치 중...") cmd = [sys.executable, '-m', 'pip', 'install', '--force-reinstall', '--no-deps', package_spec] else: # 일반 설치 print(f"[패키지] {package_spec} 설치 중...") cmd = [sys.executable, '-m', 'pip', 'install', package_spec] result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) if result.returncode == 0: print(f"[성공] {package_spec} 설치 완료") return True else: # 권한 문제 또는 파일 사용 중 오류 체크 error_output = result.stderr.lower() if any(err in error_output for err in ['winError 5', '액세스가 거부', 'permission denied', 'access is denied']): print(f"[경고] {package_spec} 권한 문제로 설치 실패") print("[팁] 해결 방법:") print(" 1. 관리자 권한으로 명령 프롬프트를 실행 후 재시도") print(" 2. 또는 현재 Python 프로세스를 종료 후 재시도") return False elif 'already satisfied' in result.stdout.lower(): print(f"[성공] {package_spec} 이미 설치됨") return True else: print(f"[실패] {package_spec} 설치 실패:") print(f"stderr: {result.stderr[:500]}...") # 에러 메시지 길이 제한 return False except subprocess.TimeoutExpired: print(f"[시간초과] {package_spec} 설치 시간 초과 (5분)") return False except Exception as e: print(f"[오류] {package_spec} 설치 중 예외: {e}") return False def uninstall_package(package_name): """패키지를 제거합니다.""" try: print(f"[삭제] {package_name} 제거 중...") cmd = [sys.executable, '-m', 'pip', 'uninstall', '-y', package_name] result = subprocess.run(cmd, capture_output=True, text=True, timeout=120) if result.returncode == 0: print(f"[성공] {package_name} 제거 완료") return True else: print(f"[경고] {package_name} 제거 실패 (이미 제거되었을 수 있음)") return True # 제거 실패는 큰 문제가 아님 except subprocess.TimeoutExpired: print(f"[시간초과] {package_name} 제거 시간 초과") return False except Exception as e: print(f"[오류] {package_name} 제거 중 예외: {e}") return False def check_and_fix_dependencies(): """의존성을 체크하고 필요시 교체합니다.""" print("[검색] DirectML 기반 ONNX Runtime 호환성을 위한 의존성 체크 시작") print("=" * 60) needs_restart = False for package_name, required_version in REQUIRED_VERSIONS.items(): print(f"\n[체크] {package_name} 버전 체크 중...") current_version = check_package_version(package_name) if current_version is None: print(f"[실패] {package_name}이 설치되지 않음") if install_package(package_name, required_version): needs_restart = True else: print(f"[오류] {package_name} 설치 실패") return False elif current_version == "BROKEN": print(f"[오류] {package_name}이 깨진 상태로 설치됨 - 재설치 필요") # 프로세스가 사용 중일 수 있는 패키지는 경고만 출력 if package_name in SKIP_IF_IN_USE: print(f"[경고] {package_name}는 현재 프로세스에서 사용 중일 수 있어 교체를 건너뜁니다.") print("[팁] 해결 방법:") print(" 1. 현재 Python 프로세스를 완전히 종료") print(" 2. fix_deps_admin.bat 실행 (관리자 권한)") print(f" 3. 또는 수동으로: pip uninstall {package_name} && pip install {package_name}=={required_version}") continue # 깨진 패키지 강제 재설치 if uninstall_package(package_name): if install_package(package_name, required_version): needs_restart = True else: print(f"[경고] {package_name} 재설치 실패 - 수동 설치 필요") else: print(f"[경고] {package_name} 제거 실패 - 수동 정리 필요") elif not is_version_compatible(current_version, required_version): print(f"[경고] {package_name} 버전 불일치:") print(f" 현재: {current_version}") print(f" 필요: {required_version}") # 프로세스가 사용 중일 수 있는 패키지는 경고만 출력 if package_name in SKIP_IF_IN_USE: print(f"[경고] {package_name}는 현재 프로세스에서 사용 중일 수 있어 교체를 건너뜁니다.") print("[팁] 해결 방법:") print(" 1. 현재 Python 프로세스를 완전히 종료") print(" 2. 관리자 권한으로 새 명령 프롬프트 실행") print(f" 3. pip install {package_name}=={required_version} --force-reinstall") print(" 4. 빌드 다시 실행") continue # 기존 버전 제거 후 재설치 if uninstall_package(package_name): if install_package(package_name, required_version): needs_restart = True else: print(f"[경고] {package_name} 재설치 실패 - 빌드 계속 진행") # return False # 실패해도 빌드는 계속 진행 else: print(f"[경고] {package_name} 제거 실패 - 빌드 계속 진행") # return False # 실패해도 빌드는 계속 진행 else: print(f"[성공] {package_name} {current_version} (호환됨)") if needs_restart: print("\n[새로고침] 패키지 변경으로 인해 Python 재시작이 권장됩니다.") print(" cx_Freeze 빌드를 다시 실행해주세요.") print("\n[완료] 모든 의존성 체크 완료!") return True def get_package_info(): """현재 설치된 패키지 정보를 출력합니다.""" print("\n[정보] 현재 패키지 정보:") print("-" * 40) for package_name, required_version in REQUIRED_VERSIONS.items(): current_version = check_package_version(package_name) status = "[성공]" if current_version and is_version_compatible(current_version, required_version) else "[실패]" print(f"{status} {package_name:15} {current_version or 'Not installed':15} (요구: {required_version})") def main(): """메인 함수""" print("[도구] DirectML 기반 ONNX Runtime 호환성 체커") print("=" * 60) try: # 1. 현재 상태 출력 get_package_info() # 2. 의존성 체크 및 수정 success = check_and_fix_dependencies() if success: print("\n[성공] 의존성 체크 완료 - 빌드 진행 가능") # 3. 최종 상태 출력 get_package_info() return 0 else: print("\n[실패] 의존성 체크 실패 - 빌드 중단") return 1 except KeyboardInterrupt: print("\n[경고] 사용자에 의해 중단됨") return 1 except Exception as e: print(f"\n[오류] 예상치 못한 오류: {e}") import traceback traceback.print_exc() return 1 if __name__ == "__main__": exit_code = main() sys.exit(exit_code)