mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-06 16:07:01 +08:00
feat: 使用 uv 锁定主程序依赖并强化插件恢复边界 (#6364)
This commit is contained in:
+20
-17
@@ -16,6 +16,7 @@ SUPERUSER=""
|
||||
SUPERUSER_PASSWORD=""
|
||||
OS_NAME="Unknown"
|
||||
PYTHON_BIN=""
|
||||
UV_VERSION="0.12.5"
|
||||
BREW_BIN=""
|
||||
PACKAGE_MANAGER=""
|
||||
PACKAGE_INDEX_UPDATED="false"
|
||||
@@ -177,7 +178,7 @@ 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)
|
||||
raise SystemExit(0 if sys.version_info >= (3, 12) else 1)
|
||||
PY
|
||||
}
|
||||
|
||||
@@ -195,7 +196,7 @@ try_python_candidate() {
|
||||
|
||||
find_python() {
|
||||
local minor=""
|
||||
for minor in 20 19 18 17 16 15 14 13 12 11; do
|
||||
for minor in 20 19 18 17 16 15 14 13 12; do
|
||||
if try_python_candidate "python3.$minor"; then
|
||||
return 0
|
||||
fi
|
||||
@@ -227,20 +228,20 @@ find_uv_python() {
|
||||
python_install_hint() {
|
||||
case "$OS_NAME" in
|
||||
macOS)
|
||||
echo "脚本已尝试自动安装 Git、curl 和 Python 3.11+。" >&2
|
||||
echo "如果自动安装失败,请先安装 Homebrew,或手动执行:brew install git curl python@3.11" >&2
|
||||
echo "脚本已尝试自动安装 Git、curl 和 Python 3.12+。" >&2
|
||||
echo "如果自动安装失败,请先安装 Homebrew,或手动执行:brew install git curl python@3.12" >&2
|
||||
;;
|
||||
Linux*)
|
||||
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
|
||||
echo "脚本已尝试自动安装 Git、curl 和 Python 3.12+。" >&2
|
||||
echo "如果自动安装失败,请先安装 Git、curl、Python 3.12+。" >&2
|
||||
echo "例如 Debian/Ubuntu: sudo apt install git curl python3.12" >&2
|
||||
echo "例如 Fedora/RHEL: sudo dnf install git curl python3.12" >&2
|
||||
;;
|
||||
Windows)
|
||||
echo "推荐在 WSL、Linux 或 macOS 终端中运行此脚本。" >&2
|
||||
;;
|
||||
*)
|
||||
echo "请先安装 Git、curl、Python 3.11 或更高版本。" >&2
|
||||
echo "请先安装 Git、curl、Python 3.12+。" >&2
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@@ -384,17 +385,19 @@ ensure_base_tools() {
|
||||
}
|
||||
|
||||
ensure_uv() {
|
||||
if command -v uv >/dev/null 2>&1; then
|
||||
if command -v uv >/dev/null 2>&1 \
|
||||
&& [[ "$(uv --version 2>/dev/null | awk '{print $2}')" == "${UV_VERSION}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "==> 自动安装 uv,用于补齐 Python 3.11+ 运行时"
|
||||
env UV_INSTALL_DIR="$HOME/.local/bin" sh -c "$(curl -LsSf https://astral.sh/uv/install.sh)"
|
||||
echo "==> 自动安装 uv ${UV_VERSION}"
|
||||
env UV_INSTALL_DIR="$HOME/.local/bin" sh -c "$(curl -LsSf "https://astral.sh/uv/${UV_VERSION}/install.sh")"
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
hash -r
|
||||
|
||||
if ! command -v uv >/dev/null 2>&1; then
|
||||
echo "uv 安装失败,无法继续自动安装 Python。" >&2
|
||||
if ! command -v uv >/dev/null 2>&1 \
|
||||
|| [[ "$(uv --version 2>/dev/null | awk '{print $2}')" != "${UV_VERSION}" ]]; then
|
||||
echo "uv ${UV_VERSION} 安装失败,无法继续自动安装 Python。" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
@@ -412,11 +415,11 @@ ensure_python() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "==> 未找到可用的 Python 3.11+,开始自动安装独立 Python 运行时"
|
||||
uv python install 3.11
|
||||
echo "==> 未找到可用的 Python 3.12+,开始自动安装 Python 3.12"
|
||||
uv python install 3.12
|
||||
PYTHON_BIN="$(find_uv_python "$(command -v uv)" || true)"
|
||||
if [[ -z "$PYTHON_BIN" ]] || ! python_version_ok "$PYTHON_BIN"; then
|
||||
echo "自动安装 Python 3.11+ 失败。" >&2
|
||||
echo "自动安装 Python 3.12 失败。" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -5,22 +5,22 @@ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "${TMP_DIR}"' EXIT
|
||||
|
||||
mkdir -p "${TMP_DIR}/venv/bin" "${TMP_DIR}/config"
|
||||
mkdir -p "${TMP_DIR}/bin" "${TMP_DIR}/venv/bin" "${TMP_DIR}/config"
|
||||
|
||||
cat > "${TMP_DIR}/venv/bin/pip" <<'SH'
|
||||
cat > "${TMP_DIR}/bin/uv" <<'SH'
|
||||
#!/usr/bin/env bash
|
||||
printf 'argv=%s\n' "$*" >> "${MP_FAKE_PIP_LOG}"
|
||||
printf 'HTTP_PROXY=%s\n' "${HTTP_PROXY:-}" >> "${MP_FAKE_PIP_LOG}"
|
||||
printf 'HTTPS_PROXY=%s\n' "${HTTPS_PROXY:-}" >> "${MP_FAKE_PIP_LOG}"
|
||||
printf 'PACKAGE_CACHE_ROOT=%s\n' "${PACKAGE_CACHE_ROOT:-}" >> "${MP_FAKE_PIP_LOG}"
|
||||
printf 'PIP_CACHE_DIR=%s\n' "${PIP_CACHE_DIR:-}" >> "${MP_FAKE_PIP_LOG}"
|
||||
printf 'UV_CACHE_DIR=%s\n' "${UV_CACHE_DIR:-}" >> "${MP_FAKE_PIP_LOG}"
|
||||
if [ "${MP_FAKE_PIP_FAIL:-}" = "1" ]; then
|
||||
printf 'argv=%s\n' "$*" >> "${MP_FAKE_UV_LOG}"
|
||||
printf 'HTTP_PROXY=%s\n' "${HTTP_PROXY:-}" >> "${MP_FAKE_UV_LOG}"
|
||||
printf 'HTTPS_PROXY=%s\n' "${HTTPS_PROXY:-}" >> "${MP_FAKE_UV_LOG}"
|
||||
printf 'PACKAGE_CACHE_ROOT=%s\n' "${PACKAGE_CACHE_ROOT:-}" >> "${MP_FAKE_UV_LOG}"
|
||||
printf 'UV_CACHE_DIR=%s\n' "${UV_CACHE_DIR:-}" >> "${MP_FAKE_UV_LOG}"
|
||||
printf 'UV_PROJECT_ENVIRONMENT=%s\n' "${UV_PROJECT_ENVIRONMENT:-}" >> "${MP_FAKE_UV_LOG}"
|
||||
if [ "${MP_FAKE_UV_FAIL:-}" = "1" ]; then
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
SH
|
||||
chmod +x "${TMP_DIR}/venv/bin/pip"
|
||||
chmod +x "${TMP_DIR}/bin/uv"
|
||||
|
||||
assert_contains() {
|
||||
local needle="$1"
|
||||
@@ -42,52 +42,50 @@ assert_not_contains() {
|
||||
fi
|
||||
}
|
||||
|
||||
UPDATE_FUNCS="${TMP_DIR}/update-functions.sh"
|
||||
awk '
|
||||
BEGIN {capture=1}
|
||||
/^if \[\[ "\$\{MOVIEPILOT_AUTO_UPDATE\}"/ {capture=0}
|
||||
capture {print}
|
||||
' "${ROOT}/docker/update.sh" > "${UPDATE_FUNCS}"
|
||||
# macOS 默认不提供 GNU timeout;模拟器只需保留被执行命令的参数和环境。
|
||||
timeout() {
|
||||
if [[ "${1:-}" == --kill-after=* ]]; then
|
||||
shift
|
||||
fi
|
||||
shift
|
||||
"$@"
|
||||
}
|
||||
|
||||
MP_FAKE_PIP_LOG="${TMP_DIR}/update.log"
|
||||
export MP_FAKE_PIP_LOG
|
||||
MP_FAKE_UV_LOG="${TMP_DIR}/update.log"
|
||||
export MP_FAKE_UV_LOG
|
||||
export UV_BIN="${TMP_DIR}/bin/uv"
|
||||
export VENV_PATH="${TMP_DIR}/venv"
|
||||
export CONFIG_DIR="${TMP_DIR}/config"
|
||||
export MOVIEPILOT_AUTO_UPDATE=false
|
||||
export PIP_PROXY="https://mirror.example/simple"
|
||||
export PROXY_HOST="http://proxy.example:7890"
|
||||
unset PACKAGE_CACHE_ROOT PIP_CACHE_DIR UV_CACHE_DIR HTTP_PROXY HTTPS_PROXY http_proxy https_proxy
|
||||
source "${UPDATE_FUNCS}" >/dev/null
|
||||
unset PACKAGE_CACHE_ROOT UV_CACHE_DIR HTTP_PROXY HTTPS_PROXY http_proxy https_proxy
|
||||
source "${ROOT}/docker/update.sh" >/dev/null
|
||||
|
||||
: > "${MP_FAKE_PIP_LOG}"
|
||||
test_connectivity_pip 0
|
||||
assert_contains "argv=install -i https://mirror.example/simple pip-hello-world" "${MP_FAKE_PIP_LOG}"
|
||||
assert_contains "HTTPS_PROXY=http://proxy.example:7890" "${MP_FAKE_PIP_LOG}"
|
||||
assert_contains "PACKAGE_CACHE_ROOT=${TMP_DIR}/config/.cache" "${MP_FAKE_PIP_LOG}"
|
||||
assert_contains "PIP_CACHE_DIR=${TMP_DIR}/config/.cache/pip" "${MP_FAKE_PIP_LOG}"
|
||||
assert_contains "UV_CACHE_DIR=${TMP_DIR}/config/.cache/uv" "${MP_FAKE_PIP_LOG}"
|
||||
if [[ "${PIP_OPTIONS}" != "-i ${PIP_PROXY}" ]]; then
|
||||
echo "mirror branch must preserve index option: ${PIP_OPTIONS}" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${PIP_OPTIONS}" == *"--proxy"* ]]; then
|
||||
echo "PIP_OPTIONS must not contain --proxy: ${PIP_OPTIONS}" >&2
|
||||
: > "${MP_FAKE_UV_LOG}"
|
||||
test_connectivity_package 0
|
||||
assert_contains "argv=pip install --target " "${MP_FAKE_UV_LOG}"
|
||||
assert_contains "--no-deps --default-index https://mirror.example/simple pip-hello-world" "${MP_FAKE_UV_LOG}"
|
||||
assert_contains "HTTPS_PROXY=http://proxy.example:7890" "${MP_FAKE_UV_LOG}"
|
||||
assert_contains "PACKAGE_CACHE_ROOT=${TMP_DIR}/config/.cache" "${MP_FAKE_UV_LOG}"
|
||||
assert_contains "UV_CACHE_DIR=${TMP_DIR}/config/.cache/uv" "${MP_FAKE_UV_LOG}"
|
||||
if [[ "${UV_OPTIONS[*]}" != "--default-index ${PIP_PROXY}" ]]; then
|
||||
echo "mirror branch must preserve uv index option: ${UV_OPTIONS[*]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -n "${HTTP_PROXY:-}" || -n "${HTTPS_PROXY:-}" || -n "${http_proxy:-}" || -n "${https_proxy:-}" ]]; then
|
||||
echo "pip connectivity must not leak PROXY_HOST into parent proxy env" >&2
|
||||
echo "package connectivity must not leak PROXY_HOST into parent proxy env" >&2
|
||||
env | grep -E '^(HTTP_PROXY|HTTPS_PROXY|http_proxy|https_proxy)=' >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
assert_not_contains "user:pass" "${MP_FAKE_PIP_LOG}"
|
||||
|
||||
: > "${MP_FAKE_PIP_LOG}"
|
||||
: > "${MP_FAKE_UV_LOG}"
|
||||
PIP_PROXY=""
|
||||
test_connectivity_pip 1
|
||||
assert_contains "argv=install pip-hello-world" "${MP_FAKE_PIP_LOG}"
|
||||
assert_contains "HTTPS_PROXY=http://proxy.example:7890" "${MP_FAKE_PIP_LOG}"
|
||||
if [[ -n "${PIP_OPTIONS}" ]]; then
|
||||
echo "proxy branch must keep PIP_OPTIONS empty: ${PIP_OPTIONS}" >&2
|
||||
test_connectivity_package 1
|
||||
assert_contains "argv=pip install --target " "${MP_FAKE_UV_LOG}"
|
||||
assert_contains "HTTPS_PROXY=http://proxy.example:7890" "${MP_FAKE_UV_LOG}"
|
||||
if [[ ${#UV_OPTIONS[@]} -ne 0 ]]; then
|
||||
echo "proxy branch must keep UV_OPTIONS empty: ${UV_OPTIONS[*]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -n "${HTTP_PROXY:-}" || -n "${HTTPS_PROXY:-}" || -n "${http_proxy:-}" || -n "${https_proxy:-}" ]]; then
|
||||
@@ -96,66 +94,54 @@ if [[ -n "${HTTP_PROXY:-}" || -n "${HTTPS_PROXY:-}" || -n "${http_proxy:-}" || -
|
||||
exit 1
|
||||
fi
|
||||
|
||||
MP_FAKE_PIP_LOG="${TMP_DIR}/update-explicit-standard-proxy.log"
|
||||
export MP_FAKE_PIP_LOG
|
||||
MP_FAKE_UV_LOG="${TMP_DIR}/update-explicit-standard-proxy.log"
|
||||
export MP_FAKE_UV_LOG
|
||||
(
|
||||
export VENV_PATH="${TMP_DIR}/venv"
|
||||
export CONFIG_DIR="${TMP_DIR}/config"
|
||||
export MOVIEPILOT_AUTO_UPDATE=false
|
||||
export PIP_PROXY=""
|
||||
export PROXY_HOST="http://proxy.example:7890"
|
||||
export HTTP_PROXY="http://explicit.example:8080"
|
||||
export HTTPS_PROXY="http://explicit.example:8080"
|
||||
export http_proxy="http://explicit.example:8080"
|
||||
export https_proxy="http://explicit.example:8080"
|
||||
source "${UPDATE_FUNCS}" >/dev/null
|
||||
test_connectivity_pip 1
|
||||
source "${ROOT}/docker/update.sh" >/dev/null
|
||||
test_connectivity_package 1
|
||||
if [[ "${HTTP_PROXY}" != "http://explicit.example:8080" || "${HTTPS_PROXY}" != "http://explicit.example:8080" ]]; then
|
||||
echo "explicit standard proxy env must be preserved" >&2
|
||||
env | grep -E '^(HTTP_PROXY|HTTPS_PROXY|http_proxy|https_proxy)=' >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
)
|
||||
assert_contains "HTTPS_PROXY=http://proxy.example:7890" "${MP_FAKE_PIP_LOG}"
|
||||
assert_contains "HTTPS_PROXY=http://proxy.example:7890" "${MP_FAKE_UV_LOG}"
|
||||
|
||||
MP_FAKE_PIP_LOG="${TMP_DIR}/update-explicit-cache.log"
|
||||
export MP_FAKE_PIP_LOG
|
||||
MP_FAKE_UV_LOG="${TMP_DIR}/update-explicit-cache.log"
|
||||
export MP_FAKE_UV_LOG
|
||||
(
|
||||
export VENV_PATH="${TMP_DIR}/venv"
|
||||
export CONFIG_DIR="${TMP_DIR}/config"
|
||||
export MOVIEPILOT_AUTO_UPDATE=false
|
||||
export PACKAGE_CACHE_ROOT="${TMP_DIR}/update-custom-package-cache"
|
||||
export PIP_CACHE_DIR="${TMP_DIR}/explicit-pip-cache"
|
||||
export UV_CACHE_DIR="${TMP_DIR}/explicit-uv-cache"
|
||||
export PIP_PROXY="https://mirror.example/simple"
|
||||
export PROXY_HOST="http://proxy.example:7890"
|
||||
source "${UPDATE_FUNCS}" >/dev/null
|
||||
test_connectivity_pip 0
|
||||
source "${ROOT}/docker/update.sh" >/dev/null
|
||||
test_connectivity_package 0
|
||||
)
|
||||
assert_contains "PACKAGE_CACHE_ROOT=${TMP_DIR}/update-custom-package-cache" "${MP_FAKE_UV_LOG}"
|
||||
assert_contains "UV_CACHE_DIR=${TMP_DIR}/explicit-uv-cache" "${MP_FAKE_UV_LOG}"
|
||||
|
||||
assert_contains "PACKAGE_CACHE_ROOT=${TMP_DIR}/update-custom-package-cache" "${MP_FAKE_PIP_LOG}"
|
||||
assert_contains "PIP_CACHE_DIR=${TMP_DIR}/explicit-pip-cache" "${MP_FAKE_PIP_LOG}"
|
||||
assert_contains "UV_CACHE_DIR=${TMP_DIR}/explicit-uv-cache" "${MP_FAKE_PIP_LOG}"
|
||||
|
||||
MP_FAKE_PIP_LOG="${TMP_DIR}/update-fallback-no-proxy.log"
|
||||
export MP_FAKE_PIP_LOG
|
||||
MP_FAKE_UV_LOG="${TMP_DIR}/update-fallback-no-proxy.log"
|
||||
export MP_FAKE_UV_LOG
|
||||
(
|
||||
export VENV_PATH="${TMP_DIR}/venv"
|
||||
export CONFIG_DIR="${TMP_DIR}/config"
|
||||
export MOVIEPILOT_AUTO_UPDATE=false
|
||||
export PIP_PROXY="https://mirror.example/simple"
|
||||
export PROXY_HOST="http://proxy.example:7890"
|
||||
unset PACKAGE_CACHE_ROOT PIP_CACHE_DIR UV_CACHE_DIR HTTP_PROXY HTTPS_PROXY http_proxy https_proxy
|
||||
source "${UPDATE_FUNCS}" >/dev/null
|
||||
MP_FAKE_PIP_FAIL=1 test_connectivity_pip 0 && exit 1
|
||||
if [[ -n "${HTTPS_PROXY:-}" || -n "${https_proxy:-}" ]]; then
|
||||
echo "mirror failure must not leak proxy env" >&2
|
||||
env | grep -E '^(HTTP_PROXY|HTTPS_PROXY|http_proxy|https_proxy)=' >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
test_connectivity_pip 2
|
||||
if [[ "${PIP_LOG}" != "不使用代理" ]]; then
|
||||
echo "fallback branch must report direct mode: ${PIP_LOG}" >&2
|
||||
unset PACKAGE_CACHE_ROOT UV_CACHE_DIR HTTP_PROXY HTTPS_PROXY http_proxy https_proxy
|
||||
source "${ROOT}/docker/update.sh" >/dev/null
|
||||
MP_FAKE_UV_FAIL=1 test_connectivity_package 0 && exit 1
|
||||
test_connectivity_package 2
|
||||
if [[ "${PACKAGE_LOG}" != "不使用代理" ]]; then
|
||||
echo "fallback branch must report direct mode: ${PACKAGE_LOG}" >&2
|
||||
exit 1
|
||||
fi
|
||||
)
|
||||
@@ -183,13 +169,13 @@ exit 0
|
||||
SH
|
||||
chmod +x "${TMP_DIR}/venv/bin/python3"
|
||||
|
||||
MP_FAKE_PIP_LOG="${TMP_DIR}/entrypoint.log"
|
||||
MP_FAKE_UV_LOG="${TMP_DIR}/entrypoint.log"
|
||||
MP_FAKE_PYTHON_COUNT="${TMP_DIR}/python-count"
|
||||
export MP_FAKE_PIP_LOG MP_FAKE_PYTHON_COUNT
|
||||
export MP_FAKE_UV_LOG MP_FAKE_PYTHON_COUNT
|
||||
(
|
||||
export VENV_PATH="${TMP_DIR}/venv"
|
||||
export CONFIG_DIR="${TMP_DIR}/config"
|
||||
unset PACKAGE_CACHE_ROOT PIP_CACHE_DIR UV_CACHE_DIR HTTP_PROXY HTTPS_PROXY http_proxy https_proxy
|
||||
unset PACKAGE_CACHE_ROOT UV_CACHE_DIR HTTP_PROXY HTTPS_PROXY http_proxy https_proxy
|
||||
export PIP_PROXY=""
|
||||
export PROXY_HOST="http://proxy.example:7890"
|
||||
source "${ENTRYPOINT_FUNCS}"
|
||||
@@ -197,25 +183,24 @@ export MP_FAKE_PIP_LOG MP_FAKE_PYTHON_COUNT
|
||||
ensure_backend_runtime_dependencies
|
||||
if [[ -n "${HTTP_PROXY:-}" || -n "${HTTPS_PROXY:-}" || -n "${http_proxy:-}" || -n "${https_proxy:-}" ]]; then
|
||||
echo "dependency recovery must not leak PROXY_HOST into parent proxy env" >&2
|
||||
env | grep -E '^(HTTP_PROXY|HTTPS_PROXY|http_proxy|https_proxy)=' >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
) >/dev/null
|
||||
|
||||
assert_contains "argv=install -r /app/requirements.txt" "${MP_FAKE_PIP_LOG}"
|
||||
assert_contains "HTTPS_PROXY=http://proxy.example:7890" "${MP_FAKE_PIP_LOG}"
|
||||
assert_contains "PACKAGE_CACHE_ROOT=${TMP_DIR}/config/.cache" "${MP_FAKE_PIP_LOG}"
|
||||
assert_contains "PIP_CACHE_DIR=${TMP_DIR}/config/.cache/pip" "${MP_FAKE_PIP_LOG}"
|
||||
assert_contains "UV_CACHE_DIR=${TMP_DIR}/config/.cache/uv" "${MP_FAKE_PIP_LOG}"
|
||||
assert_not_contains "--proxy" "${MP_FAKE_PIP_LOG}"
|
||||
assert_contains "argv=sync --project /app --locked --no-dev --no-install-project --inexact" "${MP_FAKE_UV_LOG}"
|
||||
assert_contains "HTTPS_PROXY=http://proxy.example:7890" "${MP_FAKE_UV_LOG}"
|
||||
assert_contains "PACKAGE_CACHE_ROOT=${TMP_DIR}/config/.cache" "${MP_FAKE_UV_LOG}"
|
||||
assert_contains "UV_CACHE_DIR=${TMP_DIR}/config/.cache/uv" "${MP_FAKE_UV_LOG}"
|
||||
assert_contains "UV_PROJECT_ENVIRONMENT=${TMP_DIR}/venv" "${MP_FAKE_UV_LOG}"
|
||||
assert_not_contains "requirements" "${MP_FAKE_UV_LOG}"
|
||||
|
||||
MP_FAKE_PIP_LOG="${TMP_DIR}/entrypoint-explicit-standard-proxy.log"
|
||||
MP_FAKE_UV_LOG="${TMP_DIR}/entrypoint-explicit-standard-proxy.log"
|
||||
MP_FAKE_PYTHON_COUNT="${TMP_DIR}/python-count-explicit-standard-proxy"
|
||||
export MP_FAKE_PIP_LOG MP_FAKE_PYTHON_COUNT
|
||||
export MP_FAKE_UV_LOG MP_FAKE_PYTHON_COUNT
|
||||
(
|
||||
export VENV_PATH="${TMP_DIR}/venv"
|
||||
export CONFIG_DIR="${TMP_DIR}/config"
|
||||
unset PACKAGE_CACHE_ROOT PIP_CACHE_DIR UV_CACHE_DIR
|
||||
unset PACKAGE_CACHE_ROOT UV_CACHE_DIR
|
||||
export PIP_PROXY=""
|
||||
export PROXY_HOST="http://proxy.example:7890"
|
||||
export HTTP_PROXY="http://explicit.example:8080"
|
||||
@@ -227,32 +212,28 @@ export MP_FAKE_PIP_LOG MP_FAKE_PYTHON_COUNT
|
||||
ensure_backend_runtime_dependencies
|
||||
if [[ "${HTTP_PROXY}" != "http://explicit.example:8080" || "${HTTPS_PROXY}" != "http://explicit.example:8080" ]]; then
|
||||
echo "dependency recovery must preserve explicit standard proxy env" >&2
|
||||
env | grep -E '^(HTTP_PROXY|HTTPS_PROXY|http_proxy|https_proxy)=' >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
) >/dev/null
|
||||
assert_contains "HTTPS_PROXY=http://proxy.example:7890" "${MP_FAKE_UV_LOG}"
|
||||
|
||||
assert_contains "HTTPS_PROXY=http://proxy.example:7890" "${MP_FAKE_PIP_LOG}"
|
||||
|
||||
MP_FAKE_PIP_LOG="${TMP_DIR}/entrypoint-app-env.log"
|
||||
MP_FAKE_UV_LOG="${TMP_DIR}/entrypoint-app-env.log"
|
||||
MP_FAKE_PYTHON_COUNT="${TMP_DIR}/python-count-app-env"
|
||||
cat > "${TMP_DIR}/config/app.env" <<EOF
|
||||
PACKAGE_CACHE_ROOT='${TMP_DIR}/app-env-custom-package-cache'
|
||||
PROXY_HOST='http://proxy.example:7890'
|
||||
EOF
|
||||
export MP_FAKE_PIP_LOG MP_FAKE_PYTHON_COUNT
|
||||
export MP_FAKE_UV_LOG MP_FAKE_PYTHON_COUNT
|
||||
(
|
||||
export VENV_PATH="${TMP_DIR}/venv"
|
||||
export CONFIG_DIR="${TMP_DIR}/config"
|
||||
unset PACKAGE_CACHE_ROOT PIP_CACHE_DIR UV_CACHE_DIR PIP_PROXY PROXY_HOST
|
||||
unset PACKAGE_CACHE_ROOT UV_CACHE_DIR PIP_PROXY PROXY_HOST
|
||||
source "${ENTRYPOINT_FUNCS}"
|
||||
load_config_from_app_env
|
||||
apply_package_cache_env
|
||||
ensure_backend_runtime_dependencies
|
||||
) >/dev/null
|
||||
|
||||
assert_contains "PACKAGE_CACHE_ROOT=${TMP_DIR}/app-env-custom-package-cache" "${MP_FAKE_PIP_LOG}"
|
||||
assert_contains "PIP_CACHE_DIR=${TMP_DIR}/app-env-custom-package-cache/pip" "${MP_FAKE_PIP_LOG}"
|
||||
assert_contains "UV_CACHE_DIR=${TMP_DIR}/app-env-custom-package-cache/uv" "${MP_FAKE_PIP_LOG}"
|
||||
assert_contains "PACKAGE_CACHE_ROOT=${TMP_DIR}/app-env-custom-package-cache" "${MP_FAKE_UV_LOG}"
|
||||
assert_contains "UV_CACHE_DIR=${TMP_DIR}/app-env-custom-package-cache/uv" "${MP_FAKE_UV_LOG}"
|
||||
|
||||
echo "Docker package env simulation passed"
|
||||
|
||||
@@ -21,7 +21,6 @@ def sample(name: str, request: PackageInstallRequest) -> None:
|
||||
print(rendered)
|
||||
assert all("--proxy" not in arg for arg in strategy.command)
|
||||
assert "user:pass" not in rendered
|
||||
assert strategy.env["PIP_CACHE_DIR"].endswith("/.cache/pip")
|
||||
assert strategy.env["UV_CACHE_DIR"].endswith("/.cache/uv")
|
||||
if strategy.strategy_name.endswith("代理") or strategy.strategy_name.endswith("镜像+代理"):
|
||||
assert strategy.env["HTTPS_PROXY"] == "http://proxy.example:7890"
|
||||
@@ -35,31 +34,31 @@ def main() -> None:
|
||||
|
||||
samples = {
|
||||
"plain": PackageInstallRequest(
|
||||
requirements_file=requirements,
|
||||
dependency_file=requirements,
|
||||
python_bin=python_bin,
|
||||
config_dir=config_dir,
|
||||
),
|
||||
"mirror": PackageInstallRequest(
|
||||
requirements_file=requirements,
|
||||
dependency_file=requirements,
|
||||
python_bin=python_bin,
|
||||
config_dir=config_dir,
|
||||
pip_index_url="https://user:pass@mirror.example/simple",
|
||||
package_index_url="https://user:pass@mirror.example/simple",
|
||||
),
|
||||
"proxy": PackageInstallRequest(
|
||||
requirements_file=requirements,
|
||||
dependency_file=requirements,
|
||||
python_bin=python_bin,
|
||||
config_dir=config_dir,
|
||||
proxy_url="http://proxy.example:7890",
|
||||
),
|
||||
"mirror_proxy_wheels": PackageInstallRequest(
|
||||
requirements_file=requirements,
|
||||
dependency_file=requirements,
|
||||
python_bin=python_bin,
|
||||
config_dir=config_dir,
|
||||
find_links_dirs=[
|
||||
root / "plugins.v2" / "demo" / "wheels",
|
||||
root / "plugins.v2" / "other" / "wheels",
|
||||
],
|
||||
pip_index_url="https://user:pass@mirror.example/simple",
|
||||
package_index_url="https://user:pass@mirror.example/simple",
|
||||
proxy_url="http://proxy.example:7890",
|
||||
),
|
||||
}
|
||||
|
||||
+90
-89
@@ -33,10 +33,9 @@ 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]} 或更高版本"
|
||||
)
|
||||
MIN_PYTHON_VERSION = (3, 12)
|
||||
SUPPORTED_PYTHON_TEXT = "Python 3.12+"
|
||||
UV_VERSION = "0.12.5"
|
||||
|
||||
CONFIG_DIR = LEGACY_CONFIG_DIR
|
||||
LOG_DIR = CONFIG_DIR / "logs"
|
||||
@@ -525,12 +524,10 @@ def build_package_install_env() -> dict[str, str]:
|
||||
env = os.environ.copy()
|
||||
package_cache_root = env.get("PACKAGE_CACHE_ROOT", "").strip() or str(CONFIG_DIR / ".cache")
|
||||
env.setdefault("PACKAGE_CACHE_ROOT", package_cache_root)
|
||||
env.setdefault("PIP_CACHE_DIR", os.path.join(package_cache_root, "pip"))
|
||||
env.setdefault("UV_CACHE_DIR", os.path.join(package_cache_root, "uv"))
|
||||
|
||||
index_url = env.get("PIP_PROXY", "").strip()
|
||||
if index_url:
|
||||
env["PIP_INDEX_URL"] = index_url
|
||||
env["UV_DEFAULT_INDEX"] = index_url
|
||||
|
||||
proxy = env.get("PROXY_HOST", "").strip()
|
||||
@@ -620,67 +617,36 @@ def get_venv_bin_dir(venv_dir: Path) -> Path:
|
||||
return venv_dir / "bin"
|
||||
|
||||
|
||||
def get_venv_pip(venv_dir: Path) -> Path:
|
||||
if os.name == "nt":
|
||||
return get_venv_bin_dir(venv_dir) / "pip.exe"
|
||||
return get_venv_bin_dir(venv_dir) / "pip"
|
||||
def require_uv() -> Path:
|
||||
"""返回仓库要求版本的 uv,避免不同安装入口使用不同解析器。"""
|
||||
uv_command = shutil.which("uv")
|
||||
if not uv_command:
|
||||
raise RuntimeError(
|
||||
f"未找到 uv {UV_VERSION},请先安装后重新执行。"
|
||||
)
|
||||
uv_bin = Path(uv_command).expanduser().resolve()
|
||||
version = capture([str(uv_bin), "--version"])
|
||||
if version.split()[:2] != ["uv", UV_VERSION]:
|
||||
raise RuntimeError(
|
||||
f"MoviePilot 需要 uv {UV_VERSION},当前为 {version or '未知版本'}。"
|
||||
)
|
||||
return uv_bin
|
||||
|
||||
|
||||
def _ensure_uv_available_for_venv(venv_dir: Path, venv_python: Path) -> Optional[Path]:
|
||||
if os.name == "nt":
|
||||
return None
|
||||
|
||||
def expose_uv_to_venv(uv_bin: Path, venv_dir: Path) -> Path:
|
||||
"""让运行时能从虚拟环境旁定位同一 uv 二进制。"""
|
||||
venv_bin = get_venv_bin_dir(venv_dir)
|
||||
uv_bin = venv_bin / "uv"
|
||||
if uv_bin.exists():
|
||||
return uv_bin
|
||||
|
||||
system_uv = shutil.which("uv")
|
||||
if system_uv:
|
||||
uv_target = Path(system_uv).expanduser().resolve()
|
||||
print_step(f"复用系统 uv:{uv_target}")
|
||||
if uv_bin.exists() or uv_bin.is_symlink():
|
||||
uv_bin.unlink()
|
||||
uv_bin.symlink_to(uv_target)
|
||||
return uv_bin
|
||||
|
||||
print_step("当前未检测到 uv,先在虚拟环境内安装 uv")
|
||||
command = [str(venv_python), "-m", "pip", "install", "--upgrade", "pip", "uv"]
|
||||
run(command, env=build_package_install_env(), safe_command=redact_command(command))
|
||||
if uv_bin.exists():
|
||||
return uv_bin
|
||||
raise RuntimeError("uv 安装完成,但虚拟环境中未找到 uv 可执行文件")
|
||||
|
||||
|
||||
def configure_venv_pip_compat(venv_dir: Path, venv_python: Path) -> Path:
|
||||
"""
|
||||
在虚拟环境中安装 uv 并保持 pip 命令兼容,供现有安装流程复用。
|
||||
"""
|
||||
runtime_uv = venv_bin / ("uv.exe" if os.name == "nt" else "uv")
|
||||
venv_bin.mkdir(parents=True, exist_ok=True)
|
||||
if runtime_uv.resolve() == uv_bin.resolve():
|
||||
return runtime_uv
|
||||
if runtime_uv.exists() or runtime_uv.is_symlink():
|
||||
runtime_uv.unlink()
|
||||
if os.name == "nt":
|
||||
return get_venv_pip(venv_dir)
|
||||
|
||||
_ensure_uv_available_for_venv(venv_dir, venv_python)
|
||||
venv_bin = get_venv_bin_dir(venv_dir)
|
||||
wrapper_src = ROOT / "scripts" / "uv-pip-compat.sh"
|
||||
wrapper_dst = venv_bin / "uv-pip-compat"
|
||||
shutil.copy2(wrapper_src, wrapper_dst)
|
||||
wrapper_dst.chmod(0o755)
|
||||
|
||||
python_version = get_python_version(str(venv_python))
|
||||
compat_links = {
|
||||
"pip",
|
||||
"pip3",
|
||||
f"pip{python_version[0]}",
|
||||
f"pip{python_version[0]}.{python_version[1]}",
|
||||
"pip-compile",
|
||||
"pip-sync",
|
||||
}
|
||||
for link_name in compat_links:
|
||||
link_path = venv_bin / link_name
|
||||
if link_path.exists() or link_path.is_symlink():
|
||||
link_path.unlink()
|
||||
link_path.symlink_to(wrapper_dst.name)
|
||||
return get_venv_pip(venv_dir)
|
||||
shutil.copy2(uv_bin, runtime_uv)
|
||||
else:
|
||||
runtime_uv.symlink_to(uv_bin)
|
||||
return runtime_uv
|
||||
|
||||
|
||||
def ensure_supported_python(python_bin: str) -> None:
|
||||
@@ -692,6 +658,12 @@ def ensure_supported_python(python_bin: str) -> None:
|
||||
)
|
||||
|
||||
|
||||
def resolve_python_path(python_bin: str) -> Path:
|
||||
"""解析解释器命令,优先按 PATH 找到实际执行文件。"""
|
||||
resolved = shutil.which(python_bin)
|
||||
return Path(resolved or python_bin).expanduser().resolve()
|
||||
|
||||
|
||||
def ensure_local_dirs() -> None:
|
||||
for path in (CONFIG_DIR, LOG_DIR, CACHE_DIR, TEMP_DIR, COOKIE_DIR, RUNTIME_DIR):
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
@@ -2743,33 +2715,62 @@ def install_deps(*, python_bin: str, venv_dir: Path, recreate: bool) -> Path:
|
||||
"""
|
||||
ensure_supported_python(python_bin)
|
||||
venv_dir = venv_dir.expanduser().resolve()
|
||||
if recreate:
|
||||
requested_python = resolve_python_path(python_bin)
|
||||
executing_python = Path(sys.executable).expanduser().resolve()
|
||||
if requested_python.is_relative_to(venv_dir) or executing_python.is_relative_to(
|
||||
venv_dir
|
||||
):
|
||||
raise RuntimeError(
|
||||
"重建虚拟环境需要使用 venv 外部的 Python 3.12+ 解释器。"
|
||||
)
|
||||
uv_bin = require_uv()
|
||||
temporary_uv_dir: Optional[TemporaryDirectory[str]] = None
|
||||
if recreate and venv_dir.exists() and uv_bin.is_relative_to(venv_dir):
|
||||
temporary_uv_dir = TemporaryDirectory(prefix="moviepilot-uv-")
|
||||
temporary_uv = Path(temporary_uv_dir.name) / uv_bin.name
|
||||
shutil.copy2(uv_bin, temporary_uv)
|
||||
uv_bin = temporary_uv
|
||||
venv_python = get_venv_python(venv_dir)
|
||||
venv_pip = get_venv_pip(venv_dir)
|
||||
print_step(f"使用 Python 解释器:{python_bin}")
|
||||
try:
|
||||
if recreate and venv_dir.exists():
|
||||
print_step(f"删除已有虚拟环境:{venv_dir}")
|
||||
shutil.rmtree(venv_dir)
|
||||
|
||||
if recreate and venv_dir.exists():
|
||||
print_step(f"删除已有虚拟环境:{venv_dir}")
|
||||
shutil.rmtree(venv_dir)
|
||||
if venv_python.exists():
|
||||
print_step(f"复用已有虚拟环境:{venv_dir}")
|
||||
else:
|
||||
print_step(f"创建虚拟环境:{venv_dir}")
|
||||
|
||||
if not venv_python.exists():
|
||||
print_step(f"创建虚拟环境:{venv_dir}")
|
||||
run([python_bin, "-m", "venv", str(venv_dir)])
|
||||
else:
|
||||
print_step(f"复用已有虚拟环境:{venv_dir}")
|
||||
|
||||
if os.name == "nt":
|
||||
print_step("升级 pip")
|
||||
command = [str(venv_python), "-m", "pip", "install", "--upgrade", "pip"]
|
||||
run(command, env=build_package_install_env(), safe_command=redact_command(command))
|
||||
else:
|
||||
print_step("为虚拟环境配置 uv 兼容 pip 命令")
|
||||
venv_pip = configure_venv_pip_compat(venv_dir, venv_python)
|
||||
|
||||
print_step("安装项目依赖")
|
||||
command = [str(venv_pip), "install", "-r", str(ROOT / "requirements.txt")]
|
||||
run(command, env=build_package_install_env(), safe_command=redact_command(command))
|
||||
install_browser_runtime(venv_python)
|
||||
return venv_python
|
||||
print_step("同步项目锁定依赖")
|
||||
command = [
|
||||
str(uv_bin),
|
||||
"sync",
|
||||
"--project",
|
||||
str(ROOT),
|
||||
"--locked",
|
||||
"--no-dev",
|
||||
"--no-install-project",
|
||||
"--python",
|
||||
python_bin,
|
||||
]
|
||||
env = build_package_install_env()
|
||||
env["UV_PROJECT_ENVIRONMENT"] = str(venv_dir)
|
||||
run(command, env=env, safe_command=redact_command(command))
|
||||
if temporary_uv_dir is not None:
|
||||
runtime_uv = get_venv_bin_dir(venv_dir) / (
|
||||
"uv.exe" if os.name == "nt" else "uv"
|
||||
)
|
||||
runtime_uv.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(uv_bin, runtime_uv)
|
||||
else:
|
||||
expose_uv_to_venv(uv_bin, venv_dir)
|
||||
install_browser_runtime(venv_python)
|
||||
return venv_python
|
||||
finally:
|
||||
if temporary_uv_dir is not None:
|
||||
temporary_uv_dir.cleanup()
|
||||
|
||||
|
||||
def install_browser_runtime(venv_python: Path) -> None:
|
||||
@@ -3718,7 +3719,7 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
install_parser.add_argument(
|
||||
"--python",
|
||||
default=DEFAULT_BOOTSTRAP_PYTHON,
|
||||
help="用于创建虚拟环境的 Python 解释器,默认自动选择本地 3.11+ 版本",
|
||||
help="用于创建虚拟环境的 Python 解释器,默认自动选择本地 3.12+ 版本",
|
||||
)
|
||||
install_parser.add_argument(
|
||||
"--venv", default=str(ROOT / "venv"), help="虚拟环境目录"
|
||||
@@ -3780,7 +3781,7 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
setup_parser.add_argument(
|
||||
"--python",
|
||||
default=DEFAULT_BOOTSTRAP_PYTHON,
|
||||
help="用于创建虚拟环境的 Python 解释器,默认自动选择本地 3.11+ 版本",
|
||||
help="用于创建虚拟环境的 Python 解释器,默认自动选择本地 3.12+ 版本",
|
||||
)
|
||||
setup_parser.add_argument("--venv", default=str(ROOT / "venv"), help="虚拟环境目录")
|
||||
setup_parser.add_argument(
|
||||
@@ -3852,7 +3853,7 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
update_parser.add_argument(
|
||||
"--python",
|
||||
default=DEFAULT_BOOTSTRAP_PYTHON,
|
||||
help="用于安装后端依赖的 Python 解释器,默认自动选择本地 3.11+ 版本",
|
||||
help="用于安装后端依赖的 Python 解释器,默认自动选择本地 3.12+ 版本",
|
||||
)
|
||||
update_parser.add_argument(
|
||||
"--venv", default=str(ROOT / "venv"), help="虚拟环境目录"
|
||||
|
||||
@@ -40,9 +40,9 @@ ROLE_LABEL = "org.moviepilot.perf.role"
|
||||
SOURCE_LABEL = "org.moviepilot.perf.source-commit"
|
||||
SUBSTRATE_LABEL = "org.moviepilot.perf.substrate"
|
||||
CRITICAL_SUBSTRATE_PATHS = (
|
||||
"requirements.in",
|
||||
"pyproject.toml",
|
||||
"uv.lock",
|
||||
"docker/Dockerfile",
|
||||
"scripts/uv-pip-compat.sh",
|
||||
)
|
||||
SEED_COMPATIBILITY_PATHS = ("database/versions",)
|
||||
AGENT_HEAVY_MODULE_PREFIXES = (
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
|
||||
SCRIPT_PATH="$0"
|
||||
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$SCRIPT_PATH")" && pwd)
|
||||
COMMAND_NAME=$(basename -- "$SCRIPT_PATH")
|
||||
|
||||
if [ "${COMMAND_NAME}" = "uv-pip-compat" ] || [ "${COMMAND_NAME}" = "uv-pip-compat.sh" ]; then
|
||||
if [ "$#" -eq 0 ]; then
|
||||
echo "用法: uv-pip-compat <pip|pip-compile|pip-sync> [args...]" >&2
|
||||
exit 2
|
||||
fi
|
||||
COMMAND_NAME="$1"
|
||||
shift
|
||||
fi
|
||||
|
||||
if [ -x "${SCRIPT_DIR}/uv" ]; then
|
||||
UV_BIN="${SCRIPT_DIR}/uv"
|
||||
elif command -v uv >/dev/null 2>&1; then
|
||||
UV_BIN=$(command -v uv)
|
||||
else
|
||||
echo "未找到 uv,可执行 pip 兼容层无法继续运行。" >&2
|
||||
exit 127
|
||||
fi
|
||||
|
||||
has_environment_option() {
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case "$1" in
|
||||
-p|--python|--python=*|-p*|--system|--user|\
|
||||
-t|--target|--target=*|-t*|--prefix|--prefix=*)
|
||||
return 0
|
||||
;;
|
||||
--)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
normalize_pip_proxy_args() {
|
||||
output_file="$1"
|
||||
shift
|
||||
original_args_file=$(mktemp)
|
||||
: > "${output_file}"
|
||||
trap 'rm -f "${original_args_file}"' EXIT HUP INT TERM
|
||||
|
||||
for arg in "$@"; do
|
||||
printf '%s\n' "$arg" >> "${original_args_file}"
|
||||
done
|
||||
|
||||
skip_next=0
|
||||
while IFS= read -r arg; do
|
||||
if [ "${skip_next}" -eq 1 ]; then
|
||||
proxy_value="${arg}"
|
||||
export HTTP_PROXY="${proxy_value}"
|
||||
export HTTPS_PROXY="${proxy_value}"
|
||||
export http_proxy="${proxy_value}"
|
||||
export https_proxy="${proxy_value}"
|
||||
skip_next=0
|
||||
continue
|
||||
fi
|
||||
case "$arg" in
|
||||
--proxy)
|
||||
skip_next=1
|
||||
;;
|
||||
--proxy=*)
|
||||
proxy_value="${arg#--proxy=}"
|
||||
export HTTP_PROXY="${proxy_value}"
|
||||
export HTTPS_PROXY="${proxy_value}"
|
||||
export http_proxy="${proxy_value}"
|
||||
export https_proxy="${proxy_value}"
|
||||
;;
|
||||
*)
|
||||
printf '%s\n' "$arg" >> "${output_file}"
|
||||
;;
|
||||
esac
|
||||
done < "${original_args_file}"
|
||||
|
||||
rm -f "${original_args_file}"
|
||||
trap - EXIT HUP INT TERM
|
||||
}
|
||||
|
||||
uv_pip_with_venv_python() {
|
||||
command_name="$1"
|
||||
shift
|
||||
|
||||
if [ -x "${SCRIPT_DIR}/python" ] && ! has_environment_option "$@"; then
|
||||
# uv 不会仅凭 pip 软链接位置锁定 venv,本地安装也不会激活 venv。
|
||||
# 因此需要在会读取或改写环境的 pip 子命令上显式绑定当前 venv 解释器。
|
||||
exec "${UV_BIN}" pip "${command_name}" --python "${SCRIPT_DIR}/python" "$@"
|
||||
fi
|
||||
exec "${UV_BIN}" pip "${command_name}" "$@"
|
||||
}
|
||||
|
||||
case "${COMMAND_NAME}" in
|
||||
pip|pip3|pip3.*)
|
||||
if [ "$#" -eq 0 ]; then
|
||||
exec "${UV_BIN}" pip --help
|
||||
fi
|
||||
|
||||
case "$1" in
|
||||
-V|--version|version)
|
||||
exec "${UV_BIN}" --version
|
||||
;;
|
||||
help)
|
||||
shift
|
||||
exec "${UV_BIN}" help pip "$@"
|
||||
;;
|
||||
check|freeze|install|list|show|sync|tree|uninstall)
|
||||
pip_command="$1"
|
||||
shift
|
||||
if [ "${pip_command}" = "install" ]; then
|
||||
normalized_file=$(mktemp)
|
||||
normalize_pip_proxy_args "${normalized_file}" "$@"
|
||||
set --
|
||||
while IFS= read -r arg; do
|
||||
set -- "$@" "$arg"
|
||||
done < "${normalized_file}"
|
||||
rm -f "${normalized_file}"
|
||||
fi
|
||||
uv_pip_with_venv_python "${pip_command}" "$@"
|
||||
;;
|
||||
*)
|
||||
exec "${UV_BIN}" pip "$@"
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
pip-compile)
|
||||
exec "${UV_BIN}" pip compile "$@"
|
||||
;;
|
||||
pip-sync)
|
||||
uv_pip_with_venv_python sync "$@"
|
||||
;;
|
||||
*)
|
||||
echo "不支持的 pip 兼容命令入口:${COMMAND_NAME}" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user