scu_builders.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. """Functions used to generate scu build source files during build time
  2. """
  3. import glob, os
  4. import math
  5. from methods import print_error
  6. from pathlib import Path
  7. from os.path import normpath, basename
  8. base_folder_path = str(Path(__file__).parent) + "/"
  9. base_folder_only = os.path.basename(os.path.normpath(base_folder_path))
  10. _verbose = False # Set manually for debug prints
  11. _scu_folders = set()
  12. _max_includes_per_scu = 1024
  13. def clear_out_stale_files(output_folder, extension, fresh_files):
  14. output_folder = os.path.abspath(output_folder)
  15. # print("clear_out_stale_files from folder: " + output_folder)
  16. if not os.path.isdir(output_folder):
  17. # folder does not exist or has not been created yet,
  18. # no files to clearout. (this is not an error)
  19. return
  20. for file in glob.glob(output_folder + "/*." + extension):
  21. file = Path(file)
  22. if not file in fresh_files:
  23. # print("removed stale file: " + str(file))
  24. os.remove(file)
  25. def folder_not_found(folder):
  26. abs_folder = base_folder_path + folder + "/"
  27. return not os.path.isdir(abs_folder)
  28. def find_files_in_folder(folder, sub_folder, include_list, extension, sought_exceptions, found_exceptions):
  29. abs_folder = base_folder_path + folder + "/" + sub_folder
  30. if not os.path.isdir(abs_folder):
  31. print_error(f'SCU: "{abs_folder}" not found.')
  32. return include_list, found_exceptions
  33. os.chdir(abs_folder)
  34. sub_folder_slashed = ""
  35. if sub_folder != "":
  36. sub_folder_slashed = sub_folder + "/"
  37. for file in glob.glob("*." + extension):
  38. simple_name = Path(file).stem
  39. if file.endswith(".gen.cpp"):
  40. continue
  41. li = '#include "' + folder + "/" + sub_folder_slashed + file + '"'
  42. if not simple_name in sought_exceptions:
  43. include_list.append(li)
  44. else:
  45. found_exceptions.append(li)
  46. return include_list, found_exceptions
  47. def write_output_file(file_count, include_list, start_line, end_line, output_folder, output_filename_prefix, extension):
  48. output_folder = os.path.abspath(output_folder)
  49. if not os.path.isdir(output_folder):
  50. # create
  51. os.mkdir(output_folder)
  52. if not os.path.isdir(output_folder):
  53. print_error(f'SCU: "{output_folder}" could not be created.')
  54. return
  55. if _verbose:
  56. print("SCU: Creating folder: %s" % output_folder)
  57. file_text = ""
  58. for l in range(start_line, end_line):
  59. if l < len(include_list):
  60. line = include_list[l]
  61. li = line + "\n"
  62. file_text += li
  63. num_string = ""
  64. if file_count > 0:
  65. num_string = "_" + str(file_count)
  66. short_filename = output_filename_prefix + num_string + ".gen." + extension
  67. output_filename = output_folder + "/" + short_filename
  68. output_path = Path(output_filename)
  69. if not output_path.exists() or output_path.read_text() != file_text:
  70. if _verbose:
  71. print("SCU: Generating: %s" % short_filename)
  72. output_path.write_text(file_text, encoding="utf8")
  73. elif _verbose:
  74. print("SCU: Generation not needed for: " + short_filename)
  75. return output_path
  76. def write_exception_output_file(file_count, exception_string, output_folder, output_filename_prefix, extension):
  77. output_folder = os.path.abspath(output_folder)
  78. if not os.path.isdir(output_folder):
  79. print_error(f"SCU: {output_folder} does not exist.")
  80. return
  81. file_text = exception_string + "\n"
  82. num_string = ""
  83. if file_count > 0:
  84. num_string = "_" + str(file_count)
  85. short_filename = output_filename_prefix + "_exception" + num_string + ".gen." + extension
  86. output_filename = output_folder + "/" + short_filename
  87. output_path = Path(output_filename)
  88. if not output_path.exists() or output_path.read_text() != file_text:
  89. if _verbose:
  90. print("SCU: Generating: " + short_filename)
  91. output_path.write_text(file_text, encoding="utf8")
  92. elif _verbose:
  93. print("SCU: Generation not needed for: " + short_filename)
  94. return output_path
  95. def find_section_name(sub_folder):
  96. # Construct a useful name for the section from the path for debug logging
  97. section_path = os.path.abspath(base_folder_path + sub_folder) + "/"
  98. folders = []
  99. folder = ""
  100. for i in range(8):
  101. folder = os.path.dirname(section_path)
  102. folder = os.path.basename(folder)
  103. if folder == base_folder_only:
  104. break
  105. folders.append(folder)
  106. section_path += "../"
  107. section_path = os.path.abspath(section_path) + "/"
  108. section_name = ""
  109. for n in range(len(folders)):
  110. section_name += folders[len(folders) - n - 1]
  111. if n != (len(folders) - 1):
  112. section_name += "_"
  113. return section_name
  114. # "folders" is a list of folders to add all the files from to add to the SCU
  115. # "section (like a module)". The name of the scu file will be derived from the first folder
  116. # (thus e.g. scene/3d becomes scu_scene_3d.gen.cpp)
  117. # "includes_per_scu" limits the number of includes in a single scu file.
  118. # This allows the module to be built in several translation units instead of just 1.
  119. # This will usually be slower to compile but will use less memory per compiler instance, which
  120. # is most relevant in release builds.
  121. # "sought_exceptions" are a list of files (without extension) that contain
  122. # e.g. naming conflicts, and are therefore not suitable for the scu build.
  123. # These will automatically be placed in their own separate scu file,
  124. # which is slow like a normal build, but prevents the naming conflicts.
  125. # Ideally in these situations, the source code should be changed to prevent naming conflicts.
  126. # "extension" will usually be cpp, but can also be set to c (for e.g. third party libraries that use c)
  127. def process_folder(folders, sought_exceptions=[], includes_per_scu=0, extension="cpp"):
  128. if len(folders) == 0:
  129. return
  130. # Construct the filename prefix from the FIRST folder name
  131. # e.g. "scene_3d"
  132. out_filename = find_section_name(folders[0])
  133. found_includes = []
  134. found_exceptions = []
  135. main_folder = folders[0]
  136. abs_main_folder = base_folder_path + main_folder
  137. # Keep a record of all folders that have been processed for SCU,
  138. # this enables deciding what to do when we call "add_source_files()"
  139. global _scu_folders
  140. _scu_folders.add(main_folder)
  141. # main folder (first)
  142. found_includes, found_exceptions = find_files_in_folder(
  143. main_folder, "", found_includes, extension, sought_exceptions, found_exceptions
  144. )
  145. # sub folders
  146. for d in range(1, len(folders)):
  147. found_includes, found_exceptions = find_files_in_folder(
  148. main_folder, folders[d], found_includes, extension, sought_exceptions, found_exceptions
  149. )
  150. found_includes = sorted(found_includes)
  151. # calculate how many lines to write in each file
  152. total_lines = len(found_includes)
  153. # adjust number of output files according to whether DEV or release
  154. num_output_files = 1
  155. if includes_per_scu == 0:
  156. includes_per_scu = _max_includes_per_scu
  157. else:
  158. if includes_per_scu > _max_includes_per_scu:
  159. includes_per_scu = _max_includes_per_scu
  160. num_output_files = max(math.ceil(total_lines / float(includes_per_scu)), 1)
  161. lines_per_file = math.ceil(total_lines / float(num_output_files))
  162. lines_per_file = max(lines_per_file, 1)
  163. start_line = 0
  164. file_number = 0
  165. # These do not vary throughout the loop
  166. output_folder = abs_main_folder + "/scu/"
  167. output_filename_prefix = "scu_" + out_filename
  168. fresh_files = set()
  169. for file_count in range(0, num_output_files):
  170. end_line = start_line + lines_per_file
  171. # special case to cover rounding error in final file
  172. if file_count == (num_output_files - 1):
  173. end_line = len(found_includes)
  174. fresh_file = write_output_file(
  175. file_count, found_includes, start_line, end_line, output_folder, output_filename_prefix, extension
  176. )
  177. fresh_files.add(fresh_file)
  178. start_line = end_line
  179. # Write the exceptions each in their own scu gen file,
  180. # so they can effectively compile in "old style / normal build".
  181. for exception_count in range(len(found_exceptions)):
  182. fresh_file = write_exception_output_file(
  183. exception_count, found_exceptions[exception_count], output_folder, output_filename_prefix, extension
  184. )
  185. fresh_files.add(fresh_file)
  186. # Clear out any stale file (usually we will be overwriting if necessary,
  187. # but we want to remove any that are pre-existing that will not be
  188. # overwritten, so as to not compile anything stale).
  189. clear_out_stale_files(output_folder, extension, fresh_files)
  190. def generate_scu_files(max_includes_per_scu):
  191. print("=============================")
  192. print("Single Compilation Unit Build")
  193. print("=============================")
  194. global _max_includes_per_scu
  195. _max_includes_per_scu = max_includes_per_scu
  196. print("SCU: Generating build files... (max includes per SCU: %d)" % _max_includes_per_scu)
  197. curr_folder = os.path.abspath("./")
  198. # check we are running from the correct folder
  199. if folder_not_found("core") or folder_not_found("platform") or folder_not_found("scene"):
  200. raise RuntimeError("scu_builders.py must be run from the godot folder.")
  201. return
  202. process_folder(["core"])
  203. process_folder(["core/crypto"])
  204. process_folder(["core/debugger"])
  205. process_folder(["core/extension"])
  206. process_folder(["core/input"])
  207. process_folder(["core/io"])
  208. process_folder(["core/math"])
  209. process_folder(["core/object"])
  210. process_folder(["core/os"])
  211. process_folder(["core/string"])
  212. process_folder(["core/variant"], ["variant_utility"])
  213. process_folder(["drivers/unix"])
  214. process_folder(["drivers/png"])
  215. process_folder(["editor"], ["file_system_dock", "editor_resource_preview"], 32)
  216. process_folder(["editor/debugger"])
  217. process_folder(["editor/debugger/debug_adapter"])
  218. process_folder(["editor/export"])
  219. process_folder(["editor/gui"])
  220. process_folder(["editor/import"])
  221. process_folder(["editor/import/3d"])
  222. process_folder(["editor/plugins"])
  223. process_folder(["editor/plugins/gizmos"])
  224. process_folder(["editor/plugins/tiles"])
  225. process_folder(["platform/android/export"])
  226. process_folder(["platform/ios/export"])
  227. process_folder(["platform/linuxbsd/export"])
  228. process_folder(["platform/macos/export"])
  229. process_folder(["platform/web/export"])
  230. process_folder(["platform/windows/export"])
  231. process_folder(["modules/gltf"])
  232. process_folder(["modules/gltf/structures"])
  233. process_folder(["modules/gltf/editor"])
  234. process_folder(["modules/gltf/extensions"])
  235. process_folder(["modules/gltf/extensions/physics"])
  236. process_folder(["modules/navigation"])
  237. process_folder(["modules/webrtc"])
  238. process_folder(["modules/websocket"])
  239. process_folder(["modules/gridmap"])
  240. process_folder(["modules/multiplayer"])
  241. process_folder(["modules/multiplayer/editor"])
  242. process_folder(["modules/openxr"], ["register_types"])
  243. process_folder(["modules/openxr/action_map"])
  244. process_folder(["modules/openxr/editor"])
  245. process_folder(["modules/csg"])
  246. process_folder(["modules/gdscript"])
  247. process_folder(["modules/gdscript/editor"])
  248. process_folder(["modules/gdscript/language_server"])
  249. process_folder(["scene/2d"])
  250. process_folder(["scene/2d/physics"])
  251. process_folder(["scene/2d/physics/joints"])
  252. process_folder(["scene/3d"])
  253. process_folder(["scene/3d/physics"])
  254. process_folder(["scene/3d/physics/joints"])
  255. process_folder(["scene/animation"])
  256. process_folder(["scene/gui"])
  257. process_folder(["scene/main"])
  258. process_folder(["scene/resources"])
  259. process_folder(["scene/resources/2d"])
  260. process_folder(["scene/resources/3d"])
  261. process_folder(["servers"])
  262. process_folder(["servers/rendering"])
  263. process_folder(["servers/rendering/storage"])
  264. process_folder(["servers/rendering/renderer_rd"])
  265. process_folder(["servers/rendering/renderer_rd/effects"])
  266. process_folder(["servers/rendering/renderer_rd/environment"])
  267. process_folder(["servers/rendering/renderer_rd/storage_rd"])
  268. process_folder(["servers/physics_2d"])
  269. process_folder(["servers/physics_3d"])
  270. process_folder(["servers/physics_3d/joints"])
  271. process_folder(["servers/audio"])
  272. process_folder(["servers/audio/effects"])
  273. # Finally change back the path to the calling folder
  274. os.chdir(curr_folder)
  275. if _verbose:
  276. print("SCU: Processed folders: %s" % sorted(_scu_folders))
  277. return _scu_folders