Skip to content

Commit 75246b5

Browse files
CYFS3Rbb666
authored andcommitted
ci: tighten BSP build failure detection
1 parent 3932b77 commit 75246b5

2 files changed

Lines changed: 177 additions & 54 deletions

File tree

tools/ci/bsp_buildings.py

Lines changed: 152 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,63 @@
1818
import multiprocessing
1919
import yaml
2020

21+
SCONS_FATAL_PATTERNS = (
22+
re.compile(r'No SConstruct file found', re.IGNORECASE),
23+
re.compile(r'SConscript.*No such file or directory', re.IGNORECASE),
24+
re.compile(r'No such file or directory.*SConscript', re.IGNORECASE),
25+
re.compile(r"(can'?t|cannot|unable to)\s+(read|open|find).*SConscript", re.IGNORECASE),
26+
re.compile(r'FileNotFoundError:.*SConscript', re.IGNORECASE),
27+
re.compile(r'scons:\s+\*\*\*', re.IGNORECASE),
28+
)
29+
2130
def add_summary(text):
2231
"""
2332
add summary to github action.
2433
"""
25-
os.system(f'echo "{text}" >> $GITHUB_STEP_SUMMARY ;')
34+
summary_file = os.getenv('GITHUB_STEP_SUMMARY')
35+
if summary_file:
36+
with open(summary_file, 'a', encoding='utf-8') as file:
37+
file.write(text + '\n')
38+
39+
def normalize_returncode(status):
40+
"""
41+
normalize os.system status to command exit code.
42+
"""
43+
if status == 0:
44+
return 0
45+
46+
if os.name == 'nt':
47+
return status
48+
49+
if hasattr(os, 'waitstatus_to_exitcode'):
50+
try:
51+
return os.waitstatus_to_exitcode(status)
52+
except ValueError:
53+
return status
54+
55+
return status >> 8
56+
57+
def contains_scons_fatal_error(output):
58+
"""
59+
check whether scons output contains a fatal build-script error.
60+
"""
61+
output_str = ''.join(output) if isinstance(output, list) else str(output)
62+
return any(pattern.search(output_str) for pattern in SCONS_FATAL_PATTERNS)
63+
64+
def check_bsp_build_scripts(bsp_dir):
65+
"""
66+
check whether the BSP has the basic scons build entry files.
67+
"""
68+
missing = []
69+
for filename in ('SConstruct', 'SConscript'):
70+
if not os.path.isfile(os.path.join(bsp_dir, filename)):
71+
missing.append(filename)
72+
73+
if missing:
74+
print(f"::error::missing {', '.join(missing)} in {bsp_dir}")
75+
return False
2676

77+
return True
2778

2879
def run_cmd(cmd, output_info=True):
2980
"""
@@ -33,19 +84,27 @@ def run_cmd(cmd, output_info=True):
3384

3485
output_str_list = []
3586
res = 0
87+
output_file = f'output_{os.getpid()}_{threading.get_ident()}.txt'
88+
devnull = os.devnull
3689

3790
if output_info:
38-
res = os.system(cmd + " > output.txt 2>&1")
91+
res = os.system(cmd + f" > {output_file} 2>&1")
3992
else:
40-
res = os.system(cmd + " > /dev/null 2>output.txt")
93+
res = os.system(cmd + f" > {devnull} 2>{output_file}")
4194

42-
with open("output.txt", "r") as file:
95+
with open(output_file, "r") as file:
4396
output_str_list = file.readlines()
4497

4598
for line in output_str_list:
4699
print(line, end='')
47100

48-
os.remove("output.txt")
101+
os.remove(output_file)
102+
103+
res = normalize_returncode(res)
104+
if contains_scons_fatal_error(output_str_list):
105+
print(f"::error::scons fatal error detected while running: {cmd}")
106+
if res == 0:
107+
res = 1
49108

50109
return output_str_list, res
51110

@@ -61,6 +120,10 @@ def run_dist_build_check(bsp, scons_args=''):
61120
build BSP distribution and verify that the generated project can compile.
62121
"""
63122
os.chdir(rtt_root)
123+
bsp_dir = os.path.join(rtt_root, 'bsp', bsp)
124+
if not check_bsp_build_scripts(bsp_dir):
125+
return False
126+
64127
dist_root = os.path.join(rtt_root, 'bsp', bsp, 'dist')
65128
dist_project = os.path.join(dist_root, 'project')
66129
if os.path.exists(dist_root):
@@ -77,6 +140,9 @@ def run_dist_build_check(bsp, scons_args=''):
77140
print(f"::error::dist project not found: {dist_project}")
78141
return False
79142

