scu_builders.py 12 KB

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