Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion tools/attachconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ def GenAttachConfigProject(program = None):
attachconfig=[]
GetAttachConfig("get",attachconfig,0)
print("\033[32m✅ AttachConfig has: \033[0m")
if attachconfig==[]:
print("No AttachConfig found.")
return
prefix=attachconfig[0]
for line in attachconfig:
temp_prefix=line.split(".", 1)
Expand Down Expand Up @@ -84,4 +87,4 @@ def GetAttachConfig(action,attachconfig,attachconfig_result):
from ci.bsp_buildings import get_details_and_dependencies
detail_list=get_details_and_dependencies([name],projects)
for line in detail_list:
attachconfig_result.append(line)
attachconfig_result.append(line)
112 changes: 66 additions & 46 deletions tools/ci/bsp_buildings.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,30 @@ def is_env_enabled(name, default=False):

return value.lower() not in ('', '0', 'false', 'no', 'off')

def parse_csv_env(name, required=False):
value = os.getenv(name)
if value is None or value.strip() == '':
if required:
print(f"::error::{name} is not set or empty")
sys.exit(1)
return None

items = [item.strip() for item in value.split(',') if item.strip()]
if required and items == []:
print(f"::error::{name} is not set or empty")
sys.exit(1)

return items

def split_config_commands(value):
if value is None:
return None
if isinstance(value, str):
return value.splitlines()
if isinstance(value, (list, tuple)):
return list(value)
return [value]

