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()