pipeline.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. #!/usr/bin/env python3
  2. """
  3. pipeline.py - 域控软件集成流水线主入口
  4. 完全替代原 Shell 脚本的主流程。
  5. 使用模块化设计,每个步骤对应一个或多个模块函数。
  6. """
  7. import os
  8. import sys
  9. import logging
  10. from pathlib import Path
  11. # 导入各模块
  12. from config import PipelineConfig
  13. from environment import setup_environment
  14. from logging_conf import setup_logging
  15. # 其他模块按需导入,避免循环依赖(可在函数内导入)
  16. # 这里为了代码清晰,提前导入全部
  17. from binary_repo import pull_binaries, commit_binaries
  18. from repo_manager import download_mcu, download_soc
  19. from gerrit import query_changes, apply_changes, label_verified
  20. from build_mcu import prepare_mcu, compile_mcu
  21. from build_soc import prepare_soc, compile_soc
  22. from artifacts import (
  23. collect_logs,
  24. collect_newest_one_commit,
  25. collect_review_records,
  26. collect_qac_report,
  27. prepare_ftp_upload,
  28. commit_snapshot_manifest,
  29. clean_csv_add_header,
  30. )
  31. from ftp_upload import upload_directory
  32. from utils import CommandError, RepoError, BuildMCUError, BuildSOCError, GerritError, ArtifactError
  33. def run_pipeline():
  34. """执行完整的 CI/CD 流水线。"""
  35. # ---------- 1. 加载配置 ----------
  36. cfg = PipelineConfig.from_env()
  37. # ---------- 2. 配置日志 ----------
  38. log_file = os.path.join(cfg.workspace, 'pipeline.log')
  39. setup_logging(level=logging.INFO, log_file=log_file, console=True)
  40. logger = logging.getLogger(__name__)
  41. logger.info("Pipeline started - configuration: %s", cfg)
  42. try:
  43. # ---------- 3. 初始化环境 ----------
  44. logger.info("===== Setting up environment =====")
  45. setup_environment()
  46. # 确保 PYTHONUNBUFFERED 在子进程中也生效(已在 environment 中设置)
  47. # ---------- 4. 同步二进制仓库 ----------
  48. logger.info("===== Syncing binary repositories =====")
  49. for repo in cfg.binary_repos:
  50. pull_binaries(repo)
  51. # ---------- 5. 下载源码 ----------
  52. logger.info("===== Downloading source code =====")
  53. download_mcu(
  54. cfg.mcu_dir,
  55. cfg.mcu_manifest_url,
  56. cfg.repo_branch,
  57. f"ssh://{cfg.gerrit_host}/tools/repo",
  58. cfg.mcu_release_version,
  59. cfg.snapshot,
  60. )
  61. download_soc(
  62. cfg.soc_dir,
  63. cfg.soc_manifest_url,
  64. cfg.repo_branch,
  65. f"ssh://{cfg.gerrit_host}/tools/repo",
  66. cfg.soc_release_version,
  67. cfg.snapshot,
  68. )
  69. # ---------- 6. 应用 Gerrit 变更 ----------
  70. changes = []
  71. if cfg.gerrit_flag:
  72. logger.info("===== Downloading Gerrit changes =====")
  73. changes = query_changes(
  74. cfg.gerrit_host,
  75. cfg.gerrit_port,
  76. cfg.gerrit_name,
  77. cfg.gerrit_changes_list,
  78. )
  79. apply_changes(changes, cfg.mcu_dir, cfg.soc_dir)
  80. # ---------- 7. 编译 MCU ----------
  81. logger.info("===== Compiling MCU =====")
  82. prepare_mcu(cfg.mcu_sdk_dir, cfg.soc_dir) # 当前为空,保留扩展
  83. compile_mcu(cfg.mcu_sdk_dir, cfg.profile_mode, cfg.soc_project)
  84. # ---------- 8. 编译 SOC ----------
  85. logger.info("===== Compiling SOC =====")
  86. prepare_soc(cfg.mcu_sdk_dir, cfg.soc_dir, cfg.soc_project)
  87. compile_soc(
  88. cfg.soc_dir,
  89. cfg.soc_project,
  90. cfg.secure_enable,
  91. cfg.factory_emmc_img_enable,
  92. cfg.profile_mode,
  93. )
  94. # ---------- 9. Gerrit 打分 ----------
  95. if cfg.gerrit_flag and changes:
  96. logger.info("===== Labeling Gerrit changes =====")
  97. label_verified(
  98. cfg.gerrit_host,
  99. cfg.gerrit_port,
  100. cfg.gerrit_name,
  101. changes,
  102. )
  103. # ---------- 10. 自动提交二进制产物 ----------
  104. if cfg.auto_commit_server:
  105. logger.info("===== Committing binary repos =====")
  106. for repo in cfg.binary_repos:
  107. commit_binaries(repo, cfg.auto_commit_branch)
  108. # ---------- 11. 收集日志、快照、QAC ----------
  109. logger.info("===== Collecting commit logs and snapshots =====")
  110. collect_log_file = Path(cfg.workspace) / 'commit_log.csv'
  111. newest_log_file = Path(cfg.workspace) / 'newest_one_commit_log.csv'
  112. review_records_dir = Path(cfg.workspace) / 'review_records'
  113. # 清理旧文件
  114. collect_log_file.unlink(missing_ok=True)
  115. newest_log_file.unlink(missing_ok=True)
  116. if review_records_dir.exists():
  117. import shutil
  118. shutil.rmtree(review_records_dir)
  119. review_records_dir.mkdir(parents=True)
  120. for repo_dir in [cfg.mcu_dir, cfg.soc_dir]:
  121. logger.info("Collecting logs from %s", repo_dir)
  122. collect_logs(repo_dir, str(collect_log_file))
  123. collect_newest_one_commit(repo_dir, str(newest_log_file))
  124. collect_review_records(repo_dir, str(review_records_dir))
  125. # 清理 CSV 并添加表头
  126. header_commit = [
  127. 'project name', 'commit hash', 'auth name', 'auth email',
  128. 'auth date', 'commit date', 'subject',
  129. ]
  130. header_newest = header_commit + ['code lines'] # 在新脚本中可保留兼容
  131. clean_csv_add_header(collect_log_file, header_commit)
  132. clean_csv_add_header(newest_log_file, header_newest)
  133. # 收集 QAC 报告
  134. qac_report_dir = Path(cfg.workspace) / 'qac_report'
  135. collect_qac_report(cfg.mcu_sdk_dir, str(qac_report_dir))
  136. # ---------- 12. FTP 上传准备 ----------
  137. logger.info("===== Preparing FTP upload =====")
  138. prepare_ftp_upload(
  139. cfg,
  140. str(collect_log_file),
  141. str(newest_log_file),
  142. str(review_records_dir),
  143. str(qac_report_dir) if qac_report_dir.exists() else None,
  144. )
  145. # ---------- 13. 提交发布基线 manifest ----------
  146. logger.info("===== Committing release manifests =====")
  147. commit_snapshot_manifest(cfg.mcu_dir, cfg.mcu_release_version)
  148. commit_snapshot_manifest(cfg.soc_dir, cfg.soc_release_version)
  149. # ---------- 14. 可选:触发实际的 FTP 上传 ----------
  150. if os.environ.get('FTP_UPLOAD_ENABLE', 'true').lower() == 'true':
  151. logger.info("===== Uploading to FTP =====")
  152. # 需要本地部署目录存在,由 prepare_ftp_upload 创建
  153. deploy_dir = Path(cfg.deploy_image_local_dir) / cfg.repo_branch
  154. # 找到最新的部署子目录(时间戳命名)
  155. deploy_subdirs = sorted(deploy_dir.iterdir(), key=lambda p: p.name, reverse=True)
  156. if deploy_subdirs:
  157. latest_deploy = deploy_subdirs[0]
  158. upload_directory(cfg, str(latest_deploy))
  159. else:
  160. logger.warning("No deploy directory found for FTP upload")
  161. logger.info("===== Pipeline completed successfully =====")
  162. except (CommandError, BuildMCUError, BuildSOCError, GerritError,
  163. ArtifactError, Exception) as e:
  164. logger.exception("Pipeline failed: %s", e)
  165. sys.exit(1)
  166. if __name__ == '__main__':
  167. run_pipeline()