autool/setup.py
2026-06-17 19:44:18 +08:00

245 lines
8.8 KiB
Python

import sys
import subprocess
import venv
import shutil
import argparse
from pathlib import Path
import platform
# 配置常量
VENV_DIR = Path("venv")
system = platform.system().lower()
if system == "windows":
SHARE_PATH = Path(r"\\lfs.tpshos\dpi-sync\autool_config")
elif system == "darwin":
SHARE_PATH = Path(r"/Volumes/share/autool_config")
else:
SHARE_PATH = None # 非 Windows/macOS 系统不使用网络共享
def run_command(cmd):
"""封装子进程调用"""
print(f"Running: {' '.join(map(str, cmd))}")
try:
subprocess.check_call(cmd)
except subprocess.CalledProcessError as e:
print(f"Error executing command: {e}")
sys.exit(1)
def download_file(src_path, dst_path):
"""封装文件下载/拷贝逻辑"""
if src_path.exists():
dst_path.parent.mkdir(parents=True, exist_ok=True)
shutil.copy(src_path, dst_path)
print(f"Downloaded: {src_path.name} to {dst_path}")
else:
print(f"Warning: Source file not found at {src_path}")
def get_venv_python():
"""获取虚拟环境中的 Python 解释器路径 (兼容 Windows/macOS/Linux)"""
if sys.platform == "win32":
return VENV_DIR / "Scripts" / "python.exe"
return VENV_DIR / "bin" / "python"
def parse_args():
"""解析命令行参数"""
parser = argparse.ArgumentParser(
description="Setup development environment for autool",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python setup.py # Install Android dependencies (default)
python setup.py --offline # Install without network share / LFS
python setup.py --android # Install Android dependencies explicitly
python setup.py --ios # Install iOS dependencies
python setup.py --all # Install all platform dependencies
"""
)
platform_group = parser.add_mutually_exclusive_group()
platform_group.add_argument('--android', action='store_true',
help='Install Android dependencies (default)')
platform_group.add_argument('--ios', action='store_true',
help='Install iOS dependencies')
platform_group.add_argument('--web', action='store_true',
help='Install Web dependencies')
platform_group.add_argument('--all', action='store_true',
help='Install all platform dependencies')
parser.add_argument('--offline', action='store_true',
help='Offline mode: skip network share and LFS pull operations')
return parser.parse_args()
def get_requirements_files(args):
"""根据命令行参数返回需要安装的依赖文件列表"""
base_dir = Path("doc")
# 总是安装公共依赖(包含 DroidBot 核心依赖)
files = [base_dir / "requirements-common.txt"]
if args.all:
# 安装所有平台依赖
files.extend([
base_dir / "requirements-android.txt",
base_dir / "requirements-ios.txt",
base_dir / "requirements-web.txt"
])
platform_name = "All platforms"
elif args.ios:
# 仅安装 iOS 依赖
files.append(base_dir / "requirements-ios.txt")
platform_name = "iOS"
elif args.web:
# 仅安装 Web 依赖
files.append(base_dir / "requirements-web.txt")
platform_name = "Web"
else:
# 默认安装 Android 依赖
files.append(base_dir / "requirements-android.txt")
platform_name = "Android"
return files, platform_name
def install_requirements(python_bin, req_files, local_source=None):
"""安装依赖包,带有超时和无限重试机制"""
for req_file in req_files:
if not req_file.exists():
print(f"Warning: {req_file.name} not found, skipping.")
continue
print(f"\nInstalling requirements from {req_file.name}...")
retry_count = 0
while True:
retry_count += 1
print(f"\n[Attempt {retry_count}] Running pip install...")
cmd = [
python_bin, "-m", "pip", "install",
"--timeout", "30",
"--retries", "5",
]
if local_source:
cmd.extend([
"--find-links", str(local_source),
"--only-binary", ":all:"
])
cmd.extend(["-r", req_file])
print(f"Running: {' '.join(map(str, cmd))}")
try:
subprocess.check_call(cmd)
print(f"[SUCCESS] Successfully installed requirements from {req_file.name}")
break
except subprocess.CalledProcessError as e:
print(f"[ERROR] pip install failed: {e}")
print(f"[INFO] Retrying in 3 seconds... (press Ctrl+C to stop)")
import time
time.sleep(3)
def main():
# 解析命令行参数
args = parse_args()
print(f"System: {platform.system()}")
print(f"System Python: {sys.executable}")
print(f"Python Version: {sys.version}")
# 1. 创建虚拟环境
if not VENV_DIR.exists():
print(f"\nCreating virtual environment in {VENV_DIR}...")
venv.EnvBuilder(with_pip=True).create(VENV_DIR)
else:
print("\nVirtual environment already exists.")
python_bin = get_venv_python()
if not python_bin.exists():
print(f"Error: Virtual environment python not found at {python_bin}")
sys.exit(1)
# 2. 升级 pip (带无限重试)
print("\nUpgrading pip...")
retry_count = 0
while True:
retry_count += 1
print(f"\n[Attempt {retry_count}] Upgrading pip...")
cmd = [python_bin, "-m", "pip", "install", "--upgrade", "pip"]
print(f"Running: {' '.join(map(str, cmd))}")
try:
subprocess.check_call(cmd)
print("[SUCCESS] Successfully upgraded pip")
break
except subprocess.CalledProcessError as e:
print(f"[ERROR] pip upgrade failed: {e}")
print(f"[INFO] Retrying in 3 seconds... (press Ctrl+C to stop)")
import time
time.sleep(3)
# 3. 根据平台选择安装对应依赖
req_files, platform_name = get_requirements_files(args)
print(f"\n{'='*60}")
print(f"Installing dependencies for: {platform_name}")
print(f"{'='*60}")
local_source = None
# if platform_name == "Android":
# local_source = SHARE_PATH / "android_packages"
# print(f"Using local package source: {local_source}")
install_requirements(python_bin, req_files, local_source=local_source)
# 4. 从共享文件夹下载配置文件(失败时从示例文件复制)
print(f"\n{'='*60}")
print("Setting up configuration files...")
print(f"{'='*60}")
# 处理 .env 文件
env_file = Path(".env")
env_example = Path(".env.example")
if not env_file.exists():
# 尝试从共享路径下载(非离线模式下使用网络共享)
if not args.offline and SHARE_PATH is not None:
share_env = SHARE_PATH / ".env"
if share_env.exists():
download_file(share_env, env_file)
elif env_example.exists():
print(f"Copying {env_example} to {env_file}...")
shutil.copy(env_example, env_file)
print(f"⚠️ IMPORTANT: Please edit {env_file} and fill in your actual API keys and tokens!")
else:
print(f"⚠️ WARNING: {env_example} not found. Please create {env_file} manually.")
elif env_example.exists():
# 离线模式或无可用的共享路径:从示例文件复制
print(f"Copying {env_example} to {env_file}...")
shutil.copy(env_example, env_file)
print(f"⚠️ IMPORTANT: Please edit {env_file} and fill in your actual API keys and tokens!")
else:
print(f"⚠️ WARNING: {env_example} not found. Please create {env_file} manually.")
else:
print(f"{env_file} already exists")
print(f"\n{'='*60}")
print(f"Environment initialization completed for {platform_name}!")
print(f"{'='*60}")
print(f"\nTo activate the virtual environment:")
if sys.platform == "win32":
print(f" {VENV_DIR}\\Scripts\\activate")
else:
print(f" source {VENV_DIR}/bin/activate")
# 5. 下载lfs文件
if args.offline:
print(f"\n{'='*60}")
print("Offline mode: skipping LFS pull")
print(f"{'='*60}")
else:
print(f"\n{'='*60}")
print("Downloading lfs files from share...")
# 取消本地的 fetchexclude
run_command(["git", "config", "lfs.fetchexclude", ""])
run_command(["git", "lfs", "pull"])
if __name__ == "__main__":
main()