143+
if not check_bsp_build_scripts(dist_project):
144+
return False
145+
80146
old_rtt_root = os.environ.pop('RTT_ROOT', None)
81147
_, res = run_cmd(f'scons --pyconfig-silent -C {dist_project}', output_info=True)
82148
if res != 0:
@@ -118,6 +184,15 @@ def build_bsp(bsp, scons_args='',name='default', pre_build_commands=None, post_b
118184
119185
"""
120186
success = True
187+
bsp_dir = os.path.join(rtt_root, 'bsp', bsp)
188+
189+
if not os.path.isdir(bsp_dir):
190+
print(f"::error::BSP directory not found: {bsp_dir}")
191+
return False
192+
193+
if not check_bsp_build_scripts(bsp_dir):
194+
return False
195+
121196
# 设置环境变量
122197
if bsp_build_env is not None:
123198
print("Setting environment variables:")
@@ -128,50 +203,63 @@ def build_bsp(bsp, scons_args='',name='default', pre_build_commands=None, post_b
128203
os.makedirs(f'{rtt_root}/output/bsp/{bsp}', exist_ok=True)
129204
if os.path.exists(f"{rtt_root}/bsp/{bsp}/Kconfig"):
130205
os.chdir(rtt_root)
131-
run_cmd(f'scons -C bsp/{bsp} --pyconfig-silent', output_info=True)
206+
_, res = run_cmd(f'scons -C bsp/{bsp} --pyconfig-silent', output_info=True)
207+
if res != 0:
208+
print(f"::error::pyconfig failed for {bsp}")
209+
success = False
132210

133211
os.chdir(f'{rtt_root}/bsp/{bsp}')
134-
run_cmd('pkgs --update-force', output_info=True)
135-
run_cmd('pkgs --list')
136-
137-
nproc = multiprocessing.cpu_count()
138-
if pre_build_commands is not None:
139-
print("Pre-build commands:")
140-
print(pre_build_commands)
141-
for command in pre_build_commands:
142-
print(command)
143-
output, returncode = run_cmd(command, output_info=True)
144-
print(output)
145-
if returncode != 0:
146-
print(f"Pre-build command failed: {command}")
147-
print(output)
148-
os.chdir(rtt_root)
149-
# scons 编译命令
150-
cmd = f'scons -C bsp/{bsp} -j{nproc} {scons_args}' # --debug=time for debug time
151-
output, res = run_cmd(cmd, output_info=True)
152-
if build_check_result is not None:
153-
if res != 0 or not check_output(output, build_check_result):
154-
print("Build failed or build check result not found")
155-
print(output)
212+
_, res = run_cmd('pkgs --update-force', output_info=True)
156213
if res != 0:
214+
print(f"::error::pkgs --update-force failed for {bsp}")
157215
success = False
158-
else:
159-
#拷贝当前的文件夹下面的所有以elf结尾的文件拷贝到rt-thread/output文件夹下
160-
import glob
161-
# 拷贝编译生成的文件到output目录,文件拓展为 elf,bin,hex
162-
for file_type in ['*.elf', '*.bin', '*.hex']:
163-
files = glob.glob(f'{rtt_root}/bsp/{bsp}/{file_type}')
164-
for file in files:
165-
shutil.copy(file, f'{rtt_root}/output/bsp/{bsp}/{name.replace("/", "_")}.{file_type[2:]}')
166-
if is_env_enabled('RTT_CI_BUILD_DIST'):
167-
print(f"::group::\tChecking dist project: {bsp} {name}")
168-
dist_res = run_dist_build_check(bsp, scons_args)
169-
print("::endgroup::")
170-
if not dist_res:
171-
add_summary(f'\t- ❌ dist build {bsp} {name} failed.')
172-
success = False
173-
else:
174-
add_summary(f'\t- ✅ dist build {bsp} {name} success.')
216+
_, res = run_cmd('pkgs --list')
217+
if res != 0:
218+
print(f"::error::pkgs --list failed for {bsp}")
219+
success = False
220+
else:
221+
print(f"Kconfig not found, skip pyconfig and package update: {os.path.join(bsp_dir, 'Kconfig')}")
222+
223+
nproc = multiprocessing.cpu_count()
224+
if pre_build_commands is not None:
225+
print("Pre-build commands:")
226+
print(pre_build_commands)
227+
for command in pre_build_commands:
228+
print(command)
229+
output, returncode = run_cmd(command, output_info=True)
230+
print(output)
231+
if returncode != 0:
232+
print(f"Pre-build command failed: {command}")
233+
print(output)
234+
success = False
235+
os.chdir(rtt_root)
236+
# scons 编译命令
237+
cmd = f'scons -C bsp/{bsp} -j{nproc} {scons_args}' # --debug=time for debug time
238+
output, res = run_cmd(cmd, output_info=True)
239+
if build_check_result is not None:
240+
if res != 0 or not check_output(output, build_check_result):
241+
print("Build failed or build check result not found")
242+
print(output)
243+
success = False
244+
if res != 0:
245+
success = False
246+
if success:
247+
#拷贝当前的文件夹下面的所有以elf结尾的文件拷贝到rt-thread/output文件夹下
248+
import glob
249+
# 拷贝编译生成的文件到output目录,文件拓展为 elf,bin,hex
250+
for file_type in ['*.elf', '*.bin', '*.hex']:
251+
files = glob.glob(f'{rtt_root}/bsp/{bsp}/{file_type}')
252+
for file in files:
253+
shutil.copy(file, f'{rtt_root}/output/bsp/{bsp}/{name.replace("/", "_")}.{file_type[2:]}')
254+
if is_env_enabled('RTT_CI_BUILD_DIST'):
255+
print(f"::group::\tChecking dist project: {bsp} {name}")
256+
dist_res = run_dist_build_check(bsp, scons_args)
257+
print("::endgroup::")
258+
if not dist_res:
259+
add_summary(f'\t- ❌ dist build {bsp} {name} failed.')
260+
success = False
261+
else:
262+
add_summary(f'\t- ✅ dist build {bsp} {name} success.')
175263

176264
os.chdir(f'{rtt_root}/bsp/{bsp}')
177265
if post_build_command is not None:
@@ -181,7 +269,11 @@ def build_bsp(bsp, scons_args='',name='default', pre_build_commands=None, post_b
181269
if returncode != 0:
182270
print(f"Post-build command failed: {command}")
183271
print(output)
184-
run_cmd('scons -c', output_info=False)
272+
success = False
273+
_, clean_res = run_cmd('scons -c', output_info=False)
274+
if clean_res != 0:
275+
print(f"::error::scons clean failed for {bsp}")
276+
success = False
185277

186278
return success
187279

@@ -240,19 +332,27 @@ def build_bsp_attachconfig(bsp, attach_file):
240332
"""
241333
config_file = os.path.join(rtt_root, 'bsp', bsp, '.config')
242334
config_bacakup = config_file+'.origin'
243-
shutil.copyfile(config_file, config_bacakup)
335+
if not os.path.isfile(config_file):
336+
print(f"::error::.config not found: {config_file}")
337+
return False
244338

245339
attachconfig_dir = os.path.join(rtt_root, 'bsp', bsp, '.ci/attachconfig')
246340
attach_path = os.path.join(attachconfig_dir, attach_file)
341+
if not os.path.isfile(attach_path):
342+
print(f"::error::attach config not found: {attach_path}")
343+
return False
247344

248-
append_file(attach_path, config_file)
345+
shutil.copyfile(config_file, config_bacakup)
249346

250-
scons_args = check_scons_args(attach_path)
347+
try:
348+
append_file(attach_path, config_file)
251349

252-
res = build_bsp(bsp, scons_args,name=attach_file)
350+
scons_args = check_scons_args(attach_path)
253351

254-
shutil.copyfile(config_bacakup, config_file)
255-
os.remove(config_bacakup)
352+
res = build_bsp(bsp, scons_args,name=attach_file)
353+
finally:
354+
shutil.copyfile(config_bacakup, config_file)
355+
os.remove(config_bacakup)
256356

257357
return res
258358

tools/ci/compile_bsp_with_drivers.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import subprocess
1212
import logging
1313
import os
14+
import sys
1415

1516
CONFIG_BSP_USING_X = ["CONFIG_BSP_USING_UART", "CONFIG_BSP_USING_I2C", "CONFIG_BSP_USING_SPI", "CONFIG_BSP_USING_ADC", "CONFIG_BSP_USING_DAC"]
1617

@@ -81,18 +82,40 @@ def modify_config(file_path, configs):
8182
file.write("#define " + define1 + "\n")
8283
file.write("#define " + define2 + "\n")
8384

85+
def check_scons_build_files(dir):
86+
missing = []
87+
for filename in ["SConstruct", "SConscript"]:
88+
if not os.path.isfile(os.path.join(dir, filename)):
89+
missing.append(filename)
90+
91+
if missing:
92+
logging.error("missing %s in %s", ", ".join(missing), dir)
93+
return False
94+
95+
return True
96+
8497
def recompile_bsp(dir):
8598
logging.info("recomplie bsp: {}".format(dir))
86-
os.system("scons -C " + dir)
99+
if not check_scons_build_files(dir):
100+
return 1
101+
102+
result = subprocess.run(["scons", "-C", dir])
103+
if result.returncode != 0:
104+
logging.error("scons failed for %s", dir)
105+
return result.returncode
87106

88107
if __name__ == '__main__':
89108
init_logger()
90109
recompile_bsp_dirs = diff()
110+
failed = 0
91111
for dir in recompile_bsp_dirs:
92112
dot_config_path = dir + "/" + ".config"
93113
configs = check_config_in_file(dot_config_path)
94114
logging.info("add config:")
95115
logging.info(configs)
96116
logging.info("Add configurations and recompile!")
97117
modify_config(dir, configs)
98-
recompile_bsp(dir)
118+
if recompile_bsp(dir) != 0:
119+
failed += 1
120+
121+
sys.exit(failed)

0 commit comments

Comments
 (0)