1# 2# File : compile_commands.py 3# This file is part of RT-Thread RTOS 4# COPYRIGHT (C) 2006 - 2015, 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# 2025-03-02 ZhaoCake Create compile_commands.json without bear. 23 24import os 25import json 26import re 27from SCons.Script import * 28 29def collect_compile_info(env): 30 """收集编译命令和文件信息""" 31 print("=> Starting compile command collection") 32 compile_commands = [] 33 collected_files = set() 34 35 def get_command_string(source, target, env, for_signature): 36 """从SCons获取实际的编译命令""" 37 if env.get('CCCOMSTR'): 38 return env.get('CCCOM') 39 return '${CCCOM}' 40 41 def on_compile(target, source, env): 42 """编译动作的回调函数""" 43 print(f" Processing compilation for {len(source)} source files") 44 for src in source: 45 src_path = str(src) 46 if src_path in collected_files: 47 continue 48 49 collected_files.add(src_path) 50 directory = os.path.abspath(os.path.dirname(src_path)) 51 52 # 构建编译命令 53 command = env.subst(get_command_string(source, target, env, True)) 54 55 # 解析include路径 56 includes = [] 57 for path in env.get('CPPPATH', []): 58 includes.append('-I' + str(path)) 59 60 # 添加编译命令记录 61 entry = { 62 'directory': directory, 63 'command': f"{command} {' '.join(includes)}", 64 'file': os.path.abspath(src_path), 65 'output': str(target[0]) if target else '' 66 } 67 compile_commands.append(entry) 68 print(f" Added compile command for: {os.path.basename(src_path)}") 69 70 return on_compile, compile_commands 71 72def generate_compile_commands(env): 73 """生成compile_commands.json""" 74 print("=> Enabling compile commands generation...") 75 76 # 获取输出路径并存储到环境变量 77 output_path = GetOption('compile-commands-out') or 'compile_commands.json' 78 env['COMPILE_COMMANDS_OUT'] = output_path 79 print(f" Compile commands will be written to: {os.path.abspath(output_path)}") 80 81 # 注册编译回调并保存到环境变量 82 callback, compile_commands = collect_compile_info(env) 83 env['COMPILE_COMMANDS'] = compile_commands 84 env.AddPreAction('*.o', callback) 85 print(" Registered compile command collector") 86 87 # 定义后处理动作 88 def write_compile_commands(target, source, env): 89 print("\n=> [DEBUG] Entering write_compile_commands callback") 90 print(f" Target: {target}") 91 print(f" Source: {source}") 92 93 output_path = env.get('COMPILE_COMMANDS_OUT', 'compile_commands.json') 94 compile_commands = env.get('COMPILE_COMMANDS', []) 95 96 try: 97 if not compile_commands: 98 print("Warning: No compile commands collected, skipping file generation") 99 return 100 101 print(f"\n=> Writing compile_commands.json ({len(compile_commands)} entries)") 102 with open(output_path, 'w') as f: 103 json.dump(compile_commands, f, indent=2) 104 print(f"=> Successfully generated: {os.path.abspath(output_path)}") 105 106 except PermissionError: 107 print(f"\nError: Permission denied when writing to {output_path}") 108 print("Please check file permissions and try again") 109 except Exception as e: 110 print(f"\nError writing compile_commands.json: {str(e)}") 111 import traceback 112 traceback.print_exc() 113 114 # 使用None作为目标以确保总是执行 115 print("=> Adding post-build action for compile_commands generation") 116 env.AddPostAction(None, write_compile_commands) 117 118def parse_compile_paths(json_path, rt_thread_root=None): 119 """解析compile_commands.json并提取RT-Thread相关的包含路径 120 121 Args: 122 json_path: compile_commands.json的路径 123 rt_thread_root: RT-Thread根目录路径,默认使用环境变量RTT_ROOT 124 125 Returns: 126 dict: 包含以下键的字典: 127 'sources': RT-Thread源文件的相对路径列表 128 'includes': RT-Thread头文件目录的相对路径列表 129 """ 130 if rt_thread_root is None: 131 rt_thread_root = os.getenv('RTT_ROOT') 132 if not rt_thread_root: 133 raise ValueError("RT-Thread根目录未指定") 134 135 rt_thread_root = os.path.abspath(rt_thread_root) 136 result = { 137 'sources': set(), 138 'includes': set() 139 } 140 141 try: 142 with open(json_path, 'r') as f: 143 compile_commands = json.load(f) 144 145 for entry in compile_commands: 146 # 处理源文件 147 src_file = entry.get('file', '') 148 if src_file.startswith(rt_thread_root): 149 rel_path = os.path.relpath(src_file, rt_thread_root) 150 result['sources'].add(os.path.dirname(rel_path)) 151 152 # 处理包含路径 153 command = entry.get('command', '') 154 include_paths = [p[2:] for p in command.split() if p.startswith('-I')] 155 156 for inc_path in include_paths: 157 if inc_path.startswith(rt_thread_root): 158 rel_path = os.path.relpath(inc_path, rt_thread_root) 159 result['includes'].add(rel_path) 160 161 # 转换为排序列表 162 result['sources'] = sorted(list(result['sources'])) 163 result['includes'] = sorted(list(result['includes'])) 164 165 return result 166 167 except Exception as e: 168 print(f"Error parsing compile_commands.json: {str(e)}") 169 return None 170 171def get_minimal_dist_paths(json_path=None, rt_thread_root=None): 172 """获取最小化发布所需的路径 173 174 Args: 175 json_path: compile_commands.json的路径,默认为当前目录下的compile_commands.json 176 rt_thread_root: RT-Thread根目录路径 177 178 Returns: 179 list: 需要包含在发布包中的相对路径列表 180 """ 181 if json_path is None: 182 json_path = 'compile_commands.json' 183 184 paths = parse_compile_paths(json_path, rt_thread_root) 185 if not paths: 186 return [] 187 188 # 合并源码和头文件路径 189 all_paths = set(paths['sources']) | set(paths['includes']) 190 191 return sorted(list(all_paths)) 192