update-environment-list.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. Python 环境列表更新脚本
  5. 功能:对比虚拟环境中已安装的包和 environment.txt,如果不一致则更新 environment.txt
  6. """
  7. import os
  8. import sys
  9. import subprocess
  10. import platform
  11. from pathlib import Path
  12. # 脚本所在目录即本环境目录,venv 固定为 env
  13. SCRIPT_DIR = Path(__file__).parent.absolute()
  14. _exe = Path(sys.executable).resolve()
  15. if "Scripts" in _exe.parts or "bin" in _exe.parts:
  16. VENV_PATH = _exe.parent.parent
  17. else:
  18. VENV_PATH = SCRIPT_DIR / "env"
  19. ENVIRONMENT_FILE = SCRIPT_DIR / "environment.txt"
  20. # 根据操作系统确定虚拟环境的 pip 路径
  21. if platform.system() == "Windows":
  22. VENV_PIP = VENV_PATH / "Scripts" / "pip.exe"
  23. else:
  24. VENV_PIP = VENV_PATH / "bin" / "pip"
  25. def run_command(cmd, check=True, capture_output=True):
  26. """运行命令并返回结果"""
  27. try:
  28. result = subprocess.run(
  29. cmd,
  30. shell=True,
  31. check=check,
  32. capture_output=capture_output,
  33. text=True,
  34. encoding='utf-8'
  35. )
  36. return result.returncode == 0, result.stdout, result.stderr
  37. except subprocess.CalledProcessError as e:
  38. return False, e.stdout if hasattr(e, 'stdout') else "", str(e)
  39. def get_installed_packages():
  40. """获取虚拟环境中已安装的包列表(使用 pip freeze)"""
  41. if not VENV_PATH.exists():
  42. print("[ERROR] Virtual environment not found")
  43. return None
  44. cmd = f'"{VENV_PIP}" freeze'
  45. success, output, error = run_command(cmd, check=False)
  46. if not success:
  47. print(f"[ERROR] Failed to get installed packages: {error}")
  48. return None
  49. # 解析输出,提取包名和版本
  50. packages = {}
  51. for line in output.strip().split('\n'):
  52. line = line.strip()
  53. if line and not line.startswith('#'):
  54. # 格式:package==version 或 package>=version 等
  55. if '==' in line:
  56. parts = line.split('==', 1)
  57. packages[parts[0].strip().lower()] = line.strip()
  58. else:
  59. # 如果没有版本号,使用整行
  60. pkg_name = line.split('>=')[0].split('<=')[0].split('>')[0].split('<')[0].split('~=')[0].strip()
  61. packages[pkg_name.lower()] = line.strip()
  62. return packages
  63. def read_environment_file():
  64. """读取 environment.txt 中的包列表"""
  65. if not ENVIRONMENT_FILE.exists():
  66. print(f"[WARN] {ENVIRONMENT_FILE} not found, will create new one")
  67. return {}
  68. packages = {}
  69. with open(ENVIRONMENT_FILE, 'r', encoding='utf-8') as f:
  70. for line in f:
  71. line = line.strip()
  72. if line and not line.startswith('#'):
  73. # 提取包名(支持各种版本操作符)
  74. if '==' in line:
  75. parts = line.split('==', 1)
  76. packages[parts[0].strip().lower()] = line.strip()
  77. else:
  78. pkg_name = line.split('>=')[0].split('<=')[0].split('>')[0].split('<')[0].split('~=')[0].strip()
  79. packages[pkg_name.lower()] = line.strip()
  80. return packages
  81. def compare_and_update():
  82. """对比并更新 environment.txt"""
  83. print("Comparing installed packages with environment.txt...")
  84. print("=" * 60)
  85. # 获取已安装的包
  86. installed_packages = get_installed_packages()
  87. if installed_packages is None:
  88. sys.exit(1)
  89. # 读取 environment.txt 中的包
  90. file_packages = read_environment_file()
  91. # 对比差异
  92. installed_set = set(installed_packages.keys())
  93. file_set = set(file_packages.keys())
  94. added_packages = installed_set - file_set
  95. removed_packages = file_set - installed_set
  96. changed_packages = []
  97. # 检查版本变化
  98. for pkg_name in installed_set & file_set:
  99. if installed_packages[pkg_name] != file_packages[pkg_name]:
  100. changed_packages.append(pkg_name)
  101. # 显示差异
  102. if added_packages:
  103. print(f"\n[+] Added packages ({len(added_packages)}):")
  104. for pkg in sorted(added_packages):
  105. print(f" + {installed_packages[pkg]}")
  106. if removed_packages:
  107. print(f"\n[-] Removed packages ({len(removed_packages)}):")
  108. for pkg in sorted(removed_packages):
  109. print(f" - {file_packages[pkg]}")
  110. if changed_packages:
  111. print(f"\n[~] Changed packages ({len(changed_packages)}):")
  112. for pkg in sorted(changed_packages):
  113. print(f" ~ {file_packages[pkg]} -> {installed_packages[pkg]}")
  114. # 判断是否需要更新
  115. if not added_packages and not removed_packages and not changed_packages:
  116. print("\n[OK] environment.txt is up to date")
  117. print(f" Total packages: {len(installed_packages)}")
  118. return True
  119. # 更新 environment.txt
  120. print(f"\nUpdating {ENVIRONMENT_FILE}...")
  121. # 使用 pip freeze 获取完整列表(包含所有依赖)
  122. cmd = f'"{VENV_PIP}" freeze'
  123. success, output, error = run_command(cmd, check=False)
  124. if not success:
  125. print(f"[ERROR] Failed to get installed packages: {error}")
  126. sys.exit(1)
  127. # 写入文件(使用 UTF-8 无 BOM 编码)
  128. with open(ENVIRONMENT_FILE, 'w', encoding='utf-8', newline='\n') as f:
  129. f.write(output)
  130. # 统计包数量
  131. package_count = len([line for line in output.strip().split('\n') if line.strip()])
  132. print(f"[OK] {ENVIRONMENT_FILE} updated successfully")
  133. print(f" Total packages: {package_count}")
  134. return True
  135. def main():
  136. """主函数"""
  137. if not compare_and_update():
  138. sys.exit(1)
  139. sys.exit(0)
  140. if __name__ == "__main__":
  141. main()