CadHubManage/scripts/build_offline_bundle.py
2025-12-19 17:21:53 +08:00

124 lines
3.3 KiB
Python

"""Offline bundle builder for CadHubManage.
Creates a wheelhouse and pre-built virtual environment for deployment to
air-gapped Windows hosts.
"""
from __future__ import annotations
import argparse
import os
import shutil
import subprocess
import sys
from pathlib import Path
def run(command: list[str], *, cwd: Path | None = None) -> None:
subprocess.check_call(command, cwd=cwd)
def ensure_empty_dir(path: Path) -> None:
if path.exists():
if not path.is_dir():
raise ValueError(f"路径 {path} 已存在且不是目录")
shutil.rmtree(path)
path.mkdir(parents=True, exist_ok=True)
def build_wheelhouse(requirements: Path, wheelhouse: Path, python: str) -> None:
cmd = [
python,
"-m",
"pip",
"download",
"-r",
str(requirements),
"-d",
str(wheelhouse),
]
run(cmd)
def create_virtualenv(project_root: Path, venv_dir: Path, python: str) -> None:
run([python, "-m", "venv", str(venv_dir)])
pip_executable = venv_dir / ("Scripts" if os.name == "nt" else "bin") / "pip"
wheelhouse = project_root / "dist" / "wheelhouse"
cmd = [
str(pip_executable),
"install",
"--no-index",
"--find-links",
str(wheelhouse),
"-r",
str(project_root / "requirements.txt"),
]
run(cmd)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="构建离线部署所需资源")
parser.add_argument(
"--project-root",
default=Path(__file__).resolve().parents[1],
type=Path,
help="项目根目录",
)
parser.add_argument(
"--requirements",
default=None,
type=Path,
help="requirements.txt 路径,默认为项目根目录下的 requirements.txt",
)
parser.add_argument(
"--wheelhouse",
default=None,
type=Path,
help="离线 wheel 包存放目录,默认 dist/wheelhouse",
)
parser.add_argument(
"--venv-dir",
default=None,
type=Path,
help="预构建虚拟环境的输出目录,默认 .offline-venv",
)
parser.add_argument(
"--python",
default=sys.executable,
help="用于构建的 Python 解释器路径",
)
parser.add_argument(
"--skip-wheelhouse",
action="store_true",
help="跳过 wheelhouse 构建,仅重建虚拟环境",
)
parser.add_argument(
"--skip-venv",
action="store_true",
help="跳过虚拟环境构建,仅更新 wheelhouse",
)
return parser.parse_args()
def main() -> None:
args = parse_args()
project_root = args.project_root.resolve()
requirements = (args.requirements or project_root / "requirements.txt").resolve()
if not requirements.exists():
raise FileNotFoundError(f"未找到依赖清单: {requirements}")
wheelhouse = (args.wheelhouse or project_root / "dist" / "wheelhouse").resolve()
venv_dir = (args.venv_dir or project_root / ".offline-venv").resolve()
if not args.skip_wheelhouse:
ensure_empty_dir(wheelhouse)
build_wheelhouse(requirements, wheelhouse, args.python)
if not args.skip_venv:
ensure_empty_dir(venv_dir)
create_virtualenv(project_root, venv_dir, args.python)
if __name__ == "__main__":
main()