repo_manager.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import os
  2. import subprocess
  3. import logging
  4. from pathlib import Path
  5. from typing import Optional, List
  6. logger = logging.getLogger(__name__)
  7. class RepoError(Exception):
  8. """Repo 操作异常"""
  9. pass
  10. def _run(cmd: List[str], cwd: Optional[str] = None) -> None:
  11. """执行命令,失败时抛出异常,同时输出 stdout/stderr 到控制台"""
  12. logger.debug("Running: %s", ' '.join(cmd))
  13. subprocess.run(cmd, cwd=cwd, check=True)
  14. def _repo_init(manifest_url: str, branch: str, repo_url: str,
  15. manifest_file: Optional[str] = None, depth: int = 7,
  16. cwd: Optional[str] = None):
  17. """
  18. 执行 `repo init`。
  19. 可选参数 `manifest_file` 用于指定 release manifest(相对路径,如 'release.xml' 或 'release/release_xxx.xml')
  20. """
  21. cmd = [
  22. 'repo', 'init',
  23. f'--depth={depth}',
  24. '-u', manifest_url,
  25. '-b', branch,
  26. '--repo-url', repo_url,
  27. ]
  28. if manifest_file:
  29. cmd += ['-m', manifest_file]
  30. _run(cmd, cwd=cwd)
  31. def _repo_sync(jobs: int = 4, cwd: Optional[str] = None):
  32. """
  33. 执行 `repo sync`,参数与 Shell 版本完全一致:
  34. -c: 只下载当前分支
  35. -d: 分离到 manifest 指定版本
  36. -f: 即使某个项目失败也继续
  37. --force-sync: 覆盖错误的 object 目录
  38. --no-tags: 不下载 tags
  39. --prune: 删除不存在远端分支的本地分支
  40. """
  41. cmd = [
  42. 'repo', 'sync',
  43. '-c', '-d', '-f',
  44. '--force-sync',
  45. '--no-tags',
  46. '--prune',
  47. f'-j{jobs}',
  48. ]
  49. _run(cmd, cwd=cwd)
  50. def _get_release_manifest_path(project_dir: str, version: str, snapshot: str) -> Optional[str]:
  51. """
  52. 根据快照配置返回需要使用的 manifest 文件参数(相对路径)。
  53. 返回 None 表示不需要额外 manifest。
  54. 逻辑完全对应原 Shell 中的 update_repo_extra_para()。
  55. """
  56. if not version:
  57. return None
  58. manifests_dir = Path(project_dir) / '.repo' / 'manifests'
  59. release_dir = manifests_dir / 'release'
  60. # 默认使用 release.xml(原脚本:当 version 非空时,先设置 -m release.xml)
  61. manifest_file = 'release.xml'
  62. if snapshot != 'default':
  63. specific_file = f"release_{version}.xml"
  64. specific_path = release_dir / specific_file
  65. if specific_path.is_file():
  66. # 使用具体快照文件
  67. manifest_file = f"release/{specific_file}"
  68. else:
  69. raise RepoError(f"Snapshot manifest not found: {specific_path}")
  70. return manifest_file
  71. def download_project(project_dir: str, manifest_url: str, branch: str,
  72. repo_url: str, version: Optional[str] = None,
  73. snapshot: str = 'default', sync_jobs: int = 4):
  74. """
  75. 下载一个 repo 工程到指定目录。
  76. 对应原 Shell 中的 download_mcu / download_soc。
  77. """
  78. target = Path(project_dir)
  79. target.mkdir(parents=True, exist_ok=True)
  80. # 第一次 repo init(不带 -m)
  81. _repo_init(manifest_url, branch, repo_url, depth=7, cwd=str(target))
  82. # 是否需要额外的 manifest(release.xml 或 release/release_xxx.xml)
  83. try:
  84. extra_manifest = _get_release_manifest_path(str(target), version, snapshot)
  85. if extra_manifest:
  86. logger.info("Using extra manifest: %s", extra_manifest)
  87. # 第二次 repo init 带上 -m 参数(覆盖)
  88. _repo_init(manifest_url, branch, repo_url,
  89. manifest_file=extra_manifest, depth=7, cwd=str(target))
  90. except RepoError:
  91. logger.exception("Failed to determine release manifest")
  92. raise
  93. # repo sync
  94. _repo_sync(jobs=sync_jobs, cwd=str(target))