273 lines
11 KiB
Python
273 lines
11 KiB
Python
# -*- 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)
|