def run_dist_build_check(bsp, scons_args=''):
"""
build BSP distribution and verify that the generated project can compile.
Expand Down Expand Up @@ -381,15 +405,10 @@ def check_output(output, check_string):
qemu_timeout_second = 50

rtt_root = os.getcwd()
srtt_bsp = os.getenv('SRTT_BSP').split(',')
attachconfig_rtt_bsp_env = os.getenv('ATTACHCONFIG_RTT_BSP')
attachconfig_rtt_bsp = None
if attachconfig_rtt_bsp_env:
attachconfig_rtt_bsp = {
bsp.strip()
for bsp in attachconfig_rtt_bsp_env.split(',')
if bsp.strip()
}
srtt_bsp = parse_csv_env('SRTT_BSP', required=True)
attachconfig_rtt_bsp = parse_csv_env('ATTACHCONFIG_RTT_BSP')
if attachconfig_rtt_bsp is not None:
attachconfig_rtt_bsp = set(attachconfig_rtt_bsp)
print(srtt_bsp)
for bsp in srtt_bsp:
count += 1
Expand Down Expand Up @@ -444,9 +463,9 @@ def check_output(output, check_string):
# 如果是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()
pre_build_commands = split_config_commands(details.get("pre_build"))
build_command = split_config_commands(details.get("build_cmd"))
post_build_command = split_config_commands(details.get("post_build"))
qemu_command = details.get("run_cmd")

if details.get("kconfig") is not None:
Expand All @@ -455,7 +474,7 @@ def check_output(output, check_string):
else:
build_check_result = None
if details.get("msh_cmd") is not None:
commands = details.get("msh_cmd").splitlines()
commands = split_config_commands(details.get("msh_cmd"))
else:
msh_cmd = None
if details.get("checkresult") is not None:
Expand All @@ -467,54 +486,55 @@ def check_output(output, check_string):
else:
ci_build_run_flag = None
if details.get("pre_build") is not None:
pre_build_commands = details.get("pre_build").splitlines()
pre_build_commands = split_config_commands(details.get("pre_build"))
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()
build_command = split_config_commands(details.get("build_cmd"))
else:
build_command = None
if details.get("post_build") is not None:
post_build_command = details.get("post_build").splitlines()
post_build_command = split_config_commands(details.get("post_build"))
if details.get("run_cmd") is not None:
qemu_command = details.get("run_cmd")
else:
qemu_command = None
if details.get("kconfig") is None:
continue
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)
try:
#加载yml中的配置放到.config文件
with open(config_file, 'a') as destination:
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::")


finally:
shutil.copyfile(config_bacakup, config_file)
os.remove(config_bacakup)



Expand Down
156 changes: 156 additions & 0 deletions tools/testcases/test_attachconfig_robustness.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import contextlib
import importlib.util
import os
import shutil
import subprocess
import sys
import tempfile
import types
import unittest
from io import StringIO


RTT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))


def load_attachconfig_module():
scons_mod = types.ModuleType("SCons")
script_mod = types.ModuleType("SCons.Script")
script_mod.GetOption = lambda name: None

old_scons = sys.modules.get("SCons")
old_script = sys.modules.get("SCons.Script")
sys.modules["SCons"] = scons_mod
sys.modules["SCons.Script"] = script_mod

try:
path = os.path.join(RTT_ROOT, "tools", "attachconfig.py")
spec = importlib.util.spec_from_file_location("attachconfig_under_test", path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
finally:
if old_scons is None:
sys.modules.pop("SCons", None)
else:
sys.modules["SCons"] = old_scons
if old_script is None:
sys.modules.pop("SCons.Script", None)
else:
sys.modules["SCons.Script"] = old_script


class AttachConfigRobustnessTest(unittest.TestCase):

def test_attach_query_handles_empty_attachconfig_list(self):
module = load_attachconfig_module()
module.GetOption = lambda name: "?" if name == "attach" else None
module.GetAttachConfig = lambda action, attachconfig, result: None

output = StringIO()
with contextlib.redirect_stdout(output):
module.GenAttachConfigProject()

self.assertIn("AttachConfig", output.getvalue())

def test_bsp_buildings_reports_missing_srtt_bsp_without_traceback(self):
env = os.environ.copy()
env.pop("SRTT_BSP", None)

result = subprocess.run(
[sys.executable, os.path.join("tools", "ci", "bsp_buildings.py")],
cwd=RTT_ROOT,
env=env,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)

combined_output = result.stdout + result.stderr
self.assertNotEqual(result.returncode, 0)
self.assertIn("SRTT_BSP", combined_output)
self.assertNotIn("Traceback", combined_output)
def test_bsp_board_info_allows_missing_optional_commands(self):
bsp_name = "tmp_attachconfig_board_info_test"
bsp_dir = os.path.join(RTT_ROOT, "bsp", bsp_name)
attach_dir = os.path.join(bsp_dir, ".ci", "attachconfig")

shutil.rmtree(bsp_dir, ignore_errors=True)
os.makedirs(attach_dir, exist_ok=True)

try:
config_file = os.path.join(bsp_dir, ".config")
backup_file = config_file + ".origin"
with open(config_file, "w", encoding="utf-8") as f:
f.write("CONFIG_BASE=y\n")

with open(os.path.join(attach_dir, "board_attachconfig.yml"), "w", encoding="utf-8") as f:
f.write(
"bsp_board_info:\n"
" run_cmd: \"echo run\"\n"
"board_project:\n"
" kconfig:\n"
" - CONFIG_BOARD_PROJECT=y\n"
)

env = os.environ.copy()
env["SRTT_BSP"] = bsp_name

result = subprocess.run(
[sys.executable, os.path.join("tools", "ci", "bsp_buildings.py")],
cwd=RTT_ROOT,
env=env,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)

combined_output = result.stdout + result.stderr
self.assertNotIn("AttributeError", combined_output)
self.assertNotIn("Traceback", combined_output)
self.assertFalse(os.path.exists(backup_file))
finally:
shutil.rmtree(bsp_dir, ignore_errors=True)
def test_attachconfig_without_kconfig_does_not_leave_backup(self):
bsp_name = "tmp_attachconfig_test"
bsp_dir = os.path.join(RTT_ROOT, "bsp", bsp_name)
attach_dir = os.path.join(bsp_dir, ".ci", "attachconfig")

shutil.rmtree(bsp_dir, ignore_errors=True)
os.makedirs(attach_dir, exist_ok=True)

try:
config_file = os.path.join(bsp_dir, ".config")
backup_file = config_file + ".origin"
with open(config_file, "w", encoding="utf-8") as f:
f.write("CONFIG_BASE=y\n")

with open(os.path.join(attach_dir, "bad_attachconfig.yml"), "w", encoding="utf-8") as f:
f.write(
"no_kconfig_project:\n"
" pre_build: \"echo pre\"\n"
" build_cmd: \"echo build\"\n"
" post_build: \"echo post\"\n"
)

env = os.environ.copy()
env["SRTT_BSP"] = bsp_name

subprocess.run(
[sys.executable, os.path.join("tools", "ci", "bsp_buildings.py")],
cwd=RTT_ROOT,
env=env,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)

self.assertFalse(os.path.exists(backup_file))
with open(config_file, "r", encoding="utf-8") as f:
self.assertEqual(f.read(), "CONFIG_BASE=y\n")
finally:
shutil.rmtree(bsp_dir, ignore_errors=True)


if __name__ == "__main__":
unittest.main()
Loading