123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373 |
- #
- # Copyright (c) 2025, RT-Thread Development Team
- #
- # SPDX-License-Identifier: Apache-2.0
- #
- # Change Logs:
- # Date Author Notes
- # 2025-04-21 supperthomas add the smart yml support and add env
- #
- import subprocess
- import threading
- import time
- import logging
- import sys
- import os
- import shutil
- import re
- import multiprocessing
- import yaml
- def add_summary(text):
- """
- add summary to github action.
- """
- os.system(f'echo "{text}" >> $GITHUB_STEP_SUMMARY ;')
- def run_cmd(cmd, output_info=True):
- """
- run command and return output and result.
- """
- print('\033[1;32m' + cmd + '\033[0m')
- output_str_list = []
- res = 0
- if output_info:
- res = os.system(cmd + " > output.txt 2>&1")
- else:
- res = os.system(cmd + " > /dev/null 2>output.txt")
- with open("output.txt", "r") as file:
- output_str_list = file.readlines()
- for line in output_str_list:
- print(line, end='')
- os.remove("output.txt")
- return output_str_list, res
- def build_bsp(bsp, scons_args='',name='default', pre_build_commands=None, post_build_command=None,build_check_result = None,bsp_build_env=None):
- """
- build bsp.
- cd {rtt_root}
- scons -C bsp/{bsp} --pyconfig-silent > /dev/null
- cd {rtt_root}/bsp/{bsp}
- pkgs --update > /dev/null
- pkgs --list
- cd {rtt_root}
- scons -C bsp/{bsp} -j{nproc} {scons_args}
- cd {rtt_root}/bsp/{bsp}
- scons -c > /dev/null
- rm -rf packages
- """
- success = True
- # 设置环境变量
- if bsp_build_env is not None:
- print("Setting environment variables:")
- for key, value in bsp_build_env.items():
- print(f"{key}={value}")
- os.environ[key] = value # 设置环境变量
- os.chdir(rtt_root)
- os.makedirs(f'{rtt_root}/output/bsp/{bsp}', exist_ok=True)
- if os.path.exists(f"{rtt_root}/bsp/{bsp}/Kconfig"):
- os.chdir(rtt_root)
- run_cmd(f'scons -C bsp/{bsp} --pyconfig-silent', output_info=True)
- os.chdir(f'{rtt_root}/bsp/{bsp}')
- run_cmd('pkgs --update-force', output_info=True)
- run_cmd('pkgs --list')
- nproc = multiprocessing.cpu_count()
- if pre_build_commands is not None:
- print("Pre-build commands:")
- print(pre_build_commands)
- for command in pre_build_commands:
- print(command)
- output, returncode = run_cmd(command, output_info=True)
- print(output)
- if returncode != 0:
- print(f"Pre-build command failed: {command}")
- print(output)
- os.chdir(rtt_root)
- # scons 编译命令
- cmd = f'scons -C bsp/{bsp} -j{nproc} {scons_args}' # --debug=time for debug time
- output, res = run_cmd(cmd, output_info=True)
- if build_check_result is not None:
- if res != 0 or not check_output(output, build_check_result):
- print("Build failed or build check result not found")
- print(output)
- if res != 0:
- success = False
- else:
- #拷贝当前的文件夹下面的所有以elf结尾的文件拷贝到rt-thread/output文件夹下
- import glob
- # 拷贝编译生成的文件到output目录,文件拓展为 elf,bin,hex
- for file_type in ['*.elf', '*.bin', '*.hex']:
- files = glob.glob(f'{rtt_root}/bsp/{bsp}/{file_type}')
- for file in files:
- shutil.copy(file, f'{rtt_root}/output/bsp/{bsp}/{name.replace("/", "_")}.{file_type[2:]}')
- os.chdir(f'{rtt_root}/bsp/{bsp}')
- if post_build_command is not None:
- for command in post_build_command:
- output, returncode = run_cmd(command, output_info=True)
- print(output)
- if returncode != 0:
- print(f"Post-build command failed: {command}")
- print(output)
- run_cmd('scons -c', output_info=False)
- return success
- def append_file(source_file, destination_file):
- """
- append file to another file.
- """
- with open(source_file, 'r') as source:
- with open(destination_file, 'a') as destination:
- for line in source:
- destination.write(line)
- def check_scons_args(file_path):
- args = []
- with open(file_path, 'r') as file:
- for line in file:
- match = re.search(r'#\s*scons:\s*(.*)', line)
- if match:
- args.append(match.group(1).strip())
- return ' '.join(args)
- def get_details_and_dependencies(details, projects, seen=None):
- if seen is None:
- seen = set()
- detail_list = []
- if details is not None:
- for dep in details:
- if dep not in seen:
- dep_details=projects.get(dep)
- seen.add(dep)
- if dep_details is not None:
- if dep_details.get('depends') is not None:
- detail_temp=get_details_and_dependencies(dep_details.get('depends'), projects, seen)
- for line in detail_temp:
- detail_list.append(line)
- if dep_details.get('kconfig') is not None:
- for line in dep_details.get('kconfig'):
- detail_list.append(line)
- else:
- print(f"::error::There are some problems with attachconfig depend: {dep}");
- return detail_list
- def build_bsp_attachconfig(bsp, attach_file):
- """
- build bsp with attach config.
- cp bsp/{bsp}/.config bsp/{bsp}/.config.origin
- cat .ci/attachconfig/{attach_file} >> bsp/{bsp}/.config
- build_bsp()
- cp bsp/{bsp}/.config.origin bsp/{bsp}/.config
- rm bsp/{bsp}/.config.origin
- """
- config_file = os.path.join(rtt_root, 'bsp', bsp, '.config')
- config_bacakup = config_file+'.origin'
- shutil.copyfile(config_file, config_bacakup)
- attachconfig_dir = os.path.join(rtt_root, 'bsp', bsp, '.ci/attachconfig')
- attach_path = os.path.join(attachconfig_dir, attach_file)
- append_file(attach_path, config_file)
- scons_args = check_scons_args(attach_path)
- res = build_bsp(bsp, scons_args,name=attach_file)
- shutil.copyfile(config_bacakup, config_file)
- os.remove(config_bacakup)
- return res
- def check_output(output, check_string):
- """检查输出中是否包含指定字符串"""
- output_str = ''.join(output) if isinstance(output, list) else str(output)
- flag = check_string in output_str
- if flag == True:
- print('Success: find string ' + check_string)
- else:
- print(output)
- print(f"::error:: can not find string {check_string} output: {output_str}")
- return flag
- if __name__ == "__main__":
- """
- build all bsp and attach config.
- 1. build all bsp.
- 2. build all bsp with attach config.
- """
- failed = 0
- count = 0
- ci_build_run_flag = False
- qemu_timeout_second = 50
- rtt_root = os.getcwd()
- srtt_bsp = os.getenv('SRTT_BSP').split(',')
- print(srtt_bsp)
- for bsp in srtt_bsp:
- count += 1
- print(f"::group::Compiling BSP: =={count}=== {bsp} ====")
- res = build_bsp(bsp)
- if not res:
- print(f"::error::build {bsp} failed")
- add_summary(f"- ❌ build {bsp} failed.")
- failed += 1
- else:
- add_summary(f'- ✅ build {bsp} success.')
- print("::endgroup::")
- yml_files_content = []
- directory = os.path.join(rtt_root, 'bsp', bsp, '.ci/attachconfig')
- if os.path.exists(directory):
- for root, dirs, files in os.walk(directory):
- for filename in files:
- if filename.endswith('attachconfig.yml'):
- file_path = os.path.join(root, filename)
- if os.path.exists(file_path):
- try:
- with open(file_path, 'r') as file:
- content = yaml.safe_load(file)
- if content is None:
- continue
- yml_files_content.append(content)
- except yaml.YAMLError as e:
- print(f"::error::Error parsing YAML file: {e}")
- continue
- except Exception as e:
- print(f"::error::Error reading file: {e}")
- continue
-
- config_file = os.path.join(rtt_root, 'bsp', bsp, '.config')
- # 在使用 pre_build_commands 之前,确保它被定义
- pre_build_commands = None
- build_command = None
- post_build_command = None
- qemu_command = None
- build_check_result = None
- commands = None
- check_result = None
- bsp_build_env = None
- for projects in yml_files_content:
- for name, details in projects.items():
- # 如果是bsp_board_info,读取基本的信息
- if(name == 'bsp_board_info'):
- print(details)
- pre_build_commands = details.get("pre_build").splitlines()
- build_command = details.get("build_cmd").splitlines()
- post_build_command = details.get("post_build").splitlines()
- qemu_command = details.get("run_cmd")
-
- if details.get("kconfig") is not None:
- if details.get("buildcheckresult") is not None:
- build_check_result = details.get("buildcheckresult")
- else:
- build_check_result = None
- if details.get("msh_cmd") is not None:
- commands = details.get("msh_cmd").splitlines()
- else:
- msh_cmd = None
- if details.get("checkresult") is not None:
- check_result = details.get("checkresult")
- else:
- check_result = None
- if details.get("ci_build_run_flag") is not None:
- ci_build_run_flag = details.get("ci_build_run_flag")
- else:
- ci_build_run_flag = None
- if details.get("pre_build") is not None:
- pre_build_commands = details.get("pre_build").splitlines()
- if details.get("env") is not None:
- bsp_build_env = details.get("env")
- else:
- bsp_build_env = None
- if details.get("build_cmd") is not None:
- build_command = details.get("build_cmd").splitlines()
- else:
- build_command = None
- if details.get("post_build") is not None:
- post_build_command = details.get("post_build").splitlines()
- if details.get("run_cmd") is not None:
- qemu_command = details.get("run_cmd")
- else:
- qemu_command = None
- count += 1
- config_bacakup = config_file+'.origin'
- shutil.copyfile(config_file, config_bacakup)
- #加载yml中的配置放到.config文件
- with open(config_file, 'a') as destination:
- if details.get("kconfig") is None:
- #如果没有Kconfig,读取下一个配置
- continue
- if(projects.get(name) is not None):
- # 获取Kconfig中所有的信息
- detail_list=get_details_and_dependencies([name],projects)
- for line in detail_list:
- destination.write(line + '\n')
- scons_arg=[]
- #如果配置中有scons_arg
- if details.get('scons_arg') is not None:
- for line in details.get('scons_arg'):
- scons_arg.append(line)
- scons_arg_str=' '.join(scons_arg) if scons_arg else ' '
- print(f"::group::\tCompiling yml project: =={count}==={name}=scons_arg={scons_arg_str}==")
- # #开始编译bsp
- 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)
- if not res:
- print(f"::error::build {bsp} {name} failed.")
- add_summary(f'\t- ❌ build {bsp} {name} failed.')
- failed += 1
- else:
- add_summary(f'\t- ✅ build {bsp} {name} success.')
- print("::endgroup::")
- shutil.copyfile(config_bacakup, config_file)
- os.remove(config_bacakup)
- attach_dir = os.path.join(rtt_root, 'bsp', bsp, '.ci/attachconfig')
- attach_list = []
- #这里是旧的文件方式
- for root, dirs, files in os.walk(attach_dir):
- for file in files:
- if file.endswith('attach'):
- file_path = os.path.join(root, file)
- relative_path = os.path.relpath(file_path, attach_dir)
- attach_list.append(relative_path)
- for attach_file in attach_list:
- count += 1
- print(f"::group::\tCompiling BSP: =={count}=== {bsp} {attach_file}===")
- res = build_bsp_attachconfig(bsp, attach_file)
- if not res:
- print(f"::error::build {bsp} {attach_file} failed.")
- add_summary(f'\t- ❌ build {attach_file} failed.')
- failed += 1
- else:
- add_summary(f'\t- ✅ build {attach_file} success.')
- print("::endgroup::")
- exit(failed)
|