1# 2# File : mkdir.py 3# This file is part of RT-Thread RTOS 4# COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team 5# 6# This program is free software; you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation; either version 2 of the License, or 9# (at your option) any later version. 10# 11# This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15# 16# You should have received a copy of the GNU General Public License along 17# with this program; if not, write to the Free Software Foundation, Inc., 18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19# 20# Change Logs: 21# Date Author Notes 22# 2017-10-04 Bernard The first version 23# 2025-01-07 ZhaoCake components copy and gen doc 24# 2025-03-02 ZhaoCake Add MkDist_Strip 25 26 27import os 28import subprocess 29import shutil 30from shutil import ignore_patterns 31from SCons.Script import * 32 33def do_copy_file(src, dst): 34 # check source file 35 if not os.path.exists(src): 36 return 37 38 path = os.path.dirname(dst) 39 # mkdir if path not exist 40 if not os.path.exists(path): 41 os.makedirs(path) 42 43 shutil.copy2(src, dst) 44 45def do_copy_folder(src_dir, dst_dir, ignore=None): 46 import shutil 47 # check source directory 48 if not os.path.exists(src_dir): 49 return 50 51 try: 52 if os.path.exists(dst_dir): 53 shutil.rmtree(dst_dir) 54 except: 55 print('Deletes folder: %s failed.' % dst_dir) 56 return 57 58 shutil.copytree(src_dir, dst_dir, ignore = ignore) 59 60source_ext = ['c', 'h', 's', 'S', 'cpp', 'cxx', 'cc', 'xpm'] 61source_list = [] 62 63def walk_children(child): 64 global source_list 65 global source_ext 66 67 # print child 68 full_path = child.rfile().abspath 69 file_type = full_path.rsplit('.',1)[1] 70 #print file_type 71 if file_type in source_ext: 72 if full_path not in source_list: 73 source_list.append(full_path) 74 75 children = child.all_children() 76 if children != []: 77 for item in children: 78 walk_children(item) 79 80def walk_kconfig(RTT_ROOT, source_list): 81 for parent, dirnames, filenames in os.walk(RTT_ROOT): 82 if 'bsp' in parent: 83 continue 84 if '.git' in parent: 85 continue 86 if 'tools' in parent: 87 continue 88 89 if 'Kconfig' in filenames: 90 pathfile = os.path.join(parent, 'Kconfig') 91 source_list.append(pathfile) 92 if 'KConfig' in filenames: 93 pathfile = os.path.join(parent, 'KConfig') 94 source_list.append(pathfile) 95 96def bsp_copy_files(bsp_root, dist_dir): 97 # copy BSP files 98 do_copy_folder(os.path.join(bsp_root), dist_dir, 99 ignore_patterns('build', '__pycache__', 'dist', '*.pyc', '*.old', '*.map', 'rtthread.bin', '.sconsign.dblite', '*.elf', '*.axf', 'cconfig.h')) 100 101def bsp_update_sconstruct(dist_dir): 102 with open(os.path.join(dist_dir, 'SConstruct'), 'r') as f: 103 data = f.readlines() 104 with open(os.path.join(dist_dir, 'SConstruct'), 'w') as f: 105 for line in data: 106 if line.find('RTT_ROOT') != -1: 107 if line.find('sys.path') != -1: 108 f.write('# set RTT_ROOT\n') 109 f.write('if not os.getenv("RTT_ROOT"): \n RTT_ROOT="rt-thread"\n\n') 110 f.write(line) 111 112def bsp_update_kconfig_testcases(dist_dir): 113 # delete testcases in rt-thread/Kconfig 114 if not os.path.isfile(os.path.join(dist_dir, 'rt-thread/Kconfig')): 115 return 116 117 with open(os.path.join(dist_dir, 'rt-thread/Kconfig'), 'r') as f: 118 data = f.readlines() 119 with open(os.path.join(dist_dir, 'rt-thread/Kconfig'), 'w') as f: 120 for line in data: 121 if line.find('examples/utest/testcases/Kconfig') == -1: 122 f.write(line) 123 124def bsp_update_kconfig(dist_dir): 125 # change RTT_ROOT in Kconfig 126 if not os.path.isfile(os.path.join(dist_dir, 'Kconfig')): 127 return 128 129 with open(os.path.join(dist_dir, 'Kconfig'), 'r') as f: 130 data = f.readlines() 131 with open(os.path.join(dist_dir, 'Kconfig'), 'w') as f: 132 for line in data: 133 if line.find('RTT_DIR') != -1 and line.find(':=') != -1: 134 line = 'RTT_DIR := rt-thread\n' 135 f.write(line) 136 137def bsp_update_kconfig_library(dist_dir): 138 # change RTT_ROOT in Kconfig 139 if not os.path.isfile(os.path.join(dist_dir, 'Kconfig')): 140 return 141 142 with open(os.path.join(dist_dir, 'Kconfig'), 'r') as f: 143 data = f.readlines() 144 with open(os.path.join(dist_dir, 'Kconfig'), 'w') as f: 145 for line in data: 146 if line.find('source') != -1 and line.find('../libraries') != -1: 147 line = line.replace('../libraries', 'libraries') 148 f.write(line) 149 150 # change board/kconfig path 151 if not os.path.isfile(os.path.join(dist_dir, 'board/Kconfig')): 152 return 153 154 with open(os.path.join(dist_dir, 'board/Kconfig'), 'r') as f: 155 data = f.readlines() 156 with open(os.path.join(dist_dir, 'board/Kconfig'), 'w') as f: 157 for line in data: 158 if line.find('source') != -1 and line.find('../libraries') != -1: 159 line = line.replace('../libraries', 'libraries') 160 f.write(line) 161 162def zip_dist(dist_dir, dist_name): 163 import zipfile 164 165 zip_filename = os.path.join(dist_dir) 166 zip = zipfile.ZipFile(zip_filename + '.zip', 'w') 167 pre_len = len(os.path.dirname(dist_dir)) 168 169 for parent, dirnames, filenames in os.walk(dist_dir): 170 for filename in filenames: 171 pathfile = os.path.join(parent, filename) 172 arcname = pathfile[pre_len:].strip(os.path.sep) 173 zip.write(pathfile, arcname) 174 175 zip.close() 176 177def MkDist(program, BSP_ROOT, RTT_ROOT, Env, project_name, project_path): 178 print('make distribution....') 179 180 if project_path == None: 181 dist_dir = os.path.join(BSP_ROOT, 'dist', project_name) 182 else: 183 dist_dir = project_path 184 185 rtt_dir_path = os.path.join(dist_dir, 'rt-thread') 186 187 # copy BSP files 188 print('=> %s' % os.path.basename(BSP_ROOT)) 189 bsp_copy_files(BSP_ROOT, dist_dir) 190 191 # do bsp special dist handle 192 if 'dist_handle' in Env: 193 print("=> start dist handle") 194 dist_handle = Env['dist_handle'] 195 dist_handle(BSP_ROOT, dist_dir) 196 197 # copy tools directory 198 print('=> components') 199 do_copy_folder(os.path.join(RTT_ROOT, 'components'), os.path.join(rtt_dir_path, 'components')) 200 201 # skip documentation directory 202 # skip examples 203 204 # copy include directory 205 print('=> include') 206 do_copy_folder(os.path.join(RTT_ROOT, 'include'), os.path.join(rtt_dir_path, 'include')) 207 208 # copy all libcpu/ARCH directory 209 print('=> libcpu') 210 import rtconfig 211 do_copy_folder(os.path.join(RTT_ROOT, 'libcpu', rtconfig.ARCH), os.path.join(rtt_dir_path, 'libcpu', rtconfig.ARCH)) 212 do_copy_file(os.path.join(RTT_ROOT, 'libcpu', 'Kconfig'), os.path.join(rtt_dir_path, 'libcpu', 'Kconfig')) 213 do_copy_file(os.path.join(RTT_ROOT, 'libcpu', 'SConscript'), os.path.join(rtt_dir_path, 'libcpu', 'SConscript')) 214 215 # copy src directory 216 print('=> src') 217 do_copy_folder(os.path.join(RTT_ROOT, 'src'), os.path.join(rtt_dir_path, 'src')) 218 219 # copy tools directory 220 print('=> tools') 221 do_copy_folder(os.path.join(RTT_ROOT, 'tools'), os.path.join(rtt_dir_path, 'tools'), ignore_patterns('*.pyc')) 222 223 do_copy_file(os.path.join(RTT_ROOT, 'Kconfig'), os.path.join(rtt_dir_path, 'Kconfig')) 224 do_copy_file(os.path.join(RTT_ROOT, 'AUTHORS'), os.path.join(rtt_dir_path, 'AUTHORS')) 225 do_copy_file(os.path.join(RTT_ROOT, 'COPYING'), os.path.join(rtt_dir_path, 'COPYING')) 226 do_copy_file(os.path.join(RTT_ROOT, 'README.md'), os.path.join(rtt_dir_path, 'README.md')) 227 do_copy_file(os.path.join(RTT_ROOT, 'README_zh.md'), os.path.join(rtt_dir_path, 'README_zh.md')) 228 229 print('Update configuration files...') 230 # change RTT_ROOT in SConstruct 231 bsp_update_sconstruct(dist_dir) 232 # change RTT_ROOT in Kconfig 233 bsp_update_kconfig(dist_dir) 234 bsp_update_kconfig_library(dist_dir) 235 # delete testcases in Kconfig 236 bsp_update_kconfig_testcases(dist_dir) 237 238 target_project_type = GetOption('target') 239 if target_project_type: 240 child = subprocess.Popen('scons --target={} --project-name="{}"'.format(target_project_type, project_name), cwd=dist_dir, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) 241 stdout, stderr = child.communicate() 242 if child.returncode == 0: 243 print(stdout) 244 else: 245 print(stderr) 246 else: 247 print('suggest to use command scons --dist [--target=xxx] [--project-name="xxx"] [--project-path="xxx"]') 248 249 # make zip package 250 if project_path == None: 251 zip_dist(dist_dir, project_name) 252 253 print('dist project successfully!') 254 255def MkDist_Strip(program, BSP_ROOT, RTT_ROOT, env, project_name, project_path=None): 256 """Create a minimal distribution based on compile_commands.json but keeping all build system files. 257 First copies everything like MkDist, then only removes unused source files while keeping all headers. 258 """ 259 print('Making minimal distribution for project...') 260 261 if project_path == None: 262 dist_dir = os.path.join(BSP_ROOT, 'dist', project_name) 263 else: 264 dist_dir = project_path 265 266 # First do a full distribution copy 267 MkDist(program, BSP_ROOT, RTT_ROOT, env, project_name, project_path) 268 print('\n=> Starting source files cleanup...') 269 270 # Get the minimal required source paths 271 import compile_commands 272 used_paths = compile_commands.get_minimal_dist_paths( 273 os.path.join(BSP_ROOT, 'compile_commands.json'), 274 RTT_ROOT 275 ) 276 277 # Clean up RT-Thread directory except tools and build files 278 rt_thread_dir = os.path.join(dist_dir, 'rt-thread') 279 source_extensions = ('.c', '.cpp', '.cxx', '.cc', '.s', '.S') 280 281 removed_files = [] 282 removed_dirs = [] 283 284 for root, dirs, files in os.walk(rt_thread_dir, topdown=False): 285 rel_path = os.path.relpath(root, rt_thread_dir) 286 287 if rel_path.startswith('tools') or rel_path.startswith('include'): 288 continue 289 290 keep_files = { 291 'SConscript', 292 'Kconfig', 293 'Sconscript', 294 '.config', 295 'rtconfig.h' 296 } 297 298 for f in files: 299 if f in keep_files: 300 continue 301 302 if not f.endswith(source_extensions): 303 continue 304 305 file_path = os.path.join(root, f) 306 rel_file_path = os.path.relpath(file_path, rt_thread_dir) 307 dir_name = os.path.dirname(rel_file_path) 308 309 if dir_name not in used_paths and rel_file_path not in used_paths: 310 os.remove(file_path) 311 removed_files.append(rel_file_path) 312 313 # Remove empty directories 314 try: 315 if not os.listdir(root): 316 os.rmdir(root) 317 removed_dirs.append(rel_path) 318 except: 319 pass 320 321 # Output summary 322 if removed_files: 323 print("Removed {} unused source files".format(len(removed_files))) 324 log_file = os.path.join(dist_dir, 'cleanup.log') 325 with open(log_file, 'w') as f: 326 f.write("Removed source files:\n") 327 f.write('\n'.join(removed_files)) 328 if removed_dirs: 329 f.write("\n\nRemoved empty directories:\n") 330 f.write('\n'.join(removed_dirs)) 331 print("Details have been written to {}".format(log_file)) 332 else: 333 print("No unused source files found") 334 335 # Make zip package like MkDist 336 if project_path is None: 337 zip_dist(dist_dir, project_name) 338 print("Distribution package created: {}.zip".format(dist_dir)) 339 print('=> Distribution stripped successfully')