From db359af5c02207bf0f55ef59b40ea3309edbd890 Mon Sep 17 00:00:00 2001 From: ZhaoCake Date: Sun, 2 Mar 2025 17:27:23 +0800 Subject: [PATCH] [fix][feature]Default to the previous fully packaged logic, add a 'dist-strip' option for simplified packaging. --- tools/building.py | 35 +-- tools/compile_commands.py | 191 ++++++++++++++++ tools/mkdist.py | 444 +++++++++----------------------------- tools/options.py | 10 +- 4 files changed, 327 insertions(+), 353 deletions(-) create mode 100644 tools/compile_commands.py diff --git a/tools/building.py b/tools/building.py index 6054490cf6..a570bfb66c 100644 --- a/tools/building.py +++ b/tools/building.py @@ -24,6 +24,7 @@ # group definition. # 2024-04-21 Bernard Add toolchain detection in sdk packages # 2025-01-05 Bernard Add logging as Env['log'] +# 2025-03-02 ZhaoCake Add MkDist_Strip import os import sys @@ -504,7 +505,7 @@ def GetLocalDepend(options, depend): # for list type depend for item in depend: if item != '': - if not item in options or options[item] == 0: + if not depend in options or item == 0: building = False return building @@ -958,7 +959,7 @@ def GenTargetProject(program = None): ZigBuildProject(Env, Projects) def EndBuilding(target, program = None): - from mkdist import MkDist + from mkdist import MkDist, MkDist_Strip need_exit = False @@ -986,17 +987,25 @@ def EndBuilding(target, program = None): project_name = GetOption('project-name') project_path = GetOption('project-path') - if GetOption('make-dist') and program != None: - MkDist(program, BSP_ROOT, Rtt_Root, Env, project_name, project_path) - need_exit = True - if GetOption('make-dist-ide') and program != None: - import subprocess - if not isinstance(project_path, str) or len(project_path) == 0 : - project_path = os.path.join(BSP_ROOT, 'rt-studio-project') - MkDist(program, BSP_ROOT, Rtt_Root, Env, project_name, project_path) - child = subprocess.Popen('scons --target=eclipse --project-name="{}"'.format(project_name), cwd=project_path, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) - stdout, stderr = child.communicate() - need_exit = True + + # 合并处理打包相关选项 + if program != None: + if GetOption('make-dist'): + MkDist(program, BSP_ROOT, Rtt_Root, Env, project_name, project_path) + need_exit = True + elif GetOption('dist_strip'): + MkDist_Strip(program, BSP_ROOT, Rtt_Root, Env, project_name, project_path) + need_exit = True + elif GetOption('make-dist-ide'): + import subprocess + if not isinstance(project_path, str) or len(project_path) == 0: + project_path = os.path.join(BSP_ROOT, 'rt-studio-project') + MkDist(program, BSP_ROOT, Rtt_Root, Env, project_name, project_path) + child = subprocess.Popen('scons --target=eclipse --project-name="{}"'.format(project_name), + cwd=project_path, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) + stdout, stderr = child.communicate() + need_exit = True + if GetOption('cscope'): from cscope import CscopeDatabase CscopeDatabase(Projects) diff --git a/tools/compile_commands.py b/tools/compile_commands.py new file mode 100644 index 0000000000..6c3022009f --- /dev/null +++ b/tools/compile_commands.py @@ -0,0 +1,191 @@ +# +# File : compile_commands.py +# This file is part of RT-Thread RTOS +# COPYRIGHT (C) 2006 - 2015, RT-Thread Development Team +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Change Logs: +# Date Author Notes +# 2025-03-02 ZhaoCake Create compile_commands.json without bear. + +import os +import json +import re +from SCons.Script import * + +def collect_compile_info(env): + """收集编译命令和文件信息""" + print("=> Starting compile command collection") + compile_commands = [] + collected_files = set() + + def get_command_string(source, target, env, for_signature): + """从SCons获取实际的编译命令""" + if env.get('CCCOMSTR'): + return env.get('CCCOM') + return '${CCCOM}' + + def on_compile(target, source, env): + """编译动作的回调函数""" + print(f" Processing compilation for {len(source)} source files") + for src in source: + src_path = str(src) + if src_path in collected_files: + continue + + collected_files.add(src_path) + directory = os.path.abspath(os.path.dirname(src_path)) + + # 构建编译命令 + command = env.subst(get_command_string(source, target, env, True)) + + # 解析include路径 + includes = [] + for path in env.get('CPPPATH', []): + includes.append('-I' + str(path)) + + # 添加编译命令记录 + entry = { + 'directory': directory, + 'command': f"{command} {' '.join(includes)}", + 'file': os.path.abspath(src_path), + 'output': str(target[0]) if target else '' + } + compile_commands.append(entry) + print(f" Added compile command for: {os.path.basename(src_path)}") + + return on_compile, compile_commands + +def generate_compile_commands(env): + """生成compile_commands.json""" + print("=> Enabling compile commands generation...") + + # 获取输出路径并存储到环境变量 + output_path = GetOption('compile-commands-out') or 'compile_commands.json' + env['COMPILE_COMMANDS_OUT'] = output_path + print(f" Compile commands will be written to: {os.path.abspath(output_path)}") + + # 注册编译回调并保存到环境变量 + callback, compile_commands = collect_compile_info(env) + env['COMPILE_COMMANDS'] = compile_commands + env.AddPreAction('*.o', callback) + print(" Registered compile command collector") + + # 定义后处理动作 + def write_compile_commands(target, source, env): + print("\n=> [DEBUG] Entering write_compile_commands callback") + print(f" Target: {target}") + print(f" Source: {source}") + + output_path = env.get('COMPILE_COMMANDS_OUT', 'compile_commands.json') + compile_commands = env.get('COMPILE_COMMANDS', []) + + try: + if not compile_commands: + print("Warning: No compile commands collected, skipping file generation") + return + + print(f"\n=> Writing compile_commands.json ({len(compile_commands)} entries)") + with open(output_path, 'w') as f: + json.dump(compile_commands, f, indent=2) + print(f"=> Successfully generated: {os.path.abspath(output_path)}") + + except PermissionError: + print(f"\nError: Permission denied when writing to {output_path}") + print("Please check file permissions and try again") + except Exception as e: + print(f"\nError writing compile_commands.json: {str(e)}") + import traceback + traceback.print_exc() + + # 使用None作为目标以确保总是执行 + print("=> Adding post-build action for compile_commands generation") + env.AddPostAction(None, write_compile_commands) + +def parse_compile_paths(json_path, rt_thread_root=None): + """解析compile_commands.json并提取RT-Thread相关的包含路径 + + Args: + json_path: compile_commands.json的路径 + rt_thread_root: RT-Thread根目录路径,默认使用环境变量RTT_ROOT + + Returns: + dict: 包含以下键的字典: + 'sources': RT-Thread源文件的相对路径列表 + 'includes': RT-Thread头文件目录的相对路径列表 + """ + if rt_thread_root is None: + rt_thread_root = os.getenv('RTT_ROOT') + if not rt_thread_root: + raise ValueError("RT-Thread根目录未指定") + + rt_thread_root = os.path.abspath(rt_thread_root) + result = { + 'sources': set(), + 'includes': set() + } + + try: + with open(json_path, 'r') as f: + compile_commands = json.load(f) + + for entry in compile_commands: + # 处理源文件 + src_file = entry.get('file', '') + if src_file.startswith(rt_thread_root): + rel_path = os.path.relpath(src_file, rt_thread_root) + result['sources'].add(os.path.dirname(rel_path)) + + # 处理包含路径 + command = entry.get('command', '') + include_paths = [p[2:] for p in command.split() if p.startswith('-I')] + + for inc_path in include_paths: + if inc_path.startswith(rt_thread_root): + rel_path = os.path.relpath(inc_path, rt_thread_root) + result['includes'].add(rel_path) + + # 转换为排序列表 + result['sources'] = sorted(list(result['sources'])) + result['includes'] = sorted(list(result['includes'])) + + return result + + except Exception as e: + print(f"Error parsing compile_commands.json: {str(e)}") + return None + +def get_minimal_dist_paths(json_path=None, rt_thread_root=None): + """获取最小化发布所需的路径 + + Args: + json_path: compile_commands.json的路径,默认为当前目录下的compile_commands.json + rt_thread_root: RT-Thread根目录路径 + + Returns: + list: 需要包含在发布包中的相对路径列表 + """ + if json_path is None: + json_path = 'compile_commands.json' + + paths = parse_compile_paths(json_path, rt_thread_root) + if not paths: + return [] + + # 合并源码和头文件路径 + all_paths = set(paths['sources']) | set(paths['includes']) + + return sorted(list(all_paths)) diff --git a/tools/mkdist.py b/tools/mkdist.py index 74e2c3a6ed..6f64a2e8e6 100644 --- a/tools/mkdist.py +++ b/tools/mkdist.py @@ -21,13 +21,14 @@ # Date Author Notes # 2017-10-04 Bernard The first version # 2025-01-07 ZhaoCake components copy and gen doc +# 2025-03-02 ZhaoCake Add MkDist_Strip + import os import subprocess import shutil from shutil import ignore_patterns from SCons.Script import * -import time def do_copy_file(src, dst): # check source file @@ -42,6 +43,7 @@ def do_copy_file(src, dst): shutil.copy2(src, dst) def do_copy_folder(src_dir, dst_dir, ignore=None): + import shutil # check source directory if not os.path.exists(src_dir): return @@ -94,7 +96,7 @@ def walk_kconfig(RTT_ROOT, source_list): def bsp_copy_files(bsp_root, dist_dir): # copy BSP files do_copy_folder(os.path.join(bsp_root), dist_dir, - ignore_patterns('build','__pycache__','dist', '*.pyc', '*.old', '*.map', 'rtthread.bin', '.sconsign.dblite', '*.elf', '*.axf', 'cconfig.h')) + ignore_patterns('build', '__pycache__', 'dist', '*.pyc', '*.old', '*.map', 'rtthread.bin', '.sconsign.dblite', '*.elf', '*.axf', 'cconfig.h')) def bsp_update_sconstruct(dist_dir): with open(os.path.join(dist_dir, 'SConstruct'), 'r') as f: @@ -172,326 +174,6 @@ def zip_dist(dist_dir, dist_name): zip.close() -def parse_components_from_config(config_file): - """Parse enabled components from .config file""" - enabled_components = set() - - if not os.path.exists(config_file): - print(f"Error: {config_file} does not exist") - return enabled_components - - with open(config_file, 'r') as f: - for line in f: - line = line.strip() - # Skip empty lines and comments - if not line or line.startswith('#'): - continue - - if line.startswith('CONFIG_'): - if '=' in line: - config = line.split('=')[0][7:] # Remove CONFIG_ prefix - if config.startswith('RT_USING_'): - component = config[9:].lower() # Remove RT_USING_ prefix - enabled_components.add(component) - return enabled_components - -def scan_components_dir(RTT_ROOT): - """Scan component directory structure and generate component mapping""" - components_map = {} - components_root = os.path.join(RTT_ROOT, 'components') - - def parse_kconfig(kconfig_file): - """Parse configuration options from Kconfig file""" - components = set() - try: - with open(kconfig_file, 'r') as f: - content = f.read() - # Find configurations in the form of config RT_USING_XXX - import re - matches = re.finditer(r'config\s+RT_USING_(\w+)', content) - for match in matches: - component_name = match.group(1).lower() - components.add(component_name) - except Exception as e: - print(f"Warning: Failed to parse {kconfig_file}: {str(e)}") - return components - - def get_relative_path(full_path): - """Get path relative to RTT_ROOT""" - rel_path = os.path.relpath(os.path.dirname(full_path), RTT_ROOT) - # Skip if path is directly under components directory - if rel_path == 'components' or rel_path == os.path.join('components', ''): - return None - return rel_path - - # Scan all component directories - for root, dirs, files in os.walk(components_root): - if 'Kconfig' in files: - kconfig_path = os.path.join(root, 'Kconfig') - component_configs = parse_kconfig(kconfig_path) - rel_path = get_relative_path(kconfig_path) - - # Only add component if it has a valid sub-path - if rel_path: - for comp_name in component_configs: - components_map[comp_name] = rel_path - - return components_map - -def get_component_path(component_name, RTT_ROOT): - """Get actual path of component""" - # Get dynamic component mapping - dynamic_map = scan_components_dir(RTT_ROOT) - return dynamic_map.get(component_name) - -def generate_dist_doc(dist_dir, enabled_components, project_name, BSP_ROOT, RTT_ROOT): - """Generate distribution package documentation""" - doc_lines = [] # Store document content in a list - - # Basic information - doc_lines.extend([ - "# RT-Thread Distribution Package\n", - "\n## Basic Information\n\n", - f"- Project Name: {project_name}\n", - f"- Generation Time: {time.strftime('%Y-%m-%d %H:%M:%S')}\n", - f"- BSP: {os.path.basename(BSP_ROOT)}\n", - "\n## Components\n\n", - "### Included Components:\n\n" - ]) - - # Add component information - for comp in sorted(enabled_components): - path = get_component_path(comp, RTT_ROOT) - if path: - doc_lines.append(f"- {comp}\n - Path: {path}\n") - - # Add configuration information - doc_lines.extend(["\n## Configuration\n\n"]) - config_file = os.path.join(BSP_ROOT, '.config') - if os.path.exists(config_file): - doc_lines.extend([ - "### Main Configuration Items:\n\n```\n" - ]) - with open(config_file, 'r') as f: - for line in f: - if line.startswith('CONFIG_'): - doc_lines.append(line) - doc_lines.append("```\n") - - # Add simplified directory structure - doc_lines.extend(["\n## Directory Structure\n\n```\n"]) - - # Show only top-level directories - items = os.listdir(dist_dir) - items.sort() - for item in items: - if item.startswith('.') or item == 'dist': - continue - path = os.path.join(dist_dir, item) - if os.path.isdir(path): - doc_lines.append(f"├── {item}/\n") - else: - doc_lines.append(f"├── {item}\n") - - doc_lines.append("```\n") - - # Add build instructions - doc_lines.extend([""" -## Build Instructions - -1. Requirements: - - Python 3.x - - SCons build tool - - Appropriate cross-compiler toolchain - -2. Build Steps: - ```bash - scons - ``` - -3. Clean Build: - ```bash - scons -c - ``` - -## Notes - -1. Make sure the toolchain environment variables are properly set -2. To modify configuration, use menuconfig: - ```bash - scons --menuconfig - ``` - -## License - -See `COPYING` file for details. -"""]) - - # Write documentation - doc_file = os.path.join(dist_dir, 'dist_readme.md') - with open(doc_file, 'w', encoding='utf-8') as f: - f.writelines(doc_lines) - - print(f"=> Generated distribution documentation: {doc_file}") - -def is_text_file(filepath): - """Check if a file is a text file""" - text_extensions = { - '.h', '.c', '.cpp', '.hpp', '.S', '.s', '.asm', - '.txt', '.md', '.rst', '.ini', '.conf', - 'Kconfig', 'SConscript', 'SConstruct', - '.json', '.yml', '.yaml', - '.cmake', 'CMakeLists.txt', - '.py', '.sh', '.bat', - 'README', 'LICENSE', 'Makefile' - } - - # Check by extension - ext = os.path.splitext(filepath)[1].lower() - if ext in text_extensions or os.path.basename(filepath) in text_extensions: - return True - - # Additional check for files without extension - if '.' not in os.path.basename(filepath): - try: - with open(filepath, 'r', encoding='utf-8') as f: - f.read(1024) # Try to read as text - return True - except: - return False - - return False - -def copy_component_dependencies(src_path, dst_base, RTT_ROOT, copied_files=None): - """Copy component dependencies (text files) from parent directories""" - if copied_files is None: - copied_files = set() - - # Get relative path from RTT_ROOT - rel_path = os.path.relpath(src_path, RTT_ROOT) - parent_path = os.path.dirname(rel_path) - - # Process all parent directories until RTT_ROOT - while parent_path and parent_path != '.': - src_dir = os.path.join(RTT_ROOT, parent_path) - - # Copy all text files in the directory (not recursively) - for item in os.listdir(src_dir): - src_file = os.path.join(src_dir, item) - if os.path.isfile(src_file) and src_file not in copied_files: - if is_text_file(src_file): - dst_file = os.path.join(dst_base, parent_path, item) - dst_dir = os.path.dirname(dst_file) - - if not os.path.exists(dst_dir): - os.makedirs(dst_dir) - - do_copy_file(src_file, dst_file) - copied_files.add(src_file) - print(f' => copying {item} from {parent_path}') - - parent_path = os.path.dirname(parent_path) - - return copied_files - -def get_essential_paths(): - """Get essential paths that must be included""" - return { - 'components/libc/compilers', # Common compiler support - 'components/drivers/include', # Driver headers - 'components/drivers/core', # Driver core - 'components/utilities', # Utility functions - 'components/mm', # Memory management - 'components/legacy/ipc', # IPC support, not always used, but have no config option for it - } - -def copy_essential_paths(RTT_ROOT, rtt_dir_path, copied_files=None): - """Copy essential paths and their build files""" - if copied_files is None: - copied_files = set() - - print('=> copying essential paths') - for path in get_essential_paths(): - src_path = os.path.join(RTT_ROOT, path) - if os.path.exists(src_path): - dst_path = os.path.join(rtt_dir_path, path) - print(f' => copying {path}') - do_copy_folder(src_path, dst_path) - - # Copy build files for this path - copied_files = copy_component_dependencies(src_path, rtt_dir_path, RTT_ROOT, copied_files) - - return copied_files - -def copy_components_kconfig(RTT_ROOT, rtt_dir_path): - """Copy all Kconfig files under components directory""" - components_dir = os.path.join(RTT_ROOT, 'components') - print('=> copying components Kconfig files') - - # Walk through all directories under components - for root, dirs, files in os.walk(components_dir): - if 'Kconfig' in files: - # Get relative path from components directory - rel_path = os.path.relpath(root, RTT_ROOT) - src_file = os.path.join(root, 'Kconfig') - dst_file = os.path.join(rtt_dir_path, rel_path, 'Kconfig') - - # Create destination directory if not exists - dst_dir = os.path.dirname(dst_file) - if not os.path.exists(dst_dir): - os.makedirs(dst_dir) - - do_copy_file(src_file, dst_file) - print(f' => copying Kconfig from {rel_path}') - -def components_copy_files(RTT_ROOT, rtt_dir_path, config_file): - """Copy components based on configuration""" - print('=> components (selective copy)') - - # Copy all Kconfig files first - copy_components_kconfig(RTT_ROOT, rtt_dir_path) - - # Track copied build files to avoid duplication - copied_files = set() - - # Copy components/SConscript first - components_sconscript = os.path.join(RTT_ROOT, 'components', 'SConscript') - if os.path.exists(components_sconscript): - dst_dir = os.path.join(rtt_dir_path, 'components') - if not os.path.exists(dst_dir): - os.makedirs(dst_dir) - do_copy_file(components_sconscript, os.path.join(dst_dir, 'SConscript')) - copied_files.add(components_sconscript) - - # Copy essential paths first - copied_files = copy_essential_paths(RTT_ROOT, rtt_dir_path, copied_files) - - # Get enabled components - enabled_components = parse_components_from_config(config_file) - if not enabled_components: - print("Warning: No components found in config file") - return enabled_components - - # Copy each enabled component - for comp_name in enabled_components: - comp_path = get_component_path(comp_name, RTT_ROOT) - if comp_path: - src_path = os.path.join(RTT_ROOT, comp_path) - dst_path = os.path.join(rtt_dir_path, comp_path) - - if os.path.exists(src_path): - print(f' => copying {comp_name} from {comp_path}') - do_copy_folder(src_path, dst_path) - - # Copy parent directory build files - copied_files = copy_component_dependencies(src_path, rtt_dir_path, RTT_ROOT, copied_files) - else: - print(f"Warning: Component path not found: {src_path}") - else: - print(f"Note: Skipping system feature: {comp_name}") - - return enabled_components - def MkDist(program, BSP_ROOT, RTT_ROOT, Env, project_name, project_path): print('make distribution....') @@ -502,43 +184,42 @@ def MkDist(program, BSP_ROOT, RTT_ROOT, Env, project_name, project_path): rtt_dir_path = os.path.join(dist_dir, 'rt-thread') - # Copy BSP files + # copy BSP files print('=> %s' % os.path.basename(BSP_ROOT)) bsp_copy_files(BSP_ROOT, dist_dir) - # Do BSP special dist handle + # do bsp special dist handle if 'dist_handle' in Env: print("=> start dist handle") dist_handle = Env['dist_handle'] dist_handle(BSP_ROOT, dist_dir) - # Use new component copy function and get list of enabled components - config_file = os.path.join(BSP_ROOT, '.config') - enabled_components = components_copy_files(RTT_ROOT, rtt_dir_path, config_file) - - # Skip documentation directory - # Skip examples + # copy tools directory + print('=> components') + do_copy_folder(os.path.join(RTT_ROOT, 'components'), os.path.join(rtt_dir_path, 'components')) - # Copy include directory + # skip documentation directory + # skip examples + + # copy include directory print('=> include') do_copy_folder(os.path.join(RTT_ROOT, 'include'), os.path.join(rtt_dir_path, 'include')) - # Copy all libcpu/ARCH directory + # copy all libcpu/ARCH directory print('=> libcpu') import rtconfig do_copy_folder(os.path.join(RTT_ROOT, 'libcpu', rtconfig.ARCH), os.path.join(rtt_dir_path, 'libcpu', rtconfig.ARCH)) do_copy_file(os.path.join(RTT_ROOT, 'libcpu', 'Kconfig'), os.path.join(rtt_dir_path, 'libcpu', 'Kconfig')) do_copy_file(os.path.join(RTT_ROOT, 'libcpu', 'SConscript'), os.path.join(rtt_dir_path, 'libcpu', 'SConscript')) - # Copy src directory + # copy src directory print('=> src') do_copy_folder(os.path.join(RTT_ROOT, 'src'), os.path.join(rtt_dir_path, 'src')) - # Copy tools directory + # copy tools directory print('=> tools') do_copy_folder(os.path.join(RTT_ROOT, 'tools'), os.path.join(rtt_dir_path, 'tools'), ignore_patterns('*.pyc')) - # Copy necessary files do_copy_file(os.path.join(RTT_ROOT, 'Kconfig'), os.path.join(rtt_dir_path, 'Kconfig')) do_copy_file(os.path.join(RTT_ROOT, 'AUTHORS'), os.path.join(rtt_dir_path, 'AUTHORS')) do_copy_file(os.path.join(RTT_ROOT, 'COPYING'), os.path.join(rtt_dir_path, 'COPYING')) @@ -546,14 +227,14 @@ def MkDist(program, BSP_ROOT, RTT_ROOT, Env, project_name, project_path): do_copy_file(os.path.join(RTT_ROOT, 'README_zh.md'), os.path.join(rtt_dir_path, 'README_zh.md')) print('Update configuration files...') + # change RTT_ROOT in SConstruct bsp_update_sconstruct(dist_dir) + # change RTT_ROOT in Kconfig bsp_update_kconfig(dist_dir) bsp_update_kconfig_library(dist_dir) + # delete testcases in Kconfig bsp_update_kconfig_testcases(dist_dir) - # Generate documentation - generate_dist_doc(dist_dir, enabled_components, project_name+'-dist', BSP_ROOT, RTT_ROOT) - target_project_type = GetOption('target') if target_project_type: child = subprocess.Popen('scons --target={} --project-name="{}"'.format(target_project_type, project_name), cwd=dist_dir, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) @@ -570,3 +251,90 @@ def MkDist(program, BSP_ROOT, RTT_ROOT, Env, project_name, project_path): zip_dist(dist_dir, project_name) print('dist project successfully!') + +def MkDist_Strip(program, BSP_ROOT, RTT_ROOT, env, project_name, project_path=None): + """Create a minimal distribution based on compile_commands.json but keeping all build system files. + First copies everything like MkDist, then only removes unused source files while keeping all headers. + """ + print('Making minimal distribution for project...') + + if project_path == None: + dist_dir = os.path.join(BSP_ROOT, 'dist', project_name) + else: + dist_dir = project_path + + # First do a full distribution copy + MkDist(program, BSP_ROOT, RTT_ROOT, env, project_name, project_path) + print('\n=> Starting source files cleanup...') + + # Get the minimal required source paths + import compile_commands + used_paths = compile_commands.get_minimal_dist_paths( + os.path.join(BSP_ROOT, 'compile_commands.json'), + RTT_ROOT + ) + + # Clean up RT-Thread directory except tools and build files + rt_thread_dir = os.path.join(dist_dir, 'rt-thread') + source_extensions = ('.c', '.cpp', '.cxx', '.cc', '.s', '.S') + + removed_files = [] + removed_dirs = [] + + for root, dirs, files in os.walk(rt_thread_dir, topdown=False): + rel_path = os.path.relpath(root, rt_thread_dir) + + if rel_path.startswith('tools') or rel_path.startswith('include'): + continue + + keep_files = { + 'SConscript', + 'Kconfig', + 'Sconscript', + '.config', + 'rtconfig.h' + } + + for f in files: + if f in keep_files: + continue + + if not f.endswith(source_extensions): + continue + + file_path = os.path.join(root, f) + rel_file_path = os.path.relpath(file_path, rt_thread_dir) + dir_name = os.path.dirname(rel_file_path) + + if dir_name not in used_paths and rel_file_path not in used_paths: + os.remove(file_path) + removed_files.append(rel_file_path) + + # Remove empty directories + try: + if not os.listdir(root): + os.rmdir(root) + removed_dirs.append(rel_path) + except: + pass + + # Output summary + if removed_files: + print(f"Removed {len(removed_files)} unused source files") + log_file = os.path.join(dist_dir, 'cleanup.log') + with open(log_file, 'w') as f: + f.write("Removed source files:\n") + f.write('\n'.join(removed_files)) + if removed_dirs: + f.write("\n\nRemoved empty directories:\n") + f.write('\n'.join(removed_dirs)) + print(f"Details have been written to {log_file}") + else: + print("No unused source files found") + + # Make zip package like MkDist + if project_path is None: + zip_dist(dist_dir, project_name) + print(f"Distribution package created: {dist_dir}.zip") + + print('=> Distribution stripped successfully') \ No newline at end of file diff --git a/tools/options.py b/tools/options.py index 2e92de8bd3..2a19e1ac0e 100644 --- a/tools/options.py +++ b/tools/options.py @@ -20,7 +20,7 @@ # Change Logs: # Date Author Notes # 2022-04-20 WuGensheng Add Options to SCons -# +# 2025-03-02 ZhaoCake Add Options about compile_commands from SCons.Script import AddOption import platform @@ -147,4 +147,10 @@ def AddOptions(): help = 'View attachconfig or add attach to.config.'+\ 'e.g. scons --attach=? View all attachconfig for the current bsp.'+\ ' or scons --attach=component.cherryusb_cdc Set option component.cherryusb_cdc inside attachconfig to.config.'+\ - ' or scons --attach=default Restore.config and rtconfig to before attch was set.') \ No newline at end of file + ' or scons --attach=default Restore.config and rtconfig to before attch was set.') + AddOption('--dist-strip', + dest='dist_strip', + action='store_true', + default=False, + help='create minimal distribution based on compile_commands.json.'+\ + 'So you should run `bear -- scons` to generate compile_commands.json first.') \ No newline at end of file