cmake.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. """
  2. * Copyright (c) 2006-2025 RT-Thread Development Team
  3. *
  4. * SPDX-License-Identifier: Apache-2.0
  5. *
  6. * Change Logs:
  7. * Date Author Notes
  8. * 2019-05-24 klivelinux first version
  9. * 2021-04-19 liukangcc add c++ support and libpath
  10. * 2021-06-25 Guozhanxin fix path issue
  11. * 2021-06-30 Guozhanxin add scons --target=cmake-armclang
  12. * 2022-03-16 liukangcc 通过 SCons生成 CMakefile.txt 使用相对路径
  13. * 2022-04-12 mysterywolf rtconfig.CROSS_TOOL->rtconfig.PLATFORM
  14. * 2022-04-29 SunJun8 默认开启生成编译数据库
  15. * 2024-03-18 wirano fix the issue of the missing link flags added in Sconscript
  16. * 2024-07-04 kaidegit Let cmake generator get more param from `rtconfig.py`
  17. * 2024-08-07 imi415 Updated CMake generator handles private macros, using OBJECT and INTERFACE libraries.
  18. * 2024-11-18 kaidegit fix processing groups with similar name
  19. * 2025-02-22 kaidegit fix missing some flags added in Sconscript
  20. * 2025-02-24 kaidegit remove some code that is unnecessary but takes time, get them from env
  21. """
  22. import os
  23. import sys
  24. import re
  25. import utils
  26. import rtconfig
  27. from utils import _make_path_relative
  28. from collections import defaultdict, Counter
  29. def GenerateCFiles(env, project, project_name):
  30. """
  31. Generate CMakeLists.txt files
  32. """
  33. PROJECT_NAME = project_name if project_name != "project" else "rtthread"
  34. tool_path_conv = defaultdict(lambda : {"name":"", "path": ""})
  35. tool_path_conv_helper = lambda tool: {"name": tool, "path": os.path.join(rtconfig.EXEC_PATH, tool).replace('\\', "/")}
  36. tool_path_conv["CMAKE_C_COMPILER"] = tool_path_conv_helper(rtconfig.CC)
  37. if 'CXX' in dir(rtconfig):
  38. tool_path_conv["CMAKE_CXX_COMPILER"] = tool_path_conv_helper(rtconfig.CXX)
  39. tool_path_conv["CMAKE_ASM_COMPILER"] = tool_path_conv_helper(rtconfig.AS)
  40. tool_path_conv["CMAKE_AR"] = tool_path_conv_helper(rtconfig.AR)
  41. tool_path_conv["CMAKE_LINKER"] = tool_path_conv_helper(rtconfig.LINK)
  42. if rtconfig.PLATFORM in ['gcc']:
  43. tool_path_conv["CMAKE_SIZE"] = tool_path_conv_helper(rtconfig.SIZE)
  44. tool_path_conv["CMAKE_OBJDUMP"] = tool_path_conv_helper(rtconfig.OBJDUMP)
  45. tool_path_conv["CMAKE_OBJCOPY"] = tool_path_conv_helper(rtconfig.OBJCPY)
  46. elif rtconfig.PLATFORM in ['armcc', 'armclang']:
  47. tool_path_conv["CMAKE_FROMELF"] = tool_path_conv_helper(rtconfig.FROMELF)
  48. CC = tool_path_conv["CMAKE_C_COMPILER"]["path"]
  49. CXX = tool_path_conv["CMAKE_CXX_COMPILER"]["path"]
  50. AS = tool_path_conv["CMAKE_ASM_COMPILER"]["path"]
  51. AR = tool_path_conv["CMAKE_AR"]["path"]
  52. LINK = tool_path_conv["CMAKE_LINKER"]["path"]
  53. SIZE = tool_path_conv["CMAKE_SIZE"]["path"]
  54. OBJDUMP = tool_path_conv["CMAKE_OBJDUMP"]["path"]
  55. OBJCOPY = tool_path_conv["CMAKE_OBJCOPY"]["path"]
  56. FROMELF = tool_path_conv["CMAKE_FROMELF"]["path"]
  57. CFLAGS = env['CFLAGS'].replace('\\', "/").replace('\"', "\\\"")
  58. if 'CXXFLAGS' in dir(rtconfig):
  59. cflag_str=''.join(env['CXXFLAGS'])
  60. CXXFLAGS = cflag_str.replace('\\', "/").replace('\"', "\\\"")
  61. else:
  62. CXXFLAGS = CFLAGS
  63. AFLAGS = env['ASFLAGS'].replace('\\', "/").replace('\"', "\\\"")
  64. LFLAGS = env['LINKFLAGS'].replace('\\', "/").replace('\"', "\\\"")
  65. POST_ACTION = rtconfig.POST_ACTION
  66. # replace the tool name with the cmake variable
  67. for cmake_var, each_tool in tool_path_conv.items():
  68. tool_name = each_tool['name']
  69. if tool_name == "": continue
  70. if "win32" in sys.platform:
  71. while f"{tool_name}.exe" in POST_ACTION: # find the tool with `.exe` suffix first
  72. POST_ACTION = POST_ACTION.replace(tool_name, "string_to_replace")
  73. while tool_name in POST_ACTION:
  74. POST_ACTION = POST_ACTION.replace(tool_name, "string_to_replace")
  75. while "string_to_replace" in POST_ACTION:
  76. POST_ACTION = POST_ACTION.replace("string_to_replace", f"${{{cmake_var}}}")
  77. # replace the `$TARGET` with `${CMAKE_PROJECT_NAME}.elf`
  78. while "$TARGET" in POST_ACTION:
  79. POST_ACTION = POST_ACTION.replace("$TARGET", "${CMAKE_PROJECT_NAME}.elf")
  80. # add COMMAAND before each command
  81. POST_ACTION = POST_ACTION.split('\n')
  82. POST_ACTION = [each_line.strip() for each_line in POST_ACTION]
  83. POST_ACTION = [f"\tCOMMAND {each_line}" for each_line in POST_ACTION if each_line != '']
  84. POST_ACTION = "\n".join(POST_ACTION)
  85. if "win32" in sys.platform:
  86. CC += ".exe"
  87. if CXX != '':
  88. CXX += ".exe"
  89. AS += ".exe"
  90. AR += ".exe"
  91. LINK += ".exe"
  92. if rtconfig.PLATFORM in ['gcc']:
  93. SIZE += ".exe"
  94. OBJDUMP += ".exe"
  95. OBJCOPY += ".exe"
  96. elif rtconfig.PLATFORM in ['armcc', 'armclang']:
  97. FROMELF += ".exe"
  98. if not os.path.exists(CC) or not os.path.exists(AS) or not os.path.exists(AR) or not os.path.exists(LINK):
  99. print("'Cannot found toolchain directory, please check RTT_CC and RTT_EXEC_PATH'")
  100. sys.exit(-1)
  101. with open("CMakeLists.txt", "w") as cm_file:
  102. cm_file.write("CMAKE_MINIMUM_REQUIRED(VERSION 3.10)\n\n")
  103. cm_file.write("SET(CMAKE_SYSTEM_NAME Generic)\n")
  104. cm_file.write("SET(CMAKE_SYSTEM_PROCESSOR " + rtconfig.CPU +")\n")
  105. cm_file.write("#SET(CMAKE_VERBOSE_MAKEFILE ON)\n\n")
  106. cm_file.write("SET(CMAKE_EXPORT_COMPILE_COMMANDS ON)\n\n")
  107. cm_file.write("SET(CMAKE_C_COMPILER \""+ CC + "\")\n")
  108. cm_file.write("SET(CMAKE_ASM_COMPILER \""+ AS + "\")\n")
  109. cm_file.write("SET(CMAKE_C_FLAGS \""+ CFLAGS + "\")\n")
  110. cm_file.write("SET(CMAKE_ASM_FLAGS \""+ AFLAGS + "\")\n")
  111. cm_file.write("SET(CMAKE_C_COMPILER_WORKS TRUE)\n\n")
  112. if CXX != '':
  113. cm_file.write("SET(CMAKE_CXX_COMPILER \""+ CXX + "\")\n")
  114. cm_file.write("SET(CMAKE_CXX_FLAGS \""+ CXXFLAGS + "\")\n")
  115. cm_file.write("SET(CMAKE_CXX_COMPILER_WORKS TRUE)\n\n")
  116. if rtconfig.PLATFORM in ['gcc']:
  117. cm_file.write("SET(CMAKE_OBJCOPY \""+ OBJCOPY + "\")\n")
  118. cm_file.write("SET(CMAKE_SIZE \""+ SIZE + "\")\n\n")
  119. elif rtconfig.PLATFORM in ['armcc', 'armclang']:
  120. cm_file.write("SET(CMAKE_FROMELF \""+ FROMELF + "\")\n\n")
  121. LINKER_FLAGS = ''
  122. LINKER_LIBS = ''
  123. if rtconfig.PLATFORM in ['gcc']:
  124. LINKER_FLAGS += '-T'
  125. elif rtconfig.PLATFORM in ['armcc', 'armclang']:
  126. LINKER_FLAGS += '--scatter'
  127. for group in project:
  128. if 'LIBPATH' in group.keys():
  129. for f in group['LIBPATH']:
  130. LINKER_LIBS += ' --userlibpath ' + f.replace("\\", "/")
  131. for group in project:
  132. if 'LIBS' in group.keys():
  133. for f in group['LIBS']:
  134. LINKER_LIBS += ' ' + f.replace("\\", "/") + '.lib'
  135. cm_file.write("SET(CMAKE_EXE_LINKER_FLAGS \""+ re.sub(LINKER_FLAGS + '(\s*)', LINKER_FLAGS + ' ${CMAKE_SOURCE_DIR}/', LFLAGS) + LINKER_LIBS + "\")\n\n")
  136. # get the c/cpp standard version from compilation flags
  137. # not support the version with alphabet in `-std` param yet
  138. pattern = re.compile('-std=[\w+]+')
  139. c_standard = 11
  140. if '-std=' in CFLAGS:
  141. c_standard = re.search(pattern, CFLAGS).group(0)
  142. c_standard = "".join([each for each in c_standard if each.isdigit()])
  143. else:
  144. print(f"Cannot find the param of the c standard in build flag, set to default {c_standard}")
  145. cm_file.write(f"SET(CMAKE_C_STANDARD {c_standard})\n")
  146. if CXX != '':
  147. cpp_standard = 17
  148. if '-std=' in CXXFLAGS:
  149. cpp_standard = re.search(pattern, CXXFLAGS).group(0)
  150. cpp_standard = "".join([each for each in cpp_standard if each.isdigit()])
  151. else:
  152. print(f"Cannot find the param of the cpp standard in build flag, set to default {cpp_standard}")
  153. cm_file.write(f"SET(CMAKE_CXX_STANDARD {cpp_standard})\n")
  154. cm_file.write('\n')
  155. cm_file.write(f"PROJECT({PROJECT_NAME} C {'CXX' if CXX != '' else ''} ASM)\n")
  156. cm_file.write('\n')
  157. cm_file.write("INCLUDE_DIRECTORIES(\n")
  158. for i in env['CPPPATH']:
  159. # use relative path
  160. path = _make_path_relative(os.getcwd(), i)
  161. cm_file.write( "\t" + path.replace("\\", "/") + "\n")
  162. cm_file.write(")\n\n")
  163. cm_file.write("ADD_DEFINITIONS(\n")
  164. for i in env['CPPDEFINES']:
  165. cm_file.write("\t-D" + i + "\n")
  166. cm_file.write(")\n\n")
  167. libgroups = []
  168. interfacelibgroups = []
  169. for group in project:
  170. if group['name'] == 'Applications':
  171. continue
  172. # When a group is provided without sources, add it to the <INTERFACE> library list
  173. if len(group['src']) == 0:
  174. interfacelibgroups.append(group)
  175. else:
  176. libgroups.append(group)
  177. # Process groups whose names differ only in capitalization.
  178. # (Groups have same name should be merged into one before)
  179. for group in libgroups:
  180. group['alias'] = group['name'].lower()
  181. names = [group['alias'] for group in libgroups]
  182. counter = Counter(names)
  183. names = [name for name in names if counter[name] > 1]
  184. for group in libgroups:
  185. if group['alias'] in names:
  186. counter[group['alias']] -= 1
  187. group['alias'] = f"{group['name']}_{counter[group['alias']]}"
  188. print(f"Renamed {group['name']} to {group['alias']}")
  189. group['name'] = group['alias']
  190. cm_file.write("# Library source files\n")
  191. for group in project:
  192. cm_file.write("SET(RT_{:s}_SOURCES\n".format(group['name'].upper()))
  193. for f in group['src']:
  194. # use relative path
  195. path = _make_path_relative(os.getcwd(), os.path.normpath(f.rfile().abspath))
  196. cm_file.write( "\t" + path.replace("\\", "/") + "\n" )
  197. cm_file.write(")\n\n")
  198. cm_file.write("# Library search paths\n")
  199. for group in libgroups + interfacelibgroups:
  200. if not 'LIBPATH' in group.keys():
  201. continue
  202. if len(group['LIBPATH']) == 0:
  203. continue
  204. cm_file.write("SET(RT_{:s}_LINK_DIRS\n".format(group['name'].upper()))
  205. for f in group['LIBPATH']:
  206. cm_file.write("\t"+ f.replace("\\", "/") + "\n" )
  207. cm_file.write(")\n\n")
  208. cm_file.write("# Library local macro definitions\n")
  209. for group in libgroups:
  210. if not 'LOCAL_CPPDEFINES' in group.keys():
  211. continue
  212. if len(group['LOCAL_CPPDEFINES']) == 0:
  213. continue
  214. cm_file.write("SET(RT_{:s}_DEFINES\n".format(group['name'].upper()))
  215. for f in group['LOCAL_CPPDEFINES']:
  216. cm_file.write("\t"+ f.replace("\\", "/") + "\n" )
  217. cm_file.write(")\n\n")
  218. cm_file.write("# Library dependencies\n")
  219. for group in libgroups + interfacelibgroups:
  220. if not 'LIBS' in group.keys():
  221. continue
  222. if len(group['LIBS']) == 0:
  223. continue
  224. cm_file.write("SET(RT_{:s}_LIBS\n".format(group['name'].upper()))
  225. for f in group['LIBS']:
  226. cm_file.write("\t"+ "{}\n".format(f.replace("\\", "/")))
  227. cm_file.write(")\n\n")
  228. cm_file.write("# Libraries\n")
  229. for group in libgroups:
  230. cm_file.write("ADD_LIBRARY(rtt_{:s} OBJECT ${{RT_{:s}_SOURCES}})\n"
  231. .format(group['name'], group['name'].upper()))
  232. cm_file.write("\n")
  233. cm_file.write("# Interface libraries\n")
  234. for group in interfacelibgroups:
  235. cm_file.write("ADD_LIBRARY(rtt_{:s} INTERFACE)\n".format(group['name']))
  236. cm_file.write("\n")
  237. cm_file.write("# Private macros\n")
  238. for group in libgroups:
  239. if not 'LOCAL_CPPDEFINES' in group.keys():
  240. continue
  241. if len(group['LOCAL_CPPDEFINES']) == 0:
  242. continue
  243. cm_file.write("TARGET_COMPILE_DEFINITIONS(rtt_{:s} PRIVATE ${{RT_{:s}_DEFINES}})\n"
  244. .format(group['name'], group['name'].upper()))
  245. cm_file.write("\n")
  246. cm_file.write("# Interface library search paths\n")
  247. if rtconfig.PLATFORM in ['gcc']:
  248. for group in libgroups:
  249. if not 'LIBPATH' in group.keys():
  250. continue
  251. if len(group['LIBPATH']) == 0:
  252. continue
  253. cm_file.write("TARGET_LINK_DIRECTORIES(rtt_{:s} INTERFACE ${{RT_{:s}_LINK_DIRS}})\n"
  254. .format(group['name'], group['name'].upper()))
  255. for group in libgroups:
  256. if not 'LIBS' in group.keys():
  257. continue
  258. if len(group['LIBS']) == 0:
  259. continue
  260. cm_file.write("TARGET_LINK_LIBRARIES(rtt_{:s} INTERFACE ${{RT_{:s}_LIBS}})\n"
  261. .format(group['name'], group['name'].upper()))
  262. cm_file.write("\n")
  263. cm_file.write("ADD_EXECUTABLE(${CMAKE_PROJECT_NAME}.elf ${RT_APPLICATIONS_SOURCES})\n")
  264. cm_file.write("TARGET_LINK_LIBRARIES(${CMAKE_PROJECT_NAME}.elf\n")
  265. for group in libgroups + interfacelibgroups:
  266. cm_file.write("\trtt_{:s}\n".format(group['name']))
  267. cm_file.write(")\n\n")
  268. cm_file.write("ADD_CUSTOM_COMMAND(TARGET ${CMAKE_PROJECT_NAME}.elf POST_BUILD \n" + POST_ACTION + '\n)\n')
  269. # auto inclue `custom.cmake` for user custom settings
  270. custom_cmake = \
  271. '''
  272. # if custom.cmake is exist, add it
  273. if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/custom.cmake)
  274. include(${CMAKE_CURRENT_SOURCE_DIR}/custom.cmake)
  275. endif()
  276. '''
  277. custom_cmake = custom_cmake.split('\n')
  278. custom_cmake = [each.strip() for each in custom_cmake]
  279. custom_cmake = "\n".join(custom_cmake)
  280. cm_file.write(custom_cmake)
  281. return
  282. def CMakeProject(env, project, project_name):
  283. print('Update setting files for CMakeLists.txt...')
  284. GenerateCFiles(env, project, project_name)
  285. print('Done!')
  286. return