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