Files

132 lines
5.0 KiB
Python

#!/usr/bin/env python3
"""SenSu PyEnvManager — Python 版本管理 + venv + 依赖安装"""
import os, sys, subprocess, logging, shutil
from pathlib import Path
from typing import Optional, List
logger = logging.getLogger(__name__)
class PyEnvManager:
def __init__(self, workspace_dir: str = None):
self.workspace = Path(workspace_dir or os.getcwd())
self.venvs_dir = self.workspace / "venvs"
self.venvs_dir.mkdir(parents=True, exist_ok=True)
def detect_versions(self) -> List[str]:
versions = set()
# Current Python
v = f"{sys.version_info.major}.{sys.version_info.minor}"
versions.add(v)
# Check Termux pkg
try:
result = subprocess.run(["pkg", "list-installed"], capture_output=True, text=True, timeout=10)
for line in result.stdout.split("\n"):
if line.startswith("python-3.") or line.startswith("python3-"):
versions.add(line.split("/")[0].replace("python-", "").replace("python3-", ""))
except:
pass
# Check pyenv
pyenv = shutil.which("pyenv")
if pyenv:
try:
result = subprocess.run([pyenv, "versions", "--bare"], capture_output=True, text=True, timeout=10)
for line in result.stdout.split("\n"):
line = line.strip()
if line and line[0].isdigit():
versions.add(line.split("/")[0])
except:
pass
return sorted(versions)
def ensure_version(self, version: str) -> Optional[str]:
"""确保指定 Python 版本可用,返回解释器路径"""
available = self.detect_versions()
if version in available:
return self._find_python(version)
# Try to install via Termux
if shutil.which("pkg"):
pkg_name = f"python-{version}"
logger.info(f"尝试安装 {pkg_name} ...")
try:
subprocess.run(["pkg", "install", "-y", pkg_name], check=True, timeout=120)
return self._find_python(version)
except:
pass
logger.warning(f"无法获取 Python {version},使用当前版本")
return sys.executable
def _find_python(self, version: str) -> Optional[str]:
for name in [f"python{version}", f"python{version[:3]}", "python3"]:
path = shutil.which(name)
if path: return path
return sys.executable
def create_venv(self, name: str, python_version: str = None) -> Optional[Path]:
venv_path = self.venvs_dir / name
if venv_path.exists():
logger.info(f"venv 已存在: {venv_path}")
return venv_path
python_exe = self.ensure_version(python_version) if python_version else sys.executable
logger.info(f"创建 venv: {venv_path} (Python {python_version or 'default'})")
try:
import venv as _venv
_venv.create(str(venv_path), with_pip=True, clear=True)
# Install/upgrade pip
pip = str(venv_path / "bin" / "pip")
subprocess.run([pip, "install", "--upgrade", "pip"], capture_output=True, timeout=60)
return venv_path
except Exception as e:
logger.error(f"创建 venv 失败: {e}")
# Fallback: use virtualenv
try:
subprocess.run([sys.executable, "-m", "virtualenv", str(venv_path)], check=True, timeout=120)
return venv_path
except:
return None
def install_deps(self, venv_path: Path, requirements: List[str]) -> bool:
pip = str(venv_path / "bin" / "pip")
for req_file in requirements:
req_path = Path(req_file)
if not req_path.is_absolute():
# Relative to workspace
pass
if Path(req_file).exists():
logger.info(f"安装依赖: {req_file}")
try:
subprocess.run([pip, "install", "-r", req_file], check=True, timeout=300)
except subprocess.CalledProcessError as e:
logger.warning(f"依赖安装部分失败: {e}")
return False
return True
def clone_git(self, url: str, target_dir: Path, branch: str = None) -> bool:
if target_dir.exists():
logger.info(f"目录已存在: {target_dir}")
# Try git pull instead
try:
subprocess.run(["git", "-C", str(target_dir), "pull"], check=True, timeout=60)
return True
except:
pass
cmd = ["git", "clone"]
if branch:
cmd += ["-b", branch]
cmd += [url, str(target_dir)]
try:
subprocess.run(cmd, check=True, timeout=300)
logger.info(f"Git clone 完成: {url}{target_dir}")
return True
except subprocess.CalledProcessError as e:
logger.error(f"Git clone 失败: {e}")
return False