From 2afb08d8b2cc1762d1c22e07a54b8b6298b56563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=A8=E3=82=A4=E3=82=AB=E3=82=AF?= <62183434+zouyonghe@users.noreply.github.com> Date: Mon, 9 Feb 2026 16:19:01 +0900 Subject: [PATCH] fix: handle pip install execution in frozen runtime (#4985) * fix: handle pip install execution in frozen runtime * fix: harden pip subprocess fallback handling --- astrbot/core/utils/pip_installer.py | 53 ++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/astrbot/core/utils/pip_installer.py b/astrbot/core/utils/pip_installer.py index afde805ff..a9f441099 100644 --- a/astrbot/core/utils/pip_installer.py +++ b/astrbot/core/utils/pip_installer.py @@ -5,7 +5,9 @@ import io import locale import logging import os +import shutil import sys +from pathlib import Path from astrbot.core.utils.astrbot_path import get_astrbot_site_packages_path @@ -34,6 +36,28 @@ def _is_frozen_runtime() -> bool: return bool(getattr(sys, "frozen", False)) +def _get_pip_subprocess_executable() -> str | None: + candidates = [ + getattr(sys, "_base_executable", None), + sys.executable, + shutil.which("python3"), + shutil.which("python"), + ] + + for candidate in candidates: + if not candidate: + continue + + candidate_path = Path(candidate) + with contextlib.suppress(OSError): + candidate_path = candidate_path.resolve() + + if candidate_path.is_file() and os.access(candidate_path, os.X_OK): + return str(candidate_path) + + return None + + def _get_pip_main(): try: from pip._internal.cli.main import main as pip_main @@ -92,13 +116,26 @@ class PipInstaller: logger.info(f"Pip 包管理器: pip {' '.join(args)}") result_code = None - if _is_frozen_runtime(): - result_code = await self._run_pip_in_process(args) - else: + + subprocess_executable = _get_pip_subprocess_executable() + if subprocess_executable: try: - result_code = await self._run_pip_subprocess(args) - except FileNotFoundError: - result_code = await self._run_pip_in_process(args) + result_code = await self._run_pip_subprocess( + subprocess_executable, args + ) + except OSError as exc: + logger.warning( + "Failed to launch pip subprocess (%r). Falling back to in-process pip: %s", + subprocess_executable, + exc, + ) + else: + logger.debug( + "No suitable Python executable found for pip subprocess; using in-process pip" + ) + + if result_code is None: + result_code = await self._run_pip_in_process(args) if result_code != 0: raise Exception(f"安装失败,错误码:{result_code}") @@ -107,9 +144,9 @@ class PipInstaller: sys.path.insert(0, target_site_packages) importlib.invalidate_caches() - async def _run_pip_subprocess(self, args: list[str]) -> int: + async def _run_pip_subprocess(self, executable: str, args: list[str]) -> int: process = await asyncio.create_subprocess_exec( - sys.executable, + executable, "-m", "pip", *args,