mirror of https://github.com/RT-Thread/rt-thread
374 lines
14 KiB
Python
374 lines
14 KiB
Python
#
|
||
# 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)
|