1#
2# Copyright (c) 2006-2024, RT-Thread Development Team
3#
4# SPDX-License-Identifier: Apache-2.0
5#
6# Change Logs:
7# Date           Author       Notes
8# 2024-07-25     supperthomas the first version
9#
10
11"""
12这个脚本用来编译所有的bsp
13这里的脚本是用的arm-none-eabi-gcc, 默认根据本机已经安装的gcc来编译
14其他的工具链暂时不支持,其实主要根据运行的环境中支持不支持,目前只打算支持主流的
15失败的bsp会存到failed_bsp.log里面
16"""
17
18import os
19import sys
20import shutil
21import multiprocessing
22from multiprocessing import Process
23import yaml
24
25#help说明
26def usage():
27    print('%s all     -- build all GCC bsp' % os.path.basename(sys.argv[0]))
28    print('%s clean   -- clean all bsp' % os.path.basename(sys.argv[0]))
29    print('%s update  -- update all prject files' % os.path.basename(sys.argv[0]))
30
31def add_summary(text):
32    """
33    add summary to github action.
34    """
35    os.system(f'echo "{text}" >> $GITHUB_STEP_SUMMARY ;')
36
37def run_cmd(cmd, output_info=True):
38    """
39    这个函数用来执行命令
40    run command and return output and result.
41    """
42    print('\033[1;32m' + cmd + '\033[0m   ' + os.getcwd())
43
44    output_str_list = []
45    res = 0
46
47    if output_info:
48        res = os.system(cmd + " > output.txt 2>&1")
49    else:
50        res = os.system(cmd + " > /dev/null 2>output.txt")
51    try:
52        with open("output.txt", "r") as file:
53            output_str_list = file.readlines()
54    except FileNotFoundError:
55        with open("output.txt", "w") as file:
56            file.write("new file")
57
58    for line in output_str_list:
59        print(line, end='')
60
61    os.remove("output.txt")
62
63    return output_str_list, res
64
65
66def build_bsp(bsp, scons_args=''):
67    """
68    build bsp.
69
70    cd {rtt_root}
71    scons -C bsp/{bsp} --pyconfig-silent > /dev/null
72
73    cd {rtt_root}/bsp/{bsp}
74    pkgs --update > /dev/null
75    pkgs --list
76
77    cd {rtt_root}
78    scons -C bsp/{bsp} -j{nproc} {scons_args}
79
80    cd {rtt_root}/bsp/{bsp}
81    scons -c > /dev/null
82    rm -rf packages
83
84    """
85    success = True
86    pwd = os.getcwd()
87    print('======pwd==='+ os.getcwd()+'===bsp:=='+bsp)
88
89    os.chdir(rtt_root)
90    #有Kconfig 说明可以执行menuconfig
91    if os.path.exists(f"{rtt_root}/bsp/{bsp}/Kconfig"):
92        os.chdir(rtt_root)
93        print('======pwd==='+ os.getcwd()+'===bsp:=='+bsp)
94        run_cmd(f'scons -C bsp/{bsp} --pyconfig-silent', output_info=True)
95        os.chdir(f'{rtt_root}/bsp/{bsp}')
96        print('======pwd222==='+ os.getcwd()+'===bsp:=='+bsp)
97        run_cmd('pkgs --update', output_info=True)
98        run_cmd('pkgs --list')
99        nproc = multiprocessing.cpu_count()
100        os.chdir(rtt_root)
101        cmd = f'scons -C bsp/{bsp} -j{nproc} {scons_args}'
102        result_log, res = run_cmd(cmd, output_info=True)
103
104        if res != 0:
105            # 将失败的bsp写入特定的txt文件
106            with open(os.path.join(rtt_root, 'failed_bsp_list.txt'), 'a') as file:
107                file.write(bsp + '\n')
108            # 打印失败的bsp的log,把它放到对应的文件名文bsp的txt文件中
109            with open(os.path.join(rtt_root, 'failed_bsp.log'), 'a') as file:
110                file.write(f'===================={bsp}====================\n')
111                for line in result_log:
112                    file.write(line)
113            print(f"::error::build {bsp} failed")
114            add_summary(f"- ❌ build {bsp} failed.")
115            success = False
116    else:
117        # 如果没有Kconfig直接执行scons
118        os.chdir(f'{rtt_root}/bsp/{bsp}')
119        run_cmd('scons', output_info=True)
120
121    # 删除packages文件夹
122    pkg_dir = os.path.join(rtt_root, 'bsp', bsp, 'packages')
123    shutil.rmtree(pkg_dir, ignore_errors=True)
124    #恢复到原目录
125    os.chdir(pwd)
126    return success
127
128#判断参数是否是2个
129if len(sys.argv) != 2:
130    usage()
131    sys.exit(0)
132#更新MDK等文件
133def update_project_file(project_dir):
134    if os.path.isfile(os.path.join(project_dir, 'template.Uv2')):
135        print('prepare MDK3 project file on ' + project_dir)
136        command = ' --target=mdk -s'
137        os.system('scons --directory=' + project_dir + command + ' > 1.txt')
138
139    if os.path.isfile(os.path.join(project_dir, 'template.uvproj')):
140        print('prepare MDK4 project file on ' + project_dir)
141        command = ' --target=mdk4 -s'
142        os.system('scons --directory=' + project_dir + command + ' > 1.txt')
143
144    if os.path.isfile(os.path.join(project_dir, 'template.uvprojx')):
145        print('prepare MDK5 project file on ' + project_dir)
146        command = ' --target=mdk5 -s'
147        os.system('scons --directory=' + project_dir + command + ' > 1.txt')
148
149    if os.path.isfile(os.path.join(project_dir, 'template.ewp')):
150        print('prepare IAR project file on ' + project_dir)
151        command = ' --target=iar -s'
152        os.system('scons --directory=' + project_dir + command + ' > 1.txt')
153
154#更新所有可以scons的文件夹文件,先执行menuconfig --silent 再执行scons --target=mdk5
155#处理带有sconstruct的文件夹
156def update_all_project_files(sconstruct_paths):
157    for projects in sconstruct_paths:
158        try:
159            # update rtconfig.h and .config
160            #执行menuconfig
161            if os.path.isfile(os.path.join(projects, 'Kconfig')):
162                if "win32" in sys.platform:
163                    retval = os.getcwd()
164                    os.chdir(projects)
165                    os.system("menuconfig --silent")
166                    os.chdir(retval)
167                else:
168                    os.system('scons --pyconfig-silent -C {0}'.format(projects))
169                print('==menuconfig=======projects='+ projects)
170            else:
171                print('==no kconfig=in==!!!!!=projects='+ projects)
172            # update mdk, IAR etc file
173            update_project_file(projects)
174        except Exception as e:
175            print("error message: {}".format(e))
176            sys.exit(-1)
177
178#找到带有Sconstruct的文件夹
179
180def find_sconstruct_paths(project_dir, exclude_paths, include_paths):
181    sconstruct_paths = []
182    bsp_detail_path = os.path.join(rtt_root, 'tools', 'ci', 'bsp_detail.yml')
183    if os.path.exists(bsp_detail_path):
184        with open(bsp_detail_path, 'r') as file:
185            bsp_detail = yaml.safe_load(file)
186    for root, dirs, files in os.walk(project_dir):
187        if include_paths:
188            if any(include_path in root for include_path in include_paths) and all(exclude_path not in root for exclude_path in exclude_paths):
189                if 'SConstruct' in files:
190                    bsp_name = os.path.relpath(root, bsp_root)
191                    if bsp_name in bsp_detail and bsp_detail[bsp_name].get('gcc') == 'arm-none-eabi-gcc':
192                        sconstruct_paths.append(root)
193        else:
194            if all(exclude_path not in root for exclude_path in exclude_paths):
195                if 'SConstruct' in files:
196                    bsp_name = os.path.relpath(root, bsp_root)
197                    if bsp_name in bsp_detail and bsp_detail[bsp_name].get('gcc') == 'arm-none-eabi-gcc':
198                        sconstruct_paths.append(root)
199    return sconstruct_paths
200
201#检查EXE命令是否存在,判断环境
202def check_command_availability(cmd):
203    """
204    Check if a command is available.
205    """
206    cmd_path = shutil.which(cmd)
207    if cmd_path is not None:
208        #print(f"{cmd} command is available at {cmd_path}")
209        return True
210    else:
211        print(f"{cmd} command is not available")
212        return False
213# Find the rt-thread root directory
214rtt_root = os.getcwd()
215while not os.path.exists(os.path.join(rtt_root, 'LICENSE')):
216    rtt_root = os.path.dirname(rtt_root)
217
218bsp_root = os.path.join(rtt_root, 'bsp')
219
220#需要排除的文件夹名字
221exclude_paths = ['templates', 'doc', 'libraries', 'Libraries', 'template']
222include_paths = []#['nrf5x','qemu-vexpress-a9', 'ESP32_C3','simulator']
223
224sconstruct_paths = find_sconstruct_paths(bsp_root, exclude_paths,include_paths)
225
226# get command options
227command = ''
228command_clean_flag = False
229
230print(rtt_root)
231
232if sys.argv[1] == 'all':
233    if os.path.exists(os.path.join(rtt_root, 'failed_bsp_list.txt')):
234        os.remove(os.path.join(rtt_root, 'failed_bsp_list.txt'))
235    if os.path.exists(os.path.join(rtt_root, 'failed_bsp.log')):
236        os.remove(os.path.join(rtt_root, 'failed_bsp.log'))
237    command = ' '
238#更新所有的工程
239    print('begin to update all the bsp projects')
240    update_all_project_files(sconstruct_paths)
241#iarbuild .\project.ewp -clean rt-thread
242elif sys.argv[1] == 'clean':
243    command = ' -c'
244    command_clean_flag = True
245    print('begin to clean all the bsp projects')
246# 执行所有其他IDE的 update 但是不编译,这个一般不会出错
247elif sys.argv[1] == 'update':
248    print('begin to update all the bsp projects')
249#更新所有的工程
250    update_all_project_files(sconstruct_paths)
251    print('finished!')
252    sys.exit(0)
253else:
254    usage()
255    sys.exit(0)
256
257if sconstruct_paths:
258    print("包含 'SConstruct' 文件的路径:")
259    for path in sconstruct_paths:
260        print(path)
261else:
262    print("未找到包含 'SConstruct' 文件的路径")
263
264#遍历所有的sconstruct_paths  路径中的文件夹
265
266def bsp_scons_worker(project_dir):
267    print('=========project_dir===='+ project_dir)
268#判断有没有SConstruct 文件,
269    if os.path.isfile(os.path.join(project_dir, 'SConstruct')):
270        print('==menuconfig=======rtt_root='+ rtt_root)
271        print('==project_dir=======project_dir='+ project_dir)
272
273        # 去掉 'bsp' 前面的三级目录
274        parts = project_dir.split(os.sep)
275        if 'bsp' in parts:
276            bsp_index = parts.index('bsp')
277            new_project_dir = os.sep.join(parts[bsp_index+1:])
278        else:
279            new_project_dir = project_dir
280        print('==project_dir=======new_project_dir='+ new_project_dir)
281        #开始编译bsp
282        build_bsp(new_project_dir)
283
284# 发现有keil相关的,执行keil相关的命令,先检查一下UV4.exe命令有没有,然后执行UV4.exe
285    if check_command_availability('UV4.exe') :
286        """
287        UV4.exe -b project.uvprojx -q -j0 -t rt-thread -o action_runner.log
288        ls
289        sleep 10
290        cat action_runner.log
291        """
292        if os.path.isfile(os.path.join(project_dir, 'template.uvprojx')):
293            if check_command_availability('UV4.exe'):
294                print('Start to build keil project======')
295                os.chdir(f'{project_dir}')
296                print('clean keil project======')
297                run_cmd('UV4.exe -c project.uvprojx -q')
298                ___, res = run_cmd('UV4.exe -b project.uvprojx -q -j0 -t rt-thread -o keil.log')
299                os.chdir(f'{rtt_root}')
300            else:
301                print('UV4.exe is not available, please check your keil installation')
302    if check_command_availability('iarbuild.exe') :
303        """
304        iarbuild .\project.ewp rt-thread
305        """
306        if os.path.isfile(os.path.join(project_dir, 'template.ewp')):
307            if check_command_availability('iarbuild.exe'):
308                print('Start to build iar project======')
309                os.chdir(f'{project_dir}')
310                ___, res = run_cmd('iarbuild .\project.ewp -clean rt-thread')
311                if res != 0:
312                    print('run clean failed!!')
313                ___, res = run_cmd('iarbuild .\project.ewp rt-thread > iar.log')
314                if res != 0:
315                    print('run_cmd1 failed!!')
316                os.chdir(f'{rtt_root}')
317            else:
318                print('iarbuild is not available, please check your iar installation')
319
320processes = []
321for project_dir in sconstruct_paths:
322    bsp_scons_worker(project_dir)
323    #p = Process(target=bsp_scons_worker, args=(project_dir,))
324    #p.start()
325    #processes.append(p)
326
327#for p in processes:
328#    p.join()  # 等待所有进程完成
329
330print('finished!')
331
332# 将failed_bsp_list.txt的内容追加到failed_bsp.log文件中
333if os.path.exists(os.path.join(rtt_root, 'failed_bsp_list.txt')):
334    with open(os.path.join(rtt_root, 'failed_bsp_list.txt'), 'r') as file:
335        failed_bsp_list = file.read()
336if os.path.exists(os.path.join(rtt_root, 'failed_bsp.log')):
337    with open(os.path.join(rtt_root, 'failed_bsp.log'), 'a') as file:
338        file.write(failed_bsp_list)