relax local install python requirement to 3.11

This commit is contained in:
jxxghp
2026-04-16 23:13:45 +08:00
parent e0e21e39a2
commit 810cb0a203
5 changed files with 167 additions and 81 deletions

View File

@@ -168,43 +168,74 @@ detect_package_manager() {
esac
}
find_python() {
if command -v python3 >/dev/null 2>&1; then
command -v python3
return 0
fi
if command -v python >/dev/null 2>&1; then
command -v python
python_version_ok() {
local python_bin="$1"
"$python_bin" - <<'PY' >/dev/null 2>&1
import sys
raise SystemExit(0 if sys.version_info >= (3, 11) else 1)
PY
}
try_python_candidate() {
local candidate="$1"
local python_path=""
python_path="$(command -v "$candidate" 2>/dev/null || true)"
if [[ -n "$python_path" ]] && python_version_ok "$python_path"; then
printf '%s\n' "$python_path"
return 0
fi
return 1
}
python_version_ok() {
local python_bin="$1"
"$python_bin" - <<'PY' >/dev/null 2>&1
import sys
raise SystemExit(0 if sys.version_info >= (3, 12) else 1)
PY
find_python() {
local minor=""
for minor in 20 19 18 17 16 15 14 13 12 11; do
if try_python_candidate "python3.$minor"; then
return 0
fi
done
if try_python_candidate python3; then
return 0
fi
if try_python_candidate python; then
return 0
fi
return 1
}
find_uv_python() {
local uv_bin="$1"
local minor=""
local python_path=""
for minor in 20 19 18 17 16 15 14 13 12 11; do
python_path="$("$uv_bin" python find "3.$minor" 2>/dev/null || true)"
if [[ -n "$python_path" ]] && python_version_ok "$python_path"; then
printf '%s\n' "$python_path"
return 0
fi
done
return 1
}
python_install_hint() {
case "$OS_NAME" in
macOS)
echo "脚本已尝试自动安装 Git、curl 和 Python 3.12+。" >&2
echo "如果自动安装失败,请先安装 Homebrew或手动执行brew install git curl python@3.12" >&2
echo "脚本已尝试自动安装 Git、curl 和 Python 3.11+。" >&2
echo "如果自动安装失败,请先安装 Homebrew或手动执行brew install git curl python@3.11" >&2
;;
Linux*)
echo "脚本已尝试自动安装 Git、curl 和 Python 3.12+。" >&2
echo "如果自动安装失败,请先安装 Git、curl 和 Python 3.12,并确保包含 venv 模块。" >&2
echo "例如 Debian/Ubuntu: sudo apt install git curl python3.12 python3.12-venv" >&2
echo "例如 Fedora/RHEL: sudo dnf install git curl python3.12" >&2
echo "脚本已尝试自动安装 Git、curl 和 Python 3.11+。" >&2
echo "如果自动安装失败,请先安装 Git、curl 和 Python 3.11+,并确保包含 venv 模块。" >&2
echo "例如 Debian/Ubuntu: sudo apt install git curl python3.11 python3.11-venv" >&2
echo "例如 Fedora/RHEL: sudo dnf install git curl python3.11" >&2
;;
Windows)
echo "推荐在 WSL、Linux 或 macOS 终端中运行此脚本。" >&2
;;
*)
echo "请先安装 Git、curl 和 Python 3.12。" >&2
echo "请先安装 Git、curl 和 Python 3.11 或更高版本。" >&2
;;
esac
}
@@ -352,7 +383,7 @@ ensure_uv() {
return 0
fi
echo "==> 自动安装 uv用于拉取 Python 3.12+"
echo "==> 自动安装 uv用于补齐 Python 3.11+ 运行时"
env UV_INSTALL_DIR="$HOME/.local/bin" sh -c "$(curl -LsSf https://astral.sh/uv/install.sh)"
export PATH="$HOME/.local/bin:$PATH"
hash -r
@@ -369,12 +400,18 @@ ensure_python() {
return 0
fi
echo "==> 未找到可用的 Python 3.12+,开始自动安装独立 Python 运行时"
ensure_uv
uv python install 3.12
PYTHON_BIN="$(uv python find 3.12 || true)"
PYTHON_BIN="$(find_uv_python "$(command -v uv)" || true)"
if [[ -n "$PYTHON_BIN" ]] && python_version_ok "$PYTHON_BIN"; then
return 0
fi
echo "==> 未找到可用的 Python 3.11+,开始自动安装独立 Python 运行时"
uv python install 3.11
PYTHON_BIN="$(find_uv_python "$(command -v uv)" || true)"
if [[ -z "$PYTHON_BIN" ]] || ! python_version_ok "$PYTHON_BIN"; then
echo "自动安装 Python 3.12+ 失败。" >&2
echo "自动安装 Python 3.11+ 失败。" >&2
return 1
fi
}

View File

@@ -28,6 +28,8 @@ PUBLIC_DIR = ROOT / "public"
RUNTIME_DIR = ROOT / ".runtime"
NODE_DIR = RUNTIME_DIR / "node"
INSTALL_ENV_FILE = ROOT / ".moviepilot.env"
MIN_PYTHON_VERSION = (3, 11)
SUPPORTED_PYTHON_TEXT = f"Python {MIN_PYTHON_VERSION[0]}.{MIN_PYTHON_VERSION[1]} 或更高版本"
CONFIG_DIR = LEGACY_CONFIG_DIR
LOG_DIR = CONFIG_DIR / "logs"
@@ -204,6 +206,42 @@ def command_exists(name: str) -> bool:
return shutil.which(name) is not None
def get_python_version(python_bin: str) -> tuple[int, int, int]:
version_json = capture([python_bin, "-c", "import json, sys; print(json.dumps(list(sys.version_info[:3])))"])
version_info = json.loads(version_json)
if not isinstance(version_info, list) or len(version_info) < 3:
raise RuntimeError(f"无法识别 Python 版本信息:{python_bin}")
return int(version_info[0]), int(version_info[1]), int(version_info[2])
def discover_supported_python() -> Optional[str]:
candidates = [f"python3.{minor}" for minor in range(20, MIN_PYTHON_VERSION[1] - 1, -1)]
if sys.executable:
candidates.append(sys.executable)
candidates.extend(["python3", "python"])
seen: set[str] = set()
for candidate in candidates:
if not candidate or candidate in seen:
continue
seen.add(candidate)
python_path = candidate if os.sep in candidate else (shutil.which(candidate) or "")
if not python_path:
continue
try:
version = get_python_version(python_path)
except (OSError, subprocess.CalledProcessError, json.JSONDecodeError):
continue
if version >= MIN_PYTHON_VERSION:
return python_path
return None
DEFAULT_BOOTSTRAP_PYTHON = discover_supported_python() or sys.executable
def get_venv_python(venv_dir: Path) -> Path:
if os.name == "nt":
return venv_dir / "Scripts" / "python.exe"
@@ -211,11 +249,10 @@ def get_venv_python(venv_dir: Path) -> Path:
def ensure_supported_python(python_bin: str) -> None:
version_json = capture([python_bin, "-c", "import json, sys; print(json.dumps(list(sys.version_info[:3])))"])
version = tuple(json.loads(version_json))
if version < (3, 12, 0):
version = get_python_version(python_bin)
if version < MIN_PYTHON_VERSION:
raise RuntimeError(
f"MoviePilot 本地安装需要 Python 3.12 或更高版本,当前解释器为 {python_bin} "
f"MoviePilot 本地安装需要 {SUPPORTED_PYTHON_TEXT},当前解释器为 {python_bin} "
f"({version[0]}.{version[1]}.{version[2]})"
)
@@ -1359,6 +1396,7 @@ def install_deps(*, python_bin: str, venv_dir: Path, recreate: bool) -> Path:
ensure_supported_python(python_bin)
venv_dir = venv_dir.expanduser().resolve()
venv_python = get_venv_python(venv_dir)
print_step(f"使用 Python 解释器:{python_bin}")
if recreate and venv_dir.exists():
print_step(f"删除已有虚拟环境:{venv_dir}")
@@ -1512,7 +1550,7 @@ def build_parser() -> argparse.ArgumentParser:
subparsers = parser.add_subparsers(dest="command", required=True)
install_parser = subparsers.add_parser("install-deps", help="创建虚拟环境并安装后端依赖")
install_parser.add_argument("--python", default=sys.executable, help="用于创建虚拟环境的 Python 解释器")
install_parser.add_argument("--python", default=DEFAULT_BOOTSTRAP_PYTHON, help="用于创建虚拟环境的 Python 解释器,默认自动选择本地 3.11+ 版本")
install_parser.add_argument("--venv", default=str(ROOT / "venv"), help="虚拟环境目录")
install_parser.add_argument("--recreate", action="store_true", help="删除并重建虚拟环境")
install_parser.add_argument("--config-dir", help="配置目录,默认使用程序目录外的系统配置目录")
@@ -1536,7 +1574,7 @@ def build_parser() -> argparse.ArgumentParser:
init_parser.add_argument("--config-dir", help="配置目录,默认使用程序目录外的系统配置目录")
setup_parser = subparsers.add_parser("setup", help="执行 install-deps、install-frontend、install-resources 和 init")
setup_parser.add_argument("--python", default=sys.executable, help="用于创建虚拟环境的 Python 解释器")
setup_parser.add_argument("--python", default=DEFAULT_BOOTSTRAP_PYTHON, help="用于创建虚拟环境的 Python 解释器,默认自动选择本地 3.11+ 版本")
setup_parser.add_argument("--venv", default=str(ROOT / "venv"), help="虚拟环境目录")
setup_parser.add_argument("--recreate", action="store_true", help="删除并重建虚拟环境")
setup_parser.add_argument("--frontend-version", default="latest", help="前端版本,默认 latest")
@@ -1560,7 +1598,7 @@ def build_parser() -> argparse.ArgumentParser:
update_parser.add_argument("--ref", default="latest", help="后端 Git 版本,默认 latest")
update_parser.add_argument("--frontend-version", default="latest", help="前端版本,默认 latest")
update_parser.add_argument("--node-version", default=DEFAULT_NODE_VERSION, help="本地 Node 运行时版本")
update_parser.add_argument("--python", default=sys.executable, help="用于安装后端依赖的 Python 解释器")
update_parser.add_argument("--python", default=DEFAULT_BOOTSTRAP_PYTHON, help="用于安装后端依赖的 Python 解释器,默认自动选择本地 3.11+ 版本")
update_parser.add_argument("--venv", default=str(ROOT / "venv"), help="虚拟环境目录")
update_parser.add_argument("--recreate", action="store_true", help="删除并重建虚拟环境")
update_parser.add_argument("--skip-resources", action="store_true", help="更新 all 时跳过资源同步")