compile_commands.py 7.0 KB

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