Files
glm-coding-helper/scripts/setup_backend.py
T
OLmatter f40eb564ee fix(install): mirror never reached pip - argparse ate -i as a flag
Root cause of 100% install failure: setup_backend.py --pip-arg used action=append,
so '--pip-arg -i' made argparse treat '-i' (a dash-prefixed value) as the next
option and abort with 'error: argument --pip-arg: expected one argument'.
The mirror set by one_click_start.ps1 never reached pip -> mainland users hit
PyPI timeout -> 'Backend environment repair failed'.

Fix:
- bootstrap_windows.ps1: emit '--pip-arg=VALUE' (= form) so dash-prefixed values
  like -i / --index-url are not misparsed as options.
- setup_backend.py: also pass mirror to the 'pip install --upgrade pip setuptools
  wheel' step (line 137 previously had none, would time out before requirements).
- Verified end-to-end: fresh venv, full CPU install via Tsinghua mirror,
  paddleocr-3.7.0 + paddlepaddle-3.3.1 installed, smoke test passed.
2026-06-23 03:21:50 +08:00

154 lines
5.5 KiB
Python

from __future__ import annotations
import argparse
import os
import shutil
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
CPU_VENV = ROOT / ".venv_paddle"
GPU_VENV = ROOT / ".venv_paddle_gpu"
CPU_REQ = ROOT / "requirements-backend-cpu.txt"
GPU_REQ = ROOT / "requirements-backend-gpu.txt"
YOLO_WEIGHT = ROOT / "models" / "weights" / "yolo-captcha-detector.pt"
def venv_python(venv: Path) -> Path:
if os.name == "nt":
return venv / "Scripts" / "python.exe"
return venv / "bin" / "python"
def run(cmd: list[str], *, cwd: Path = ROOT) -> None:
print("+ " + " ".join(cmd), flush=True)
subprocess.run(cmd, cwd=str(cwd), check=True)
def has_nvidia_gpu() -> bool:
nvidia_smi = shutil.which("nvidia-smi")
if not nvidia_smi:
return False
try:
proc = subprocess.run(
[nvidia_smi, "-L"],
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=5,
check=False,
)
except Exception:
return False
return proc.returncode == 0 and "GPU" in proc.stdout
def create_venv(venv: Path, recreate: bool) -> Path:
if recreate and venv.exists():
print(f"Removing existing environment: {venv}", flush=True)
shutil.rmtree(venv)
if not venv.exists():
run([sys.executable, "-m", "venv", str(venv)])
return venv_python(venv)
def install_requirements(py: Path, req: Path, extra_pip_args: list[str]) -> None:
if not req.exists():
raise FileNotFoundError(req)
run([str(py), "-m", "pip", "install", "-r", str(req), *extra_pip_args])
def smoke_test(py: Path, mode: str) -> None:
code = (
"import PIL, cv2, numpy, ultralytics; "
"from paddleocr import TextRecognition; "
"print('core imports ok')"
)
run([str(py), "-c", code])
extra_code = (
"import fastapi, uvicorn, psutil; print('backend deps ok')"
)
run([str(py), "-c", extra_code])
if mode == "gpu":
gpu_code = (
"import paddle; "
"print('cuda_compiled=', paddle.is_compiled_with_cuda()); "
"print('cuda_count=', paddle.device.cuda.device_count() if paddle.is_compiled_with_cuda() else 0)"
)
run([str(py), "-c", gpu_code])
gpu_ocr_code = (
"import os; "
f"os.environ['HOME'] = {str(ROOT / '.paddle_home_gpu')!r}; "
f"os.environ['USERPROFILE'] = {str(ROOT / '.paddle_home_gpu')!r}; "
f"os.environ['PADDLE_HOME'] = {str(ROOT / '.paddle_home_gpu' / '.cache' / 'paddle')!r}; "
f"os.environ['PADDLE_PDX_CACHE_HOME'] = {str(ROOT / '.paddlex_cache_gpu')!r}; "
"os.environ.setdefault('PADDLE_PDX_DISABLE_MODEL_SOURCE_CHECK', 'True'); "
"from paddleocr import TextRecognition; "
"r = TextRecognition(model_name='PP-OCRv5_server_rec', device='gpu:0', engine='paddle_dynamic'); "
"close = getattr(r, 'close', None); "
"close() if callable(close) else None; "
"print('gpu ocr ok')"
)
run([str(py), "-c", gpu_ocr_code])
def check_assets() -> None:
missing: list[Path] = []
if not YOLO_WEIGHT.exists():
missing.append(YOLO_WEIGHT)
if missing:
print("\nMissing model assets:", flush=True)
for path in missing:
print(f" - {path}", flush=True)
print("Place the detector weight at the path above before starting the backend.", flush=True)
else:
print(f"Model assets ok: {YOLO_WEIGHT}", flush=True)
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="Create local backend environments for CNCAPTCHA.")
parser.add_argument("--target", choices=["auto", "cpu", "gpu", "both"], default="auto")
parser.add_argument("--recreate", action="store_true", help="Delete and recreate selected virtualenvs")
parser.add_argument("--skip-install", action="store_true", help="Create venvs but do not install packages")
parser.add_argument("--no-smoke-test", action="store_true", help="Skip import tests after installation")
parser.add_argument(
"--pip-arg",
action="append",
default=[],
help="Extra argument appended to pip install, repeatable. Use the --pip-arg=VALUE form when VALUE starts with a dash (e.g. --pip-arg=-i).",
)
args = parser.parse_args(argv)
target = args.target
if target == "auto":
target = "gpu" if has_nvidia_gpu() else "cpu"
print(f"Auto selected target: {target}", flush=True)
selected = ["cpu", "gpu"] if target == "both" else [target]
for mode in selected:
venv = GPU_VENV if mode == "gpu" else CPU_VENV
req = GPU_REQ if mode == "gpu" else CPU_REQ
print(f"\n=== Setting up {mode.upper()} backend environment ===", flush=True)
py = create_venv(venv, args.recreate)
if not args.skip_install:
run([str(py), "-m", "pip", "install", "--upgrade", "pip", "setuptools", "wheel", *args.pip_arg])
install_requirements(py, req, args.pip_arg)
if not args.no_smoke_test:
smoke_test(py, mode)
check_assets()
print("\nDone.", flush=True)
print("Start GUI backend:", flush=True)
print(" python scripts\\tools\\start_backend.py --mode auto", flush=True)
print("Start headless backend:", flush=True)
print(" python scripts\\tools\\start_backend.py --headless --mode auto", flush=True)
return 0
if __name__ == "__main__":
raise SystemExit(main())