bsp_buildings.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. #
  2. # Copyright (c) 2025, RT-Thread Development Team
  3. #
  4. # SPDX-License-Identifier: Apache-2.0
  5. #
  6. # Change Logs:
  7. # Date Author Notes
  8. # 2025-04-21 supperthomas add the smart yml support and add env
  9. #
  10. import subprocess
  11. import threading
  12. import time
  13. import logging
  14. import sys
  15. import os
  16. import shutil
  17. import re
  18. import multiprocessing
  19. import yaml
  20. def add_summary(text):
  21. """
  22. add summary to github action.
  23. """
  24. os.system(f'echo "{text}" >> $GITHUB_STEP_SUMMARY ;')
  25. def run_cmd(cmd, output_info=True):
  26. """
  27. run command and return output and result.
  28. """
  29. print('\033[1;32m' + cmd + '\033[0m')
  30. output_str_list = []
  31. res = 0
  32. if output_info:
  33. res = os.system(cmd + " > output.txt 2>&1")
  34. else:
  35. res = os.system(cmd + " > /dev/null 2>output.txt")
  36. with open("output.txt", "r") as file:
  37. output_str_list = file.readlines()
  38. for line in output_str_list:
  39. print(line, end='')
  40. os.remove("output.txt")
  41. return output_str_list, res
  42. def build_bsp(bsp, scons_args='',name='default', pre_build_commands=None, post_build_command=None,build_check_result = None,bsp_build_env=None):
  43. """
  44. build bsp.
  45. cd {rtt_root}
  46. scons -C bsp/{bsp} --pyconfig-silent > /dev/null
  47. cd {rtt_root}/bsp/{bsp}
  48. pkgs --update > /dev/null
  49. pkgs --list
  50. cd {rtt_root}
  51. scons -C bsp/{bsp} -j{nproc} {scons_args}
  52. cd {rtt_root}/bsp/{bsp}
  53. scons -c > /dev/null
  54. rm -rf packages
  55. """
  56. success = True
  57. # 设置环境变量
  58. if bsp_build_env is not None:
  59. print("Setting environment variables:")
  60. for key, value in bsp_build_env.items():
  61. print(f"{key}={value}")
  62. os.environ[key] = value # 设置环境变量
  63. os.chdir(rtt_root)
  64. os.makedirs(f'{rtt_root}/output/bsp/{bsp}', exist_ok=True)
  65. if os.path.exists(f"{rtt_root}/bsp/{bsp}/Kconfig"):
  66. os.chdir(rtt_root)
  67. run_cmd(f'scons -C bsp/{bsp} --pyconfig-silent', output_info=True)
  68. os.chdir(f'{rtt_root}/bsp/{bsp}')
  69. run_cmd('pkgs --update-force', output_info=True)
  70. run_cmd('pkgs --list')
  71. nproc = multiprocessing.cpu_count()
  72. if pre_build_commands is not None:
  73. print("Pre-build commands:")
  74. print(pre_build_commands)
  75. for command in pre_build_commands:
  76. print(command)
  77. output, returncode = run_cmd(command, output_info=True)
  78. print(output)
  79. if returncode != 0:
  80. print(f"Pre-build command failed: {command}")
  81. print(output)
  82. os.chdir(rtt_root)
  83. # scons 编译命令
  84. cmd = f'scons -C bsp/{bsp} -j{nproc} {scons_args}' # --debug=time for debug time
  85. output, res = run_cmd(cmd, output_info=True)
  86. if build_check_result is not None:
  87. if res != 0 or not check_output(output, build_check_result):
  88. print("Build failed or build check result not found")
  89. print(output)
  90. if res != 0:
  91. success = False
  92. else:
  93. #拷贝当前的文件夹下面的所有以elf结尾的文件拷贝到rt-thread/output文件夹下
  94. import glob
  95. # 拷贝编译生成的文件到output目录,文件拓展为 elf,bin,hex
  96. for file_type in ['*.elf', '*.bin', '*.hex']:
  97. files = glob.glob(f'{rtt_root}/bsp/{bsp}/{file_type}')
  98. for file in files:
  99. shutil.copy(file, f'{rtt_root}/output/bsp/{bsp}/{name.replace("/", "_")}.{file_type[2:]}')
  100. os.chdir(f'{rtt_root}/bsp/{bsp}')
  101. if post_build_command is not None:
  102. for command in post_build_command:
  103. output, returncode = run_cmd(command, output_info=True)
  104. print(output)
  105. if returncode != 0:
  106. print(f"Post-build command failed: {command}")
  107. print(output)
  108. run_cmd('scons -c', output_info=False)
  109. return success
  110. def append_file(source_file, destination_file):
  111. """
  112. append file to another file.
  113. """
  114. with open(source_file, 'r') as source:
  115. with open(destination_file, 'a') as destination:
  116. for line in source:
  117. destination.write(line)
  118. def check_scons_args(file_path):
  119. args = []
  120. with open(file_path, 'r') as file:
  121. for line in file:
  122. match = re.search(r'#\s*scons:\s*(.*)', line)
  123. if match:
  124. args.append(match.group(1).strip())
  125. return ' '.join(args)
  126. def get_details_and_dependencies(details, projects, seen=None):
  127. if seen is None:
  128. seen = set()
  129. detail_list = []
  130. if details is not None:
  131. for dep in details:
  132. if dep not in seen:
  133. dep_details=projects.get(dep)
  134. seen.add(dep)
  135. if dep_details is not None:
  136. if dep_details.get('depends') is not None:
  137. detail_temp=get_details_and_dependencies(dep_details.get('depends'), projects, seen)
  138. for line in detail_temp:
  139. detail_list.append(line)
  140. if dep_details.get('kconfig') is not None:
  141. for line in dep_details.get('kconfig'):
  142. detail_list.append(line)
  143. else:
  144. print(f"::error::There are some problems with attachconfig depend: {dep}");
  145. return detail_list
  146. def build_bsp_attachconfig(bsp, attach_file):
  147. """
  148. build bsp with attach config.
  149. cp bsp/{bsp}/.config bsp/{bsp}/.config.origin
  150. cat .ci/attachconfig/{attach_file} >> bsp/{bsp}/.config
  151. build_bsp()
  152. cp bsp/{bsp}/.config.origin bsp/{bsp}/.config
  153. rm bsp/{bsp}/.config.origin
  154. """
  155. config_file = os.path.join(rtt_root, 'bsp', bsp, '.config')
  156. config_bacakup = config_file+'.origin'
  157. shutil.copyfile(config_file, config_bacakup)
  158. attachconfig_dir = os.path.join(rtt_root, 'bsp', bsp, '.ci/attachconfig')
  159. attach_path = os.path.join(attachconfig_dir, attach_file)
  160. append_file(attach_path, config_file)
  161. scons_args = check_scons_args(attach_path)
  162. res = build_bsp(bsp, scons_args,name=attach_file)
  163. shutil.copyfile(config_bacakup, config_file)
  164. os.remove(config_bacakup)
  165. return res
  166. def check_output(output, check_string):
  167. """检查输出中是否包含指定字符串"""
  168. output_str = ''.join(output) if isinstance(output, list) else str(output)
  169. flag = check_string in output_str
  170. if flag == True:
  171. print('Success: find string ' + check_string)
  172. else:
  173. print(output)
  174. print(f"::error:: can not find string {check_string} output: {output_str}")
  175. return flag
  176. if __name__ == "__main__":
  177. """
  178. build all bsp and attach config.
  179. 1. build all bsp.
  180. 2. build all bsp with attach config.
  181. """
  182. failed = 0
  183. count = 0
  184. ci_build_run_flag = False
  185. qemu_timeout_second = 50
  186. rtt_root = os.getcwd()
  187. srtt_bsp = os.getenv('SRTT_BSP').split(',')
  188. print(srtt_bsp)
  189. for bsp in srtt_bsp:
  190. count += 1
  191. print(f"::group::Compiling BSP: =={count}=== {bsp} ====")
  192. res = build_bsp(bsp)
  193. if not res:
  194. print(f"::error::build {bsp} failed")
  195. add_summary(f"- ❌ build {bsp} failed.")
  196. failed += 1
  197. else:
  198. add_summary(f'- ✅ build {bsp} success.')
  199. print("::endgroup::")
  200. yml_files_content = []
  201. directory = os.path.join(rtt_root, 'bsp', bsp, '.ci/attachconfig')
  202. if os.path.exists(directory):
  203. for root, dirs, files in os.walk(directory):
  204. for filename in files:
  205. if filename.endswith('attachconfig.yml'):
  206. file_path = os.path.join(root, filename)
  207. if os.path.exists(file_path):
  208. try:
  209. with open(file_path, 'r') as file:
  210. content = yaml.safe_load(file)
  211. if content is None:
  212. continue
  213. yml_files_content.append(content)
  214. except yaml.YAMLError as e:
  215. print(f"::error::Error parsing YAML file: {e}")
  216. continue
  217. except Exception as e:
  218. print(f"::error::Error reading file: {e}")
  219. continue
  220. config_file = os.path.join(rtt_root, 'bsp', bsp, '.config')
  221. # 在使用 pre_build_commands 之前,确保它被定义
  222. pre_build_commands = None
  223. build_command = None
  224. post_build_command = None
  225. qemu_command = None
  226. build_check_result = None
  227. commands = None
  228. check_result = None
  229. bsp_build_env = None
  230. for projects in yml_files_content:
  231. for name, details in projects.items():
  232. # 如果是bsp_board_info,读取基本的信息
  233. if(name == 'bsp_board_info'):
  234. print(details)
  235. pre_build_commands = details.get("pre_build").splitlines()
  236. build_command = details.get("build_cmd").splitlines()
  237. post_build_command = details.get("post_build").splitlines()
  238. qemu_command = details.get("run_cmd")
  239. if details.get("kconfig") is not None:
  240. if details.get("buildcheckresult") is not None:
  241. build_check_result = details.get("buildcheckresult")
  242. else:
  243. build_check_result = None
  244. if details.get("msh_cmd") is not None:
  245. commands = details.get("msh_cmd").splitlines()
  246. else:
  247. msh_cmd = None
  248. if details.get("checkresult") is not None:
  249. check_result = details.get("checkresult")
  250. else:
  251. check_result = None
  252. if details.get("ci_build_run_flag") is not None:
  253. ci_build_run_flag = details.get("ci_build_run_flag")
  254. else:
  255. ci_build_run_flag = None
  256. if details.get("pre_build") is not None:
  257. pre_build_commands = details.get("pre_build").splitlines()
  258. if details.get("env") is not None:
  259. bsp_build_env = details.get("env")
  260. else:
  261. bsp_build_env = None
  262. if details.get("build_cmd") is not None:
  263. build_command = details.get("build_cmd").splitlines()
  264. else:
  265. build_command = None
  266. if details.get("post_build") is not None:
  267. post_build_command = details.get("post_build").splitlines()
  268. if details.get("run_cmd") is not None:
  269. qemu_command = details.get("run_cmd")
  270. else:
  271. qemu_command = None
  272. count += 1
  273. config_bacakup = config_file+'.origin'
  274. shutil.copyfile(config_file, config_bacakup)
  275. #加载yml中的配置放到.config文件
  276. with open(config_file, 'a') as destination:
  277. if details.get("kconfig") is None:
  278. #如果没有Kconfig,读取下一个配置
  279. continue
  280. if(projects.get(name) is not None):
  281. # 获取Kconfig中所有的信息
  282. detail_list=get_details_and_dependencies([name],projects)
  283. for line in detail_list:
  284. destination.write(line + '\n')
  285. scons_arg=[]
  286. #如果配置中有scons_arg
  287. if details.get('scons_arg') is not None:
  288. for line in details.get('scons_arg'):
  289. scons_arg.append(line)
  290. scons_arg_str=' '.join(scons_arg) if scons_arg else ' '
  291. print(f"::group::\tCompiling yml project: =={count}==={name}=scons_arg={scons_arg_str}==")
  292. # #开始编译bsp
  293. res = build_bsp(bsp, scons_arg_str,name=name,pre_build_commands=pre_build_commands,post_build_command=post_build_command,build_check_result=build_check_result,bsp_build_env =bsp_build_env)
  294. if not res:
  295. print(f"::error::build {bsp} {name} failed.")
  296. add_summary(f'\t- ❌ build {bsp} {name} failed.')
  297. failed += 1
  298. else:
  299. add_summary(f'\t- ✅ build {bsp} {name} success.')
  300. print("::endgroup::")
  301. shutil.copyfile(config_bacakup, config_file)
  302. os.remove(config_bacakup)
  303. attach_dir = os.path.join(rtt_root, 'bsp', bsp, '.ci/attachconfig')
  304. attach_list = []
  305. #这里是旧的文件方式
  306. for root, dirs, files in os.walk(attach_dir):
  307. for file in files:
  308. if file.endswith('attach'):
  309. file_path = os.path.join(root, file)
  310. relative_path = os.path.relpath(file_path, attach_dir)
  311. attach_list.append(relative_path)
  312. for attach_file in attach_list:
  313. count += 1
  314. print(f"::group::\tCompiling BSP: =={count}=== {bsp} {attach_file}===")
  315. res = build_bsp_attachconfig(bsp, attach_file)
  316. if not res:
  317. print(f"::error::build {bsp} {attach_file} failed.")
  318. add_summary(f'\t- ❌ build {attach_file} failed.')
  319. failed += 1
  320. else:
  321. add_summary(f'\t- ✅ build {attach_file} success.')
  322. print("::endgroup::")
  323. exit(failed)