wechat-robot-skills/skills/export-chat-history/scripts/bootstrap.py

132 lines
3.5 KiB
Python

#!/usr/bin/env python3
from __future__ import annotations
import hashlib
import shutil
import subprocess
import sys
import traceback
from pathlib import Path
sys.stderr = sys.stdout
def _skill_root(script_dir: Path) -> Path:
return script_dir.parent
def _venv_dir(script_dir: Path) -> Path:
return _skill_root(script_dir) / ".venv"
def _venv_python(venv_dir: Path) -> Path:
if sys.platform == "win32":
return venv_dir / "Scripts" / "python.exe"
return venv_dir / "bin" / "python"
def _stamp_file(venv_dir: Path) -> Path:
return venv_dir / ".req_hash"
def _file_hash(path: Path) -> str:
return hashlib.sha256(path.read_bytes()).hexdigest()
def _deps_up_to_date(requirements_file: Path, venv_dir: Path) -> bool:
stamp = _stamp_file(venv_dir)
return stamp.is_file() and stamp.read_text().strip() == _file_hash(
requirements_file
)
def _write_stamp(requirements_file: Path, venv_dir: Path) -> None:
_stamp_file(venv_dir).write_text(_file_hash(requirements_file))
def _ensure_venv(venv_dir: Path, venv_python: Path) -> int:
if venv_python.is_file():
return 0
sys.stdout.write(f"未检测到技能虚拟环境,正在创建: {venv_dir}\n")
python_executable = sys.executable or next(
(
shutil.which(candidate)
for candidate in ("python3", "python")
if shutil.which(candidate)
),
None,
)
if not python_executable:
raise RuntimeError("无法找到 Python 解释器路径")
try:
subprocess.run(
[python_executable, "-m", "venv", str(venv_dir)],
check=True,
stdout=sys.stdout,
stderr=sys.stdout,
)
except subprocess.CalledProcessError as exc:
sys.stdout.write(f"创建虚拟环境失败,退出码: {exc.returncode}\n")
return exc.returncode or 1
return 0
def main() -> int:
script_dir = Path(__file__).resolve().parent
requirements_file = script_dir / "requirements.txt"
venv_dir = _venv_dir(script_dir)
venv_python = _venv_python(venv_dir)
if not requirements_file.is_file():
sys.stdout.write(f"未找到依赖文件: {requirements_file}\n")
return 1
ensure_result = _ensure_venv(venv_dir, venv_python)
if ensure_result != 0:
return ensure_result
if _deps_up_to_date(requirements_file, venv_dir):
sys.stdout.write("依赖已是最新,跳过安装\n")
return 0
try:
subprocess.run(
[str(venv_python), "-m", "pip", "install", "--upgrade", "pip"],
check=True,
stdout=sys.stdout,
stderr=sys.stdout,
)
subprocess.run(
[
str(venv_python),
"-m",
"pip",
"install",
"-r",
str(requirements_file),
],
check=True,
stdout=sys.stdout,
stderr=sys.stdout,
)
except subprocess.CalledProcessError as exc:
sys.stdout.write(f"安装依赖失败,退出码: {exc.returncode}\n")
return exc.returncode or 1
_write_stamp(requirements_file, venv_dir)
sys.stdout.write(f"依赖安装完成,当前技能虚拟环境: {venv_dir}\n")
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except SystemExit:
raise
except Exception:
traceback.print_exc(file=sys.stdout)
raise SystemExit(1)