core_builders.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. """Functions used to generate source files during build time"""
  2. from collections import OrderedDict
  3. from io import TextIOWrapper
  4. import methods
  5. # Generate disabled classes
  6. def disabled_class_builder(target, source, env):
  7. with methods.generated_wrapper(str(target[0])) as file:
  8. for c in source[0].read():
  9. if cs := c.strip():
  10. file.write(f"#define ClassDB_Disable_{cs} 1\n")
  11. # Generate version info
  12. def version_info_builder(target, source, env):
  13. with methods.generated_wrapper(str(target[0])) as file:
  14. file.write(
  15. """\
  16. #define GODOT_VERSION_SHORT_NAME "{short_name}"
  17. #define GODOT_VERSION_NAME "{name}"
  18. #define GODOT_VERSION_MAJOR {major}
  19. #define GODOT_VERSION_MINOR {minor}
  20. #define GODOT_VERSION_PATCH {patch}
  21. #define GODOT_VERSION_STATUS "{status}"
  22. #define GODOT_VERSION_BUILD "{build}"
  23. #define GODOT_VERSION_MODULE_CONFIG "{module_config}"
  24. #define GODOT_VERSION_WEBSITE "{website}"
  25. #define GODOT_VERSION_DOCS_BRANCH "{docs_branch}"
  26. #define GODOT_VERSION_DOCS_URL "https://docs.godotengine.org/en/" GODOT_VERSION_DOCS_BRANCH
  27. """.format(**source[0].read())
  28. )
  29. def version_hash_builder(target, source, env):
  30. with methods.generated_wrapper(str(target[0])) as file:
  31. file.write(
  32. """\
  33. #include "core/version.h"
  34. const char *const GODOT_VERSION_HASH = "{git_hash}";
  35. const uint64_t GODOT_VERSION_TIMESTAMP = {git_timestamp};
  36. """.format(**source[0].read())
  37. )
  38. def encryption_key_builder(target, source, env):
  39. src = source[0].read() or "0" * 64
  40. try:
  41. buffer = bytes.fromhex(src)
  42. if len(buffer) != 32:
  43. raise ValueError
  44. except ValueError:
  45. methods.print_error(
  46. f'Invalid AES256 encryption key, not 64 hexadecimal characters: "{src}".\n'
  47. "Unset `SCRIPT_AES256_ENCRYPTION_KEY` in your environment "
  48. "or make sure that it contains exactly 64 hexadecimal characters."
  49. )
  50. raise
  51. with methods.generated_wrapper(str(target[0])) as file:
  52. file.write(
  53. f"""\
  54. #include "core/config/project_settings.h"
  55. uint8_t script_encryption_key[32] = {{
  56. {methods.format_buffer(buffer, 1)}
  57. }};"""
  58. )
  59. def make_certs_header(target, source, env):
  60. buffer = methods.get_buffer(str(source[0]))
  61. decomp_size = len(buffer)
  62. buffer = methods.compress_buffer(buffer)
  63. with methods.generated_wrapper(str(target[0])) as file:
  64. # System certs path. Editor will use them if defined. (for package maintainers)
  65. file.write(f'#define _SYSTEM_CERTS_PATH "{source[2]}"\n')
  66. if source[1].read():
  67. # Defined here and not in env so changing it does not trigger a full rebuild.
  68. file.write(f"""\
  69. #define BUILTIN_CERTS_ENABLED
  70. inline constexpr int _certs_compressed_size = {len(buffer)};
  71. inline constexpr int _certs_uncompressed_size = {decomp_size};
  72. inline constexpr unsigned char _certs_compressed[] = {{
  73. {methods.format_buffer(buffer, 1)}
  74. }};
  75. """)
  76. def make_authors_header(target, source, env):
  77. SECTIONS = {
  78. "Project Founders": "AUTHORS_FOUNDERS",
  79. "Lead Developer": "AUTHORS_LEAD_DEVELOPERS",
  80. "Project Manager": "AUTHORS_PROJECT_MANAGERS",
  81. "Developers": "AUTHORS_DEVELOPERS",
  82. }
  83. buffer = methods.get_buffer(str(source[0]))
  84. reading = False
  85. with methods.generated_wrapper(str(target[0])) as file:
  86. def close_section():
  87. file.write("\tnullptr,\n};\n\n")
  88. for line in buffer.decode().splitlines():
  89. if line.startswith(" ") and reading:
  90. file.write(f'\t"{methods.to_escaped_cstring(line).strip()}",\n')
  91. elif line.startswith("## "):
  92. if reading:
  93. close_section()
  94. reading = False
  95. section = SECTIONS[line[3:].strip()]
  96. if section:
  97. file.write(f"inline constexpr const char *{section}[] = {{\n")
  98. reading = True
  99. if reading:
  100. close_section()
  101. def make_donors_header(target, source, env):
  102. SECTIONS = {
  103. "Patrons": "DONORS_PATRONS",
  104. "Platinum sponsors": "DONORS_SPONSORS_PLATINUM",
  105. "Gold sponsors": "DONORS_SPONSORS_GOLD",
  106. "Silver sponsors": "DONORS_SPONSORS_SILVER",
  107. "Diamond members": "DONORS_MEMBERS_DIAMOND",
  108. "Titanium members": "DONORS_MEMBERS_TITANIUM",
  109. "Platinum members": "DONORS_MEMBERS_PLATINUM",
  110. "Gold members": "DONORS_MEMBERS_GOLD",
  111. }
  112. buffer = methods.get_buffer(str(source[0]))
  113. reading = False
  114. with methods.generated_wrapper(str(target[0])) as file:
  115. def close_section():
  116. file.write("\tnullptr,\n};\n\n")
  117. for line in buffer.decode().splitlines():
  118. if line.startswith(" ") and reading:
  119. file.write(f'\t"{methods.to_escaped_cstring(line).strip()}",\n')
  120. elif line.startswith("## "):
  121. if reading:
  122. close_section()
  123. reading = False
  124. section = SECTIONS.get(line[3:].strip())
  125. if section:
  126. file.write(f"inline constexpr const char *{section}[] = {{\n")
  127. reading = True
  128. if reading:
  129. close_section()
  130. def make_license_header(target, source, env):
  131. src_copyright = str(source[0])
  132. src_license = str(source[1])
  133. class LicenseReader:
  134. def __init__(self, license_file: TextIOWrapper):
  135. self._license_file = license_file
  136. self.line_num = 0
  137. self.current = self.next_line()
  138. def next_line(self):
  139. line = self._license_file.readline()
  140. self.line_num += 1
  141. while line.startswith("#"):
  142. line = self._license_file.readline()
  143. self.line_num += 1
  144. self.current = line
  145. return line
  146. def next_tag(self):
  147. if ":" not in self.current:
  148. return ("", [])
  149. tag, line = self.current.split(":", 1)
  150. lines = [line.strip()]
  151. while self.next_line() and self.current.startswith(" "):
  152. lines.append(self.current.strip())
  153. return (tag, lines)
  154. projects = OrderedDict()
  155. license_list = []
  156. with open(src_copyright, "r", encoding="utf-8") as copyright_file:
  157. reader = LicenseReader(copyright_file)
  158. part = {}
  159. while reader.current:
  160. tag, content = reader.next_tag()
  161. if tag in ("Files", "Copyright", "License"):
  162. part[tag] = content[:]
  163. elif tag == "Comment" and part:
  164. # attach non-empty part to named project
  165. projects[content[0]] = projects.get(content[0], []) + [part]
  166. if not tag or not reader.current:
  167. # end of a paragraph start a new part
  168. if "License" in part and "Files" not in part:
  169. # no Files tag in this one, so assume standalone license
  170. license_list.append(part["License"])
  171. part = {}
  172. reader.next_line()
  173. data_list = []
  174. for project in iter(projects.values()):
  175. for part in project:
  176. part["file_index"] = len(data_list)
  177. data_list += part["Files"]
  178. part["copyright_index"] = len(data_list)
  179. data_list += part["Copyright"]
  180. with open(src_license, "r", encoding="utf-8") as file:
  181. license_text = file.read()
  182. with methods.generated_wrapper(str(target[0])) as file:
  183. file.write(f"""\
  184. inline constexpr const char *GODOT_LICENSE_TEXT = {{
  185. {methods.to_raw_cstring(license_text)}
  186. }};
  187. struct ComponentCopyrightPart {{
  188. const char *license;
  189. const char *const *files;
  190. const char *const *copyright_statements;
  191. int file_count;
  192. int copyright_count;
  193. }};
  194. struct ComponentCopyright {{
  195. const char *name;
  196. const ComponentCopyrightPart *parts;
  197. int part_count;
  198. }};
  199. """)
  200. file.write("inline constexpr const char *COPYRIGHT_INFO_DATA[] = {\n")
  201. for line in data_list:
  202. file.write(f'\t"{methods.to_escaped_cstring(line)}",\n')
  203. file.write("};\n\n")
  204. file.write("inline constexpr ComponentCopyrightPart COPYRIGHT_PROJECT_PARTS[] = {\n")
  205. part_index = 0
  206. part_indexes = {}
  207. for project_name, project in iter(projects.items()):
  208. part_indexes[project_name] = part_index
  209. for part in project:
  210. file.write(
  211. f'\t{{ "{methods.to_escaped_cstring(part["License"][0])}", '
  212. + f"&COPYRIGHT_INFO_DATA[{part['file_index']}], "
  213. + f"&COPYRIGHT_INFO_DATA[{part['copyright_index']}], "
  214. + f"{len(part['Files'])}, {len(part['Copyright'])} }},\n"
  215. )
  216. part_index += 1
  217. file.write("};\n\n")
  218. file.write(f"inline constexpr int COPYRIGHT_INFO_COUNT = {len(projects)};\n")
  219. file.write("inline constexpr ComponentCopyright COPYRIGHT_INFO[] = {\n")
  220. for project_name, project in iter(projects.items()):
  221. file.write(
  222. f'\t{{ "{methods.to_escaped_cstring(project_name)}", '
  223. + f"&COPYRIGHT_PROJECT_PARTS[{part_indexes[project_name]}], "
  224. + f"{len(project)} }},\n"
  225. )
  226. file.write("};\n\n")
  227. file.write(f"inline constexpr int LICENSE_COUNT = {len(license_list)};\n")
  228. file.write("inline constexpr const char *LICENSE_NAMES[] = {\n")
  229. for license in license_list:
  230. file.write(f'\t"{methods.to_escaped_cstring(license[0])}",\n')
  231. file.write("};\n\n")
  232. file.write("inline constexpr const char *LICENSE_BODIES[] = {\n\n")
  233. for license in license_list:
  234. to_raw = []
  235. for line in license[1:]:
  236. if line == ".":
  237. to_raw += [""]
  238. else:
  239. to_raw += [line]
  240. file.write(f"{methods.to_raw_cstring(to_raw)},\n\n")
  241. file.write("};\n\n")