SCons: Make builders prettier, utilize constexpr

This commit is contained in:
Thaddeus Crews 2024-10-29 14:23:08 -05:00
parent 7893202fba
commit be429eb404
No known key found for this signature in database
GPG Key ID: 62181B86FE9E5D84
21 changed files with 510 additions and 723 deletions

View File

@ -968,8 +968,6 @@ if env.editor_build:
print_error("Not all modules required by editor builds are enabled.") print_error("Not all modules required by editor builds are enabled.")
Exit(255) Exit(255)
env.version_info = methods.get_version_info(env.module_version_string)
env["PROGSUFFIX_WRAP"] = suffix + env.module_version_string + ".console" + env["PROGSUFFIX"] env["PROGSUFFIX_WRAP"] = suffix + env.module_version_string + ".console" + env["PROGSUFFIX"]
env["PROGSUFFIX"] = suffix + env.module_version_string + env["PROGSUFFIX"] env["PROGSUFFIX"] = suffix + env.module_version_string + env["PROGSUFFIX"]
env["OBJSUFFIX"] = suffix + env["OBJSUFFIX"] env["OBJSUFFIX"] = suffix + env["OBJSUFFIX"]

View File

@ -167,10 +167,9 @@ env.add_source_files(env.core_sources, "*.cpp")
# Generate disabled classes # Generate disabled classes
def disabled_class_builder(target, source, env): def disabled_class_builder(target, source, env):
with methods.generated_wrapper(target) as file: with methods.generated_wrapper(str(target[0])) as file:
for c in source[0].read(): for c in source[0].read():
cs = c.strip() if cs := c.strip():
if cs != "":
file.write(f"#define ClassDB_Disable_{cs} 1\n") file.write(f"#define ClassDB_Disable_{cs} 1\n")
@ -179,7 +178,7 @@ env.CommandNoCache("disabled_classes.gen.h", env.Value(env.disabled_classes), en
# Generate version info # Generate version info
def version_info_builder(target, source, env): def version_info_builder(target, source, env):
with methods.generated_wrapper(target) as file: with methods.generated_wrapper(str(target[0])) as file:
file.write( file.write(
"""\ """\
#define VERSION_SHORT_NAME "{short_name}" #define VERSION_SHORT_NAME "{short_name}"
@ -193,35 +192,37 @@ def version_info_builder(target, source, env):
#define VERSION_WEBSITE "{website}" #define VERSION_WEBSITE "{website}"
#define VERSION_DOCS_BRANCH "{docs_branch}" #define VERSION_DOCS_BRANCH "{docs_branch}"
#define VERSION_DOCS_URL "https://docs.godotengine.org/en/" VERSION_DOCS_BRANCH #define VERSION_DOCS_URL "https://docs.godotengine.org/en/" VERSION_DOCS_BRANCH
""".format(**env.version_info) """.format(**source[0].read())
) )
env.CommandNoCache("version_generated.gen.h", env.Value(env.version_info), env.Run(version_info_builder)) env.CommandNoCache(
"version_generated.gen.h",
env.Value(methods.get_version_info(env.module_version_string)),
env.Run(version_info_builder),
)
# Generate version hash # Generate version hash
def version_hash_builder(target, source, env): def version_hash_builder(target, source, env):
with methods.generated_wrapper(target) as file: with methods.generated_wrapper(str(target[0])) as file:
file.write( file.write(
"""\ """\
#include "core/version.h" #include "core/version.h"
const char *const VERSION_HASH = "{git_hash}"; const char *const VERSION_HASH = "{git_hash}";
const uint64_t VERSION_TIMESTAMP = {git_timestamp}; const uint64_t VERSION_TIMESTAMP = {git_timestamp};
""".format(**env.version_info) """.format(**source[0].read())
) )
gen_hash = env.CommandNoCache( gen_hash = env.CommandNoCache("version_hash.gen.cpp", env.Value(methods.get_git_info()), env.Run(version_hash_builder))
"version_hash.gen.cpp", env.Value(env.version_info["git_hash"]), env.Run(version_hash_builder)
)
env.add_source_files(env.core_sources, gen_hash) env.add_source_files(env.core_sources, gen_hash)
# Generate AES256 script encryption key # Generate AES256 script encryption key
def encryption_key_builder(target, source, env): def encryption_key_builder(target, source, env):
with methods.generated_wrapper(target) as file: with methods.generated_wrapper(str(target[0])) as file:
file.write( file.write(
f"""\ f"""\
#include "core/config/project_settings.h" #include "core/config/project_settings.h"
@ -251,30 +252,21 @@ env.add_source_files(env.core_sources, gen_encrypt)
# Certificates # Certificates
env.Depends(
"#core/io/certs_compressed.gen.h",
["#thirdparty/certs/ca-certificates.crt", env.Value(env["builtin_certs"]), env.Value(env["system_certs_path"])],
)
env.CommandNoCache( env.CommandNoCache(
"#core/io/certs_compressed.gen.h", "#core/io/certs_compressed.gen.h",
"#thirdparty/certs/ca-certificates.crt", ["#thirdparty/certs/ca-certificates.crt", env.Value(env["builtin_certs"]), env.Value(env["system_certs_path"])],
env.Run(core_builders.make_certs_header), env.Run(core_builders.make_certs_header),
) )
# Authors # Authors
env.Depends("#core/authors.gen.h", "../AUTHORS.md") env.CommandNoCache("#core/authors.gen.h", "#AUTHORS.md", env.Run(core_builders.make_authors_header))
env.CommandNoCache("#core/authors.gen.h", "../AUTHORS.md", env.Run(core_builders.make_authors_header))
# Donors # Donors
env.Depends("#core/donors.gen.h", "../DONORS.md") env.CommandNoCache("#core/donors.gen.h", "#DONORS.md", env.Run(core_builders.make_donors_header))
env.CommandNoCache("#core/donors.gen.h", "../DONORS.md", env.Run(core_builders.make_donors_header))
# License # License
env.Depends("#core/license.gen.h", ["../COPYRIGHT.txt", "../LICENSE.txt"])
env.CommandNoCache( env.CommandNoCache(
"#core/license.gen.h", "#core/license.gen.h", ["#COPYRIGHT.txt", "#LICENSE.txt"], env.Run(core_builders.make_license_header)
["../COPYRIGHT.txt", "../LICENSE.txt"],
env.Run(core_builders.make_license_header),
) )
# Chain load SCsubs # Chain load SCsubs

View File

@ -1,171 +1,104 @@
"""Functions used to generate source files during build time""" """Functions used to generate source files during build time"""
import zlib from collections import OrderedDict
from io import TextIOWrapper
import methods
def escape_string(s):
def charcode_to_c_escapes(c):
rev_result = []
while c >= 256:
c, low = (c // 256, c % 256)
rev_result.append("\\%03o" % low)
rev_result.append("\\%03o" % c)
return "".join(reversed(rev_result))
result = ""
if isinstance(s, str):
s = s.encode("utf-8")
for c in s:
if not (32 <= c < 127) or c in (ord("\\"), ord('"')):
result += charcode_to_c_escapes(c)
else:
result += chr(c)
return result
def make_certs_header(target, source, env): def make_certs_header(target, source, env):
src = str(source[0]) buffer = methods.get_buffer(str(source[0]))
dst = str(target[0]) decomp_size = len(buffer)
with open(src, "rb") as f, open(dst, "w", encoding="utf-8", newline="\n") as g: buffer = methods.compress_buffer(buffer)
buf = f.read()
decomp_size = len(buf)
# Use maximum zlib compression level to further reduce file size
# (at the cost of initial build times).
buf = zlib.compress(buf, zlib.Z_BEST_COMPRESSION)
g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n")
g.write("#ifndef CERTS_COMPRESSED_GEN_H\n")
g.write("#define CERTS_COMPRESSED_GEN_H\n")
with methods.generated_wrapper(str(target[0])) as file:
# System certs path. Editor will use them if defined. (for package maintainers) # System certs path. Editor will use them if defined. (for package maintainers)
path = env["system_certs_path"] file.write('#define _SYSTEM_CERTS_PATH "{}"\n'.format(env["system_certs_path"]))
g.write('#define _SYSTEM_CERTS_PATH "%s"\n' % str(path))
if env["builtin_certs"]: if env["builtin_certs"]:
# Defined here and not in env so changing it does not trigger a full rebuild. # Defined here and not in env so changing it does not trigger a full rebuild.
g.write("#define BUILTIN_CERTS_ENABLED\n") file.write(f"""\
g.write("static const int _certs_compressed_size = " + str(len(buf)) + ";\n") #define BUILTIN_CERTS_ENABLED
g.write("static const int _certs_uncompressed_size = " + str(decomp_size) + ";\n")
g.write("static const unsigned char _certs_compressed[] = {\n") inline constexpr int _certs_compressed_size = {len(buffer)};
for i in range(len(buf)): inline constexpr int _certs_uncompressed_size = {decomp_size};
g.write("\t" + str(buf[i]) + ",\n") inline constexpr unsigned char _certs_compressed[] = {{
g.write("};\n") {methods.format_buffer(buffer, 1)}
g.write("#endif // CERTS_COMPRESSED_GEN_H") }};
""")
def make_authors_header(target, source, env): def make_authors_header(target, source, env):
sections = [ SECTIONS = {
"Project Founders", "Project Founders": "AUTHORS_FOUNDERS",
"Lead Developer", "Lead Developer": "AUTHORS_LEAD_DEVELOPERS",
"Project Manager", "Project Manager": "AUTHORS_PROJECT_MANAGERS",
"Developers", "Developers": "AUTHORS_DEVELOPERS",
] }
sections_id = [ buffer = methods.get_buffer(str(source[0]))
"AUTHORS_FOUNDERS", reading = False
"AUTHORS_LEAD_DEVELOPERS",
"AUTHORS_PROJECT_MANAGERS",
"AUTHORS_DEVELOPERS",
]
src = str(source[0]) with methods.generated_wrapper(str(target[0])) as file:
dst = str(target[0])
with open(src, "r", encoding="utf-8") as f, open(dst, "w", encoding="utf-8", newline="\n") as g:
g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n")
g.write("#ifndef AUTHORS_GEN_H\n")
g.write("#define AUTHORS_GEN_H\n")
reading = False
def close_section(): def close_section():
g.write("\t0\n") file.write("\tnullptr,\n};\n\n")
g.write("};\n")
for line in f: for line in buffer.decode().splitlines():
if reading: if line.startswith(" ") and reading:
if line.startswith(" "): file.write(f'\t"{methods.to_escaped_cstring(line).strip()}",\n')
g.write('\t"' + escape_string(line.strip()) + '",\n') elif line.startswith("## "):
continue
if line.startswith("## "):
if reading: if reading:
close_section() close_section()
reading = False reading = False
for section, section_id in zip(sections, sections_id): section = SECTIONS[line[3:].strip()]
if line.strip().endswith(section): if section:
current_section = escape_string(section_id) file.write(f"inline constexpr const char *{section}[] = {{\n")
reading = True reading = True
g.write("const char *const " + current_section + "[] = {\n")
break
if reading: if reading:
close_section() close_section()
g.write("#endif // AUTHORS_GEN_H\n")
def make_donors_header(target, source, env): def make_donors_header(target, source, env):
sections = [ SECTIONS = {
"Patrons", "Patrons": "DONORS_PATRONS",
"Platinum sponsors", "Platinum sponsors": "DONORS_SPONSORS_PLATINUM",
"Gold sponsors", "Gold sponsors": "DONORS_SPONSORS_GOLD",
"Silver sponsors", "Silver sponsors": "DONORS_SPONSORS_SILVER",
"Diamond members", "Diamond members": "DONORS_MEMBERS_DIAMOND",
"Titanium members", "Titanium members": "DONORS_MEMBERS_TITANIUM",
"Platinum members", "Platinum members": "DONORS_MEMBERS_PLATINUM",
"Gold members", "Gold members": "DONORS_MEMBERS_GOLD",
] }
sections_id = [ buffer = methods.get_buffer(str(source[0]))
"DONORS_PATRONS", reading = False
"DONORS_SPONSORS_PLATINUM",
"DONORS_SPONSORS_GOLD",
"DONORS_SPONSORS_SILVER",
"DONORS_MEMBERS_DIAMOND",
"DONORS_MEMBERS_TITANIUM",
"DONORS_MEMBERS_PLATINUM",
"DONORS_MEMBERS_GOLD",
]
src = str(source[0]) with methods.generated_wrapper(str(target[0])) as file:
dst = str(target[0])
with open(src, "r", encoding="utf-8") as f, open(dst, "w", encoding="utf-8", newline="\n") as g:
g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n")
g.write("#ifndef DONORS_GEN_H\n")
g.write("#define DONORS_GEN_H\n")
reading = False
def close_section(): def close_section():
g.write("\t0\n") file.write("\tnullptr,\n};\n\n")
g.write("};\n")
for line in f: for line in buffer.decode().splitlines():
if reading >= 0: if line.startswith(" ") and reading:
if line.startswith(" "): file.write(f'\t"{methods.to_escaped_cstring(line).strip()}",\n')
g.write('\t"' + escape_string(line.strip()) + '",\n') elif line.startswith("## "):
continue
if line.startswith("## "):
if reading: if reading:
close_section() close_section()
reading = False reading = False
for section, section_id in zip(sections, sections_id): section = SECTIONS.get(line[3:].strip())
if line.strip().endswith(section): if section:
current_section = escape_string(section_id) file.write(f"inline constexpr const char *{section}[] = {{\n")
reading = True reading = True
g.write("const char *const " + current_section + "[] = {\n")
break
if reading: if reading:
close_section() close_section()
g.write("#endif // DONORS_GEN_H\n")
def make_license_header(target, source, env): def make_license_header(target, source, env):
src_copyright = str(source[0]) src_copyright = str(source[0])
src_license = str(source[1]) src_license = str(source[1])
dst = str(target[0])
class LicenseReader: class LicenseReader:
def __init__(self, license_file): def __init__(self, license_file: TextIOWrapper):
self._license_file = license_file self._license_file = license_file
self.line_num = 0 self.line_num = 0
self.current = self.next_line() self.current = self.next_line()
@ -188,9 +121,7 @@ def make_license_header(target, source, env):
lines.append(self.current.strip()) lines.append(self.current.strip())
return (tag, lines) return (tag, lines)
from collections import OrderedDict projects = OrderedDict()
projects: dict = OrderedDict()
license_list = [] license_list = []
with open(src_copyright, "r", encoding="utf-8") as copyright_file: with open(src_copyright, "r", encoding="utf-8") as copyright_file:
@ -212,7 +143,7 @@ def make_license_header(target, source, env):
part = {} part = {}
reader.next_line() reader.next_line()
data_list: list = [] data_list = []
for project in iter(projects.values()): for project in iter(projects.values()):
for part in project: for part in project:
part["file_index"] = len(data_list) part["file_index"] = len(data_list)
@ -220,96 +151,76 @@ def make_license_header(target, source, env):
part["copyright_index"] = len(data_list) part["copyright_index"] = len(data_list)
data_list += part["Copyright"] data_list += part["Copyright"]
with open(dst, "w", encoding="utf-8", newline="\n") as f: with open(src_license, "r", encoding="utf-8") as file:
f.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") license_text = file.read()
f.write("#ifndef LICENSE_GEN_H\n")
f.write("#define LICENSE_GEN_H\n")
f.write("const char *const GODOT_LICENSE_TEXT =")
with open(src_license, "r", encoding="utf-8") as license_file: with methods.generated_wrapper(str(target[0])) as file:
for line in license_file: file.write(f"""\
escaped_string = escape_string(line.strip()) inline constexpr const char *GODOT_LICENSE_TEXT = {{
f.write('\n\t\t"' + escaped_string + '\\n"') {methods.to_raw_cstring(license_text)}
f.write(";\n\n") }};
f.write( struct ComponentCopyrightPart {{
"struct ComponentCopyrightPart {\n" const char *license;
"\tconst char *license;\n" const char *const *files;
"\tconst char *const *files;\n" const char *const *copyright_statements;
"\tconst char *const *copyright_statements;\n" int file_count;
"\tint file_count;\n" int copyright_count;
"\tint copyright_count;\n" }};
"};\n\n"
)
f.write( struct ComponentCopyright {{
"struct ComponentCopyright {\n" const char *name;
"\tconst char *name;\n" const ComponentCopyrightPart *parts;
"\tconst ComponentCopyrightPart *parts;\n" int part_count;
"\tint part_count;\n" }};
"};\n\n"
)
f.write("const char *const COPYRIGHT_INFO_DATA[] = {\n") """)
file.write("inline constexpr const char *COPYRIGHT_INFO_DATA[] = {\n")
for line in data_list: for line in data_list:
f.write('\t"' + escape_string(line) + '",\n') file.write(f'\t"{methods.to_escaped_cstring(line)}",\n')
f.write("};\n\n") file.write("};\n\n")
f.write("const ComponentCopyrightPart COPYRIGHT_PROJECT_PARTS[] = {\n") file.write("inline constexpr ComponentCopyrightPart COPYRIGHT_PROJECT_PARTS[] = {\n")
part_index = 0 part_index = 0
part_indexes = {} part_indexes = {}
for project_name, project in iter(projects.items()): for project_name, project in iter(projects.items()):
part_indexes[project_name] = part_index part_indexes[project_name] = part_index
for part in project: for part in project:
f.write( file.write(
'\t{ "' f'\t{{ "{methods.to_escaped_cstring(part["License"][0])}", '
+ escape_string(part["License"][0]) + f"&COPYRIGHT_INFO_DATA[{part['file_index']}], "
+ '", ' + f"&COPYRIGHT_INFO_DATA[{part['copyright_index']}], "
+ "&COPYRIGHT_INFO_DATA[" + f"{len(part['Files'])}, {len(part['Copyright'])} }},\n"
+ str(part["file_index"])
+ "], "
+ "&COPYRIGHT_INFO_DATA["
+ str(part["copyright_index"])
+ "], "
+ str(len(part["Files"]))
+ ", "
+ str(len(part["Copyright"]))
+ " },\n"
) )
part_index += 1 part_index += 1
f.write("};\n\n") file.write("};\n\n")
f.write("const int COPYRIGHT_INFO_COUNT = " + str(len(projects)) + ";\n") file.write(f"inline constexpr int COPYRIGHT_INFO_COUNT = {len(projects)};\n")
f.write("const ComponentCopyright COPYRIGHT_INFO[] = {\n") file.write("inline constexpr ComponentCopyright COPYRIGHT_INFO[] = {\n")
for project_name, project in iter(projects.items()): for project_name, project in iter(projects.items()):
f.write( file.write(
'\t{ "' f'\t{{ "{methods.to_escaped_cstring(project_name)}", '
+ escape_string(project_name) + f"&COPYRIGHT_PROJECT_PARTS[{part_indexes[project_name]}], "
+ '", ' + f"{len(project)} }},\n"
+ "&COPYRIGHT_PROJECT_PARTS["
+ str(part_indexes[project_name])
+ "], "
+ str(len(project))
+ " },\n"
) )
f.write("};\n\n") file.write("};\n\n")
f.write("const int LICENSE_COUNT = " + str(len(license_list)) + ";\n") file.write(f"inline constexpr int LICENSE_COUNT = {len(license_list)};\n")
f.write("const char *const LICENSE_NAMES[] = {\n") file.write("inline constexpr const char *LICENSE_NAMES[] = {\n")
for license in license_list: for license in license_list:
f.write('\t"' + escape_string(license[0]) + '",\n') file.write(f'\t"{methods.to_escaped_cstring(license[0])}",\n')
f.write("};\n\n") file.write("};\n\n")
f.write("const char *const LICENSE_BODIES[] = {\n\n") file.write("inline constexpr const char *LICENSE_BODIES[] = {\n\n")
for license in license_list: for license in license_list:
to_raw = []
for line in license[1:]: for line in license[1:]:
if line == ".": if line == ".":
f.write('\t"\\n"\n') to_raw += [""]
else: else:
f.write('\t"' + escape_string(line) + '\\n"\n') to_raw += [line]
f.write('\t"",\n\n') file.write(f"{methods.to_raw_cstring(to_raw)},\n\n")
f.write("};\n\n") file.write("};\n\n")
f.write("#endif // LICENSE_GEN_H\n")

View File

@ -1,52 +1,37 @@
import zlib import methods
def run(target, source, env): def run(target, source, env):
src = str(source[0]) buffer = methods.get_buffer(str(source[0]))
dst = str(target[0]) decomp_size = len(buffer)
with open(src, "rb") as f, open(dst, "w", encoding="utf-8", newline="\n") as g: buffer = methods.compress_buffer(buffer)
buf = f.read()
decomp_size = len(buf)
# Use maximum zlib compression level to further reduce file size
# (at the cost of initial build times).
buf = zlib.compress(buf, zlib.Z_BEST_COMPRESSION)
g.write(
"""/* THIS FILE IS GENERATED DO NOT EDIT */
#pragma once
with methods.generated_wrapper(str(target[0])) as file:
file.write(f"""\
#ifdef TOOLS_ENABLED #ifdef TOOLS_ENABLED
#include "core/io/compression.h" #include "core/io/compression.h"
#include "core/io/file_access.h" #include "core/io/file_access.h"
#include "core/string/ustring.h" #include "core/string/ustring.h"
""" inline constexpr int _gdextension_interface_data_compressed_size = {len(buffer)};
) inline constexpr int _gdextension_interface_data_uncompressed_size = {decomp_size};
inline constexpr unsigned char _gdextension_interface_data_compressed[] = {{
{methods.format_buffer(buffer, 1)}
}};
g.write("static const int _gdextension_interface_data_compressed_size = " + str(len(buf)) + ";\n") class GDExtensionInterfaceDump {{
g.write("static const int _gdextension_interface_data_uncompressed_size = " + str(decomp_size) + ";\n") public:
g.write("static const unsigned char _gdextension_interface_data_compressed[] = {\n") static void generate_gdextension_interface_file(const String &p_path) {{
for i in range(len(buf)): Ref<FileAccess> fa = FileAccess::open(p_path, FileAccess::WRITE);
g.write("\t" + str(buf[i]) + ",\n") ERR_FAIL_COND_MSG(fa.is_null(), vformat("Cannot open file '%s' for writing.", p_path));
g.write("};\n") Vector<uint8_t> data;
data.resize(_gdextension_interface_data_uncompressed_size);
g.write( int ret = Compression::decompress(data.ptrw(), _gdextension_interface_data_uncompressed_size, _gdextension_interface_data_compressed, _gdextension_interface_data_compressed_size, Compression::MODE_DEFLATE);
""" ERR_FAIL_COND_MSG(ret == -1, "Compressed file is corrupt.");
class GDExtensionInterfaceDump { fa->store_buffer(data.ptr(), data.size());
public: }};
static void generate_gdextension_interface_file(const String &p_path) { }};
Ref<FileAccess> fa = FileAccess::open(p_path, FileAccess::WRITE);
ERR_FAIL_COND_MSG(fa.is_null(), vformat("Cannot open file '%s' for writing.", p_path));
Vector<uint8_t> data;
data.resize(_gdextension_interface_data_uncompressed_size);
int ret = Compression::decompress(data.ptrw(), _gdextension_interface_data_uncompressed_size, _gdextension_interface_data_compressed, _gdextension_interface_data_compressed_size, Compression::MODE_DEFLATE);
ERR_FAIL_COND_MSG(ret == -1, "Compressed file is corrupt.");
fa->store_buffer(data.ptr(), data.size());
};
};
#endif // TOOLS_ENABLED #endif // TOOLS_ENABLED
""" """)
)

View File

@ -2,18 +2,22 @@
from collections import OrderedDict from collections import OrderedDict
import methods
def make_default_controller_mappings(target, source, env): def make_default_controller_mappings(target, source, env):
dst = str(target[0]) with methods.generated_wrapper(str(target[0])) as file:
with open(dst, "w", encoding="utf-8", newline="\n") as g: file.write("""\
g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") #include "core/input/default_controller_mappings.h"
g.write('#include "core/typedefs.h"\n')
g.write('#include "core/input/default_controller_mappings.h"\n') #include "core/typedefs.h"
""")
# ensure mappings have a consistent order # ensure mappings have a consistent order
platform_mappings: dict = OrderedDict() platform_mappings = OrderedDict()
for src_path in source: for src_path in map(str, source):
with open(str(src_path), "r", encoding="utf-8") as f: with open(src_path, "r", encoding="utf-8") as f:
# read mapping file and skip header # read mapping file and skip header
mapping_file_lines = f.readlines()[2:] mapping_file_lines = f.readlines()[2:]
@ -32,28 +36,28 @@ def make_default_controller_mappings(target, source, env):
line_parts = line.split(",") line_parts = line.split(",")
guid = line_parts[0] guid = line_parts[0]
if guid in platform_mappings[current_platform]: if guid in platform_mappings[current_platform]:
g.write( file.write(
"// WARNING: DATABASE {} OVERWROTE PRIOR MAPPING: {} {}\n".format( "// WARNING: DATABASE {} OVERWROTE PRIOR MAPPING: {} {}\n".format(
src_path, current_platform, platform_mappings[current_platform][guid] src_path, current_platform, platform_mappings[current_platform][guid]
) )
) )
platform_mappings[current_platform][guid] = line platform_mappings[current_platform][guid] = line
platform_variables = { PLATFORM_VARIABLES = {
"Linux": "#ifdef LINUXBSD_ENABLED", "Linux": "LINUXBSD",
"Windows": "#ifdef WINDOWS_ENABLED", "Windows": "WINDOWS",
"Mac OS X": "#ifdef MACOS_ENABLED", "Mac OS X": "MACOS",
"Android": "#ifdef ANDROID_ENABLED", "Android": "ANDROID",
"iOS": "#ifdef IOS_ENABLED", "iOS": "IOS",
"Web": "#ifdef WEB_ENABLED", "Web": "WEB",
} }
g.write("const char* DefaultControllerMappings::mappings[] = {\n") file.write("const char *DefaultControllerMappings::mappings[] = {\n")
for platform, mappings in platform_mappings.items(): for platform, mappings in platform_mappings.items():
variable = platform_variables[platform] variable = PLATFORM_VARIABLES[platform]
g.write("{}\n".format(variable)) file.write(f"#ifdef {variable}_ENABLED\n")
for mapping in mappings.values(): for mapping in mappings.values():
g.write('\t"{}",\n'.format(mapping)) file.write(f'\t"{mapping}",\n')
g.write("#endif\n") file.write(f"#endif // {variable}_ENABLED\n")
g.write("\tnullptr\n};\n") file.write("\tnullptr\n};\n")

View File

@ -5,7 +5,6 @@ Import("env")
env.editor_sources = [] env.editor_sources = []
import glob
import os import os
import editor_builders import editor_builders
@ -17,17 +16,16 @@ if env.editor_build:
def doc_data_class_path_builder(target, source, env): def doc_data_class_path_builder(target, source, env):
paths = dict(sorted(source[0].read().items())) paths = dict(sorted(source[0].read().items()))
data = "\n".join([f'\t{{"{key}", "{value}"}},' for key, value in paths.items()]) data = "\n".join([f'\t{{"{key}", "{value}"}},' for key, value in paths.items()])
with methods.generated_wrapper(target) as file: with methods.generated_wrapper(str(target[0])) as file:
file.write( file.write(
f"""\ f"""\
static const int _doc_data_class_path_count = {len(paths)};
struct _DocDataClassPath {{ struct _DocDataClassPath {{
const char *name; const char *name;
const char *path; const char *path;
}}; }};
static const _DocDataClassPath _doc_data_class_paths[{len(env.doc_class_path) + 1}] = {{ inline constexpr int _doc_data_class_path_count = {len(paths)};
inline constexpr _DocDataClassPath _doc_data_class_paths[{len(env.doc_class_path) + 1}] = {{
{data} {data}
{{nullptr, nullptr}}, {{nullptr, nullptr}},
}}; }};
@ -42,7 +40,7 @@ static const _DocDataClassPath _doc_data_class_paths[{len(env.doc_class_path) +
exp_inc = "\n".join([f'#include "platform/{p}/export/export.h"' for p in platforms]) exp_inc = "\n".join([f'#include "platform/{p}/export/export.h"' for p in platforms])
exp_reg = "\n".join([f"\tregister_{p}_exporter();" for p in platforms]) exp_reg = "\n".join([f"\tregister_{p}_exporter();" for p in platforms])
exp_type = "\n".join([f"\tregister_{p}_exporter_types();" for p in platforms]) exp_type = "\n".join([f"\tregister_{p}_exporter_types();" for p in platforms])
with methods.generated_wrapper(target) as file: with methods.generated_wrapper(str(target[0])) as file:
file.write( file.write(
f"""\ f"""\
#include "register_exporters.h" #include "register_exporters.h"
@ -83,7 +81,6 @@ void register_exporter_types() {{
docs += Glob(d + "/*.xml") # Custom. docs += Glob(d + "/*.xml") # Custom.
docs = sorted(docs) docs = sorted(docs)
env.Depends("#editor/doc_data_compressed.gen.h", docs)
env.CommandNoCache( env.CommandNoCache(
"#editor/doc_data_compressed.gen.h", "#editor/doc_data_compressed.gen.h",
docs, docs,
@ -97,40 +94,31 @@ void register_exporter_types() {{
# Generated with `make include-list` for each resource. # Generated with `make include-list` for each resource.
# Editor translations # Editor translations
tlist = glob.glob(env.Dir("#editor/translations/editor").abspath + "/*.po")
env.Depends("#editor/editor_translations.gen.h", tlist)
env.CommandNoCache( env.CommandNoCache(
"#editor/editor_translations.gen.h", "#editor/editor_translations.gen.h",
tlist, Glob("#editor/translations/editor/*"),
env.Run(editor_builders.make_editor_translations_header), env.Run(editor_builders.make_translations_header),
) )
# Property translations # Property translations
tlist = glob.glob(env.Dir("#editor/translations/properties").abspath + "/*.po")
env.Depends("#editor/property_translations.gen.h", tlist)
env.CommandNoCache( env.CommandNoCache(
"#editor/property_translations.gen.h", "#editor/property_translations.gen.h",
tlist, Glob("#editor/translations/properties/*"),
env.Run(editor_builders.make_property_translations_header), env.Run(editor_builders.make_translations_header),
) )
# Documentation translations # Documentation translations
tlist = glob.glob(env.Dir("#doc/translations").abspath + "/*.po")
env.Depends("#editor/doc_translations.gen.h", tlist)
env.CommandNoCache( env.CommandNoCache(
"#editor/doc_translations.gen.h", "#editor/doc_translations.gen.h",
tlist, Glob("#doc/translations/*"),
env.Run(editor_builders.make_doc_translations_header), env.Run(editor_builders.make_translations_header),
) )
# Extractable translations # Extractable translations
tlist = glob.glob(env.Dir("#editor/translations/extractable").abspath + "/*.po")
tlist.extend(glob.glob(env.Dir("#editor/translations/extractable").abspath + "/extractable.pot"))
env.Depends("#editor/extractable_translations.gen.h", tlist)
env.CommandNoCache( env.CommandNoCache(
"#editor/extractable_translations.gen.h", "#editor/extractable_translations.gen.h",
tlist, Glob("#editor/translations/extractable/*"),
env.Run(editor_builders.make_extractable_translations_header), env.Run(editor_builders.make_translations_header),
) )
env.add_source_files(env.editor_sources, "*.cpp") env.add_source_files(env.editor_sources, "*.cpp")

View File

@ -2,141 +2,95 @@
import os import os
import os.path import os.path
import shutil
import subprocess import subprocess
import tempfile import tempfile
import uuid import uuid
import zlib
from methods import print_warning import methods
def make_doc_header(target, source, env): def make_doc_header(target, source, env):
dst = str(target[0]) buffer = b"".join([methods.get_buffer(src) for src in map(str, source)])
with open(dst, "w", encoding="utf-8", newline="\n") as g: decomp_size = len(buffer)
buf = "" buffer = methods.compress_buffer(buffer)
docbegin = ""
docend = ""
for src in source:
src = str(src)
if not src.endswith(".xml"):
continue
with open(src, "r", encoding="utf-8") as f:
content = f.read()
buf += content
buf = (docbegin + buf + docend).encode("utf-8") with methods.generated_wrapper(str(target[0])) as file:
decomp_size = len(buf) file.write(f"""\
inline constexpr const char *_doc_data_hash = "{hash(buffer)}";
# Use maximum zlib compression level to further reduce file size inline constexpr int _doc_data_compressed_size = {len(buffer)};
# (at the cost of initial build times). inline constexpr int _doc_data_uncompressed_size = {decomp_size};
buf = zlib.compress(buf, zlib.Z_BEST_COMPRESSION) inline constexpr const unsigned char _doc_data_compressed[] = {{
{methods.format_buffer(buffer, 1)}
g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") }};
g.write("#ifndef _DOC_DATA_RAW_H\n") """)
g.write("#define _DOC_DATA_RAW_H\n")
g.write('static const char *_doc_data_hash = "' + str(hash(buf)) + '";\n')
g.write("static const int _doc_data_compressed_size = " + str(len(buf)) + ";\n")
g.write("static const int _doc_data_uncompressed_size = " + str(decomp_size) + ";\n")
g.write("static const unsigned char _doc_data_compressed[] = {\n")
for i in range(len(buf)):
g.write("\t" + str(buf[i]) + ",\n")
g.write("};\n")
g.write("#endif")
def make_translations_header(target, source, env, category): def make_translations_header(target, source, env):
dst = str(target[0]) category = os.path.basename(str(target[0])).split("_")[0]
sorted_paths = sorted([src.abspath for src in source], key=lambda path: os.path.splitext(os.path.basename(path))[0])
with open(dst, "w", encoding="utf-8", newline="\n") as g: xl_names = []
g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") msgfmt = env.Detect("msgfmt")
g.write("#ifndef _{}_TRANSLATIONS_H\n".format(category.upper())) if not msgfmt:
g.write("#define _{}_TRANSLATIONS_H\n".format(category.upper())) methods.print_warning("msgfmt not found, using .po files instead of .mo")
sorted_paths = sorted([str(x) for x in source], key=lambda path: os.path.splitext(os.path.basename(path))[0]) with methods.generated_wrapper(str(target[0])) as file:
for path in sorted_paths:
msgfmt_available = shutil.which("msgfmt") is not None name = os.path.splitext(os.path.basename(path))[0]
if not msgfmt_available:
print_warning("msgfmt is not found, using .po files instead of .mo")
xl_names = []
for i in range(len(sorted_paths)):
name = os.path.splitext(os.path.basename(sorted_paths[i]))[0]
# msgfmt erases non-translated messages, so avoid using it if exporting the POT. # msgfmt erases non-translated messages, so avoid using it if exporting the POT.
if msgfmt_available and name != category: if msgfmt and name != category:
mo_path = os.path.join(tempfile.gettempdir(), uuid.uuid4().hex + ".mo") mo_path = os.path.join(tempfile.gettempdir(), uuid.uuid4().hex + ".mo")
cmd = "msgfmt " + sorted_paths[i] + " --no-hash -o " + mo_path cmd = f"{msgfmt} {path} --no-hash -o {mo_path}"
try: try:
subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE).communicate() subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE).communicate()
with open(mo_path, "rb") as f: buffer = methods.get_buffer(mo_path)
buf = f.read()
except OSError as e: except OSError as e:
print_warning( methods.print_warning(
"msgfmt execution failed, using .po file instead of .mo: path=%r; [%s] %s" "msgfmt execution failed, using .po file instead of .mo: path=%r; [%s] %s"
% (sorted_paths[i], e.__class__.__name__, e) % (path, e.__class__.__name__, e)
) )
with open(sorted_paths[i], "rb") as f: buffer = methods.get_buffer(path)
buf = f.read()
finally: finally:
try: try:
os.remove(mo_path) if os.path.exists(mo_path):
os.remove(mo_path)
except OSError as e: except OSError as e:
# Do not fail the entire build if it cannot delete a temporary file. # Do not fail the entire build if it cannot delete a temporary file.
print_warning( methods.print_warning(
"Could not delete temporary .mo file: path=%r; [%s] %s" % (mo_path, e.__class__.__name__, e) "Could not delete temporary .mo file: path=%r; [%s] %s" % (mo_path, e.__class__.__name__, e)
) )
else: else:
with open(sorted_paths[i], "rb") as f: buffer = methods.get_buffer(path)
buf = f.read()
if name == category: if name == category:
name = "source" name = "source"
decomp_size = len(buf) decomp_size = len(buffer)
# Use maximum zlib compression level to further reduce file size buffer = methods.compress_buffer(buffer)
# (at the cost of initial build times).
buf = zlib.compress(buf, zlib.Z_BEST_COMPRESSION)
g.write("static const unsigned char _{}_translation_{}_compressed[] = {{\n".format(category, name)) file.write(f"""\
for j in range(len(buf)): inline constexpr const unsigned char _{category}_translation_{name}_compressed[] = {{
g.write("\t" + str(buf[j]) + ",\n") {methods.format_buffer(buffer, 1)}
}};
g.write("};\n") """)
xl_names.append([name, len(buf), str(decomp_size)]) xl_names.append([name, len(buffer), decomp_size])
file.write(f"""\
struct {category.capitalize()}TranslationList {{
const char* lang;
int comp_size;
int uncomp_size;
const unsigned char* data;
}};
inline constexpr {category.capitalize()}TranslationList _{category}_translations[] = {{
""")
g.write("struct {}TranslationList {{\n".format(category.capitalize()))
g.write("\tconst char* lang;\n")
g.write("\tint comp_size;\n")
g.write("\tint uncomp_size;\n")
g.write("\tconst unsigned char* data;\n")
g.write("};\n\n")
g.write("static {}TranslationList _{}_translations[] = {{\n".format(category.capitalize(), category))
for x in xl_names: for x in xl_names:
g.write( file.write(f'\t{{ "{x[0]}", {x[1]}, {x[2]}, _{category}_translation_{x[0]}_compressed }},\n')
'\t{{ "{}", {}, {}, _{}_translation_{}_compressed }},\n'.format(
x[0], str(x[1]), str(x[2]), category, x[0]
)
)
g.write("\t{nullptr, 0, 0, nullptr}\n")
g.write("};\n")
g.write("#endif") file.write("""\
{ nullptr, 0, 0, nullptr },
};
def make_editor_translations_header(target, source, env): """)
make_translations_header(target, source, env, "editor")
def make_property_translations_header(target, source, env):
make_translations_header(target, source, env, "property")
def make_doc_translations_header(target, source, env):
make_translations_header(target, source, env, "doc")
def make_extractable_translations_header(target, source, env):
make_translations_header(target, source, env, "extractable")

View File

@ -42,7 +42,7 @@
Vector<String> get_editor_locales() { Vector<String> get_editor_locales() {
Vector<String> locales; Vector<String> locales;
EditorTranslationList *etl = _editor_translations; const EditorTranslationList *etl = _editor_translations;
while (etl->data) { while (etl->data) {
const String &locale = etl->lang; const String &locale = etl->lang;
locales.push_back(locale); locales.push_back(locale);
@ -56,7 +56,7 @@ Vector<String> get_editor_locales() {
void load_editor_translations(const String &p_locale) { void load_editor_translations(const String &p_locale) {
const Ref<TranslationDomain> domain = TranslationServer::get_singleton()->get_or_add_domain("godot.editor"); const Ref<TranslationDomain> domain = TranslationServer::get_singleton()->get_or_add_domain("godot.editor");
EditorTranslationList *etl = _editor_translations; const EditorTranslationList *etl = _editor_translations;
while (etl->data) { while (etl->data) {
if (etl->lang == p_locale) { if (etl->lang == p_locale) {
Vector<uint8_t> data; Vector<uint8_t> data;
@ -84,7 +84,7 @@ void load_editor_translations(const String &p_locale) {
void load_property_translations(const String &p_locale) { void load_property_translations(const String &p_locale) {
const Ref<TranslationDomain> domain = TranslationServer::get_singleton()->get_or_add_domain("godot.properties"); const Ref<TranslationDomain> domain = TranslationServer::get_singleton()->get_or_add_domain("godot.properties");
PropertyTranslationList *etl = _property_translations; const PropertyTranslationList *etl = _property_translations;
while (etl->data) { while (etl->data) {
if (etl->lang == p_locale) { if (etl->lang == p_locale) {
Vector<uint8_t> data; Vector<uint8_t> data;
@ -112,7 +112,7 @@ void load_property_translations(const String &p_locale) {
void load_doc_translations(const String &p_locale) { void load_doc_translations(const String &p_locale) {
const Ref<TranslationDomain> domain = TranslationServer::get_singleton()->get_or_add_domain("godot.documentation"); const Ref<TranslationDomain> domain = TranslationServer::get_singleton()->get_or_add_domain("godot.documentation");
DocTranslationList *dtl = _doc_translations; const DocTranslationList *dtl = _doc_translations;
while (dtl->data) { while (dtl->data) {
if (dtl->lang == p_locale) { if (dtl->lang == p_locale) {
Vector<uint8_t> data; Vector<uint8_t> data;
@ -140,7 +140,7 @@ void load_doc_translations(const String &p_locale) {
void load_extractable_translations(const String &p_locale) { void load_extractable_translations(const String &p_locale) {
const Ref<TranslationDomain> domain = TranslationServer::get_singleton()->get_or_add_domain("godot.editor"); const Ref<TranslationDomain> domain = TranslationServer::get_singleton()->get_or_add_domain("godot.editor");
ExtractableTranslationList *etl = _extractable_translations; const ExtractableTranslationList *etl = _extractable_translations;
while (etl->data) { while (etl->data) {
if (etl->lang == p_locale) { if (etl->lang == p_locale) {
Vector<uint8_t> data; Vector<uint8_t> data;
@ -166,7 +166,7 @@ void load_extractable_translations(const String &p_locale) {
} }
Vector<Vector<String>> get_extractable_message_list() { Vector<Vector<String>> get_extractable_message_list() {
ExtractableTranslationList *etl = _extractable_translations; const ExtractableTranslationList *etl = _extractable_translations;
Vector<Vector<String>> list; Vector<Vector<String>> list;
while (etl->data) { while (etl->data) {

View File

@ -1,71 +1,47 @@
"""Functions used to generate source files during build time""" """Functions used to generate source files during build time"""
import os import os
from io import StringIO
from methods import to_raw_cstring import methods
# See also `scene/theme/icons/default_theme_icons_builders.py`. # See also `scene/theme/icons/default_theme_icons_builders.py`.
def make_editor_icons_action(target, source, env): def make_editor_icons_action(target, source, env):
dst = str(target[0]) icons_names = []
svg_icons = source icons_raw = []
icons_med = []
icons_big = []
with StringIO() as icons_string, StringIO() as s: for idx, svg in enumerate(source):
for svg in svg_icons: path = str(svg)
with open(str(svg), "r") as svgf: with open(path, encoding="utf-8", newline="\n") as file:
icons_string.write("\t%s,\n" % to_raw_cstring(svgf.read())) icons_raw.append(methods.to_raw_cstring(file.read()))
s.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") name = os.path.splitext(os.path.basename(path))[0]
s.write("#ifndef _EDITOR_ICONS_H\n") icons_names.append(f'"{name}"')
s.write("#define _EDITOR_ICONS_H\n")
s.write("static const int editor_icons_count = {};\n".format(len(svg_icons)))
s.write("static const char *editor_icons_sources[] = {\n")
s.write(icons_string.getvalue())
s.write("};\n\n")
s.write("static const char *editor_icons_names[] = {\n")
# this is used to store the indices of thumbnail icons if name.endswith("MediumThumb"):
thumb_medium_indices = [] icons_med.append(str(idx))
thumb_big_indices = [] elif name.endswith(("BigThumb", "GodotFile")):
index = 0 icons_big.append(str(idx))
for f in svg_icons:
fname = str(f)
# Trim the `.svg` extension from the string. icons_names_str = ",\n\t".join(icons_names)
icon_name = os.path.basename(fname)[:-4] icons_raw_str = ",\n\t".join(icons_raw)
# some special cases
if icon_name.endswith("MediumThumb"): # don't know a better way to handle this
thumb_medium_indices.append(str(index))
if icon_name.endswith("BigThumb"): # don't know a better way to handle this
thumb_big_indices.append(str(index))
if icon_name.endswith("GodotFile"): # don't know a better way to handle this
thumb_big_indices.append(str(index))
s.write('\t"{0}"'.format(icon_name)) with methods.generated_wrapper(str(target[0])) as file:
file.write(f"""\
inline constexpr int editor_icons_count = {len(icons_names)};
inline constexpr const char *editor_icons_sources[] = {{
{icons_raw_str}
}};
if fname != svg_icons[-1]: inline constexpr const char *editor_icons_names[] = {{
s.write(",") {icons_names_str}
s.write("\n") }};
index += 1 inline constexpr int editor_md_thumbs_count = {len(icons_med)};
inline constexpr int editor_md_thumbs_indices[] = {{ {", ".join(icons_med)} }};
s.write("};\n") inline constexpr int editor_bg_thumbs_count = {len(icons_big)};
inline constexpr int editor_bg_thumbs_indices[] = {{ {", ".join(icons_big)} }};
if thumb_medium_indices: """)
s.write("\n\n")
s.write("static const int editor_md_thumbs_count = {};\n".format(len(thumb_medium_indices)))
s.write("static const int editor_md_thumbs_indices[] = {")
s.write(", ".join(thumb_medium_indices))
s.write("};\n")
if thumb_big_indices:
s.write("\n\n")
s.write("static const int editor_bg_thumbs_count = {};\n".format(len(thumb_big_indices)))
s.write("static const int editor_bg_thumbs_indices[] = {")
s.write(", ".join(thumb_big_indices))
s.write("};\n")
s.write("#endif\n")
with open(dst, "w", encoding="utf-8", newline="\n") as f:
f.write(s.getvalue())

View File

@ -1,7 +1,8 @@
"""Functions used to generate source files during build time""" """Functions used to generate source files during build time"""
import os import os
from io import StringIO
import methods
def parse_template(inherits, source, delimiter): def parse_template(inherits, source, delimiter):
@ -36,54 +37,36 @@ def parse_template(inherits, source, delimiter):
script_template["script"].replace('"', '\\"').lstrip().replace("\n", "\\n").replace("\t", "_TS_") script_template["script"].replace('"', '\\"').lstrip().replace("\n", "\\n").replace("\t", "_TS_")
) )
return ( return (
'{ String("' f'{{ String("{script_template["inherits"]}"), '
+ script_template["inherits"] + f'String("{script_template["name"]}"), '
+ '"), String("' + f'String("{script_template["description"]}"), '
+ script_template["name"] + f'String("{script_template["script"]}") }},'
+ '"), String("'
+ script_template["description"]
+ '"), String("'
+ script_template["script"]
+ '")'
+ " },\n"
) )
def make_templates(target, source, env): def make_templates(target, source, env):
dst = str(target[0]) delimiter = "#" # GDScript single line comment delimiter by default.
with StringIO() as s: if source:
s.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n\n") ext = os.path.splitext(str(source[0]))[1]
s.write("#ifndef _CODE_TEMPLATES_H\n") if ext == ".cs":
s.write("#define _CODE_TEMPLATES_H\n\n") delimiter = "//"
s.write('#include "core/object/object.h"\n')
s.write('#include "core/object/script_language.h"\n')
delimiter = "#" # GDScript single line comment delimiter by default. parsed_templates = []
if source:
ext = os.path.splitext(str(source[0]))[1]
if ext == ".cs":
delimiter = "//"
parsed_template_string = "" for filepath in source:
number_of_templates = 0 filepath = str(filepath)
node_name = os.path.basename(os.path.dirname(filepath))
parsed_templates.append(parse_template(node_name, filepath, delimiter))
for filepath in source: parsed_template_string = "\n\t".join(parsed_templates)
filepath = str(filepath)
node_name = os.path.basename(os.path.dirname(filepath))
parsed_template = parse_template(node_name, filepath, delimiter)
parsed_template_string += "\t" + parsed_template
number_of_templates += 1
s.write("\nstatic const int TEMPLATES_ARRAY_SIZE = " + str(number_of_templates) + ";\n") with methods.generated_wrapper(str(target[0])) as file:
s.write( file.write(f"""\
"\nstatic const struct ScriptLanguage::ScriptTemplate TEMPLATES[" + str(number_of_templates) + "] = {\n" #include "core/object/object.h"
) #include "core/object/script_language.h"
s.write(parsed_template_string) inline constexpr int TEMPLATES_ARRAY_SIZE = {len(parsed_templates)};
static const struct ScriptLanguage::ScriptTemplate TEMPLATES[TEMPLATES_ARRAY_SIZE] = {{
s.write("};\n") {parsed_template_string}
}};
s.write("\n#endif\n") """)
with open(dst, "w", encoding="utf-8", newline="\n") as f:
f.write(s.getvalue())

View File

@ -3,17 +3,14 @@ from misc.utility.scons_hints import *
Import("env") Import("env")
import glob
import editor_theme_builders import editor_theme_builders
# Fonts # Fonts
flist = glob.glob(env.Dir("#thirdparty").abspath + "/fonts/*.ttf") flist = Glob("#thirdparty/fonts/*.ttf")
flist.extend(glob.glob(env.Dir("#thirdparty").abspath + "/fonts/*.otf")) flist.extend(Glob("#thirdparty/fonts/*.otf"))
flist.extend(glob.glob(env.Dir("#thirdparty").abspath + "/fonts/*.woff")) flist.extend(Glob("#thirdparty/fonts/*.woff"))
flist.extend(glob.glob(env.Dir("#thirdparty").abspath + "/fonts/*.woff2")) flist.extend(Glob("#thirdparty/fonts/*.woff2"))
flist.sort() flist.sort()
env.Depends("#editor/themes/builtin_fonts.gen.h", flist)
env.CommandNoCache( env.CommandNoCache(
"#editor/themes/builtin_fonts.gen.h", "#editor/themes/builtin_fonts.gen.h",
flist, flist,

View File

@ -2,28 +2,20 @@
import os import os
import methods
def make_fonts_header(target, source, env): def make_fonts_header(target, source, env):
dst = str(target[0]) with methods.generated_wrapper(str(target[0])) as file:
for src in map(str, source):
# Saving uncompressed, since FreeType will reference from memory pointer.
buffer = methods.get_buffer(src)
name = os.path.splitext(os.path.basename(src))[0]
with open(dst, "w", encoding="utf-8", newline="\n") as g: file.write(f"""\
g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") inline constexpr int _font_{name}_size = {len(buffer)};
g.write("#ifndef _EDITOR_FONTS_H\n") inline constexpr unsigned char _font_{name}[] = {{
g.write("#define _EDITOR_FONTS_H\n") {methods.format_buffer(buffer, 1)}
}};
# Saving uncompressed, since FreeType will reference from memory pointer. """)
for i in range(len(source)):
file = str(source[i])
with open(file, "rb") as f:
buf = f.read()
name = os.path.splitext(os.path.basename(file))[0]
g.write("static const int _font_" + name + "_size = " + str(len(buf)) + ";\n")
g.write("static const unsigned char _font_" + name + "[] = {\n")
for j in range(len(buf)):
g.write("\t" + str(buf[j]) + ",\n")
g.write("};\n")
g.write("#endif")

View File

@ -17,7 +17,6 @@ if env["steamapi"] and env.editor_build:
if env["tests"]: if env["tests"]:
env_main.Append(CPPDEFINES=["TESTS_ENABLED"]) env_main.Append(CPPDEFINES=["TESTS_ENABLED"])
env_main.Depends("#main/splash.gen.h", "#main/splash.png")
env_main.CommandNoCache( env_main.CommandNoCache(
"#main/splash.gen.h", "#main/splash.gen.h",
"#main/splash.png", "#main/splash.png",
@ -25,14 +24,12 @@ env_main.CommandNoCache(
) )
if env_main.editor_build and not env_main["no_editor_splash"]: if env_main.editor_build and not env_main["no_editor_splash"]:
env_main.Depends("#main/splash_editor.gen.h", "#main/splash_editor.png")
env_main.CommandNoCache( env_main.CommandNoCache(
"#main/splash_editor.gen.h", "#main/splash_editor.gen.h",
"#main/splash_editor.png", "#main/splash_editor.png",
env.Run(main_builders.make_splash_editor), env.Run(main_builders.make_splash_editor),
) )
env_main.Depends("#main/app_icon.gen.h", "#main/app_icon.png")
env_main.CommandNoCache( env_main.CommandNoCache(
"#main/app_icon.gen.h", "#main/app_icon.gen.h",
"#main/app_icon.png", "#main/app_icon.png",

View File

@ -1,60 +1,42 @@
"""Functions used to generate source files during build time""" """Functions used to generate source files during build time"""
import methods
def make_splash(target, source, env): def make_splash(target, source, env):
src = str(source[0]) buffer = methods.get_buffer(str(source[0]))
dst = str(target[0])
with open(src, "rb") as f: with methods.generated_wrapper(str(target[0])) as file:
buf = f.read()
with open(dst, "w", encoding="utf-8", newline="\n") as g:
g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n")
g.write("#ifndef BOOT_SPLASH_H\n")
g.write("#define BOOT_SPLASH_H\n")
# Use a neutral gray color to better fit various kinds of projects. # Use a neutral gray color to better fit various kinds of projects.
g.write("static const Color boot_splash_bg_color = Color(0.14, 0.14, 0.14);\n") file.write(f"""\
g.write("static const unsigned char boot_splash_png[] = {\n") static const Color boot_splash_bg_color = Color(0.14, 0.14, 0.14);
for i in range(len(buf)): inline constexpr const unsigned char boot_splash_png[] = {{
g.write(str(buf[i]) + ",\n") {methods.format_buffer(buffer, 1)}
g.write("};\n") }};
g.write("#endif") """)
def make_splash_editor(target, source, env): def make_splash_editor(target, source, env):
src = str(source[0]) buffer = methods.get_buffer(str(source[0]))
dst = str(target[0])
with open(src, "rb") as f: with methods.generated_wrapper(str(target[0])) as file:
buf = f.read()
with open(dst, "w", encoding="utf-8", newline="\n") as g:
g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n")
g.write("#ifndef BOOT_SPLASH_EDITOR_H\n")
g.write("#define BOOT_SPLASH_EDITOR_H\n")
# The editor splash background color is taken from the default editor theme's background color. # The editor splash background color is taken from the default editor theme's background color.
# This helps achieve a visually "smoother" transition between the splash screen and the editor. # This helps achieve a visually "smoother" transition between the splash screen and the editor.
g.write("static const Color boot_splash_editor_bg_color = Color(0.125, 0.145, 0.192);\n") file.write(f"""\
g.write("static const unsigned char boot_splash_editor_png[] = {\n") static const Color boot_splash_editor_bg_color = Color(0.125, 0.145, 0.192);
for i in range(len(buf)): inline constexpr const unsigned char boot_splash_editor_png[] = {{
g.write(str(buf[i]) + ",\n") {methods.format_buffer(buffer, 1)}
g.write("};\n") }};
g.write("#endif") """)
def make_app_icon(target, source, env): def make_app_icon(target, source, env):
src = str(source[0]) buffer = methods.get_buffer(str(source[0]))
dst = str(target[0])
with open(src, "rb") as f: with methods.generated_wrapper(str(target[0])) as file:
buf = f.read() # Use a neutral gray color to better fit various kinds of projects.
file.write(f"""\
with open(dst, "w", encoding="utf-8", newline="\n") as g: inline constexpr const unsigned char app_icon_png[] = {{
g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") {methods.format_buffer(buffer, 1)}
g.write("#ifndef APP_ICON_H\n") }};
g.write("#define APP_ICON_H\n") """)
g.write("static const unsigned char app_icon_png[] = {\n")
for i in range(len(buf)):
g.write(str(buf[i]) + ",\n")
g.write("};\n")
g.write("#endif")

View File

@ -6,6 +6,8 @@ import os
import re import re
import subprocess import subprocess
import sys import sys
import textwrap
import zlib
from collections import OrderedDict from collections import OrderedDict
from io import StringIO, TextIOBase from io import StringIO, TextIOBase
from pathlib import Path from pathlib import Path
@ -144,30 +146,36 @@ def get_version_info(module_version_string="", silent=False):
if not silent: if not silent:
print_info(f"Using version status '{version_info['status']}', overriding the original '{version.status}'.") print_info(f"Using version status '{version_info['status']}', overriding the original '{version.status}'.")
return version_info
def get_git_info():
os.chdir(base_folder_path)
# Parse Git hash if we're in a Git repo. # Parse Git hash if we're in a Git repo.
githash = "" git_hash = ""
gitfolder = ".git" git_folder = ".git"
if os.path.isfile(".git"): if os.path.isfile(".git"):
with open(".git", "r", encoding="utf-8") as file: with open(".git", "r", encoding="utf-8") as file:
module_folder = file.readline().strip() module_folder = file.readline().strip()
if module_folder.startswith("gitdir: "): if module_folder.startswith("gitdir: "):
gitfolder = module_folder[8:] git_folder = module_folder[8:]
if os.path.isfile(os.path.join(gitfolder, "HEAD")): if os.path.isfile(os.path.join(git_folder, "HEAD")):
with open(os.path.join(gitfolder, "HEAD"), "r", encoding="utf8") as file: with open(os.path.join(git_folder, "HEAD"), "r", encoding="utf8") as file:
head = file.readline().strip() head = file.readline().strip()
if head.startswith("ref: "): if head.startswith("ref: "):
ref = head[5:] ref = head[5:]
# If this directory is a Git worktree instead of a root clone. # If this directory is a Git worktree instead of a root clone.
parts = gitfolder.split("/") parts = git_folder.split("/")
if len(parts) > 2 and parts[-2] == "worktrees": if len(parts) > 2 and parts[-2] == "worktrees":
gitfolder = "/".join(parts[0:-2]) git_folder = "/".join(parts[0:-2])
head = os.path.join(gitfolder, ref) head = os.path.join(git_folder, ref)
packedrefs = os.path.join(gitfolder, "packed-refs") packedrefs = os.path.join(git_folder, "packed-refs")
if os.path.isfile(head): if os.path.isfile(head):
with open(head, "r", encoding="utf-8") as file: with open(head, "r", encoding="utf-8") as file:
githash = file.readline().strip() git_hash = file.readline().strip()
elif os.path.isfile(packedrefs): elif os.path.isfile(packedrefs):
# Git may pack refs into a single file. This code searches .git/packed-refs file for the current ref's hash. # Git may pack refs into a single file. This code searches .git/packed-refs file for the current ref's hash.
# https://mirrors.edge.kernel.org/pub/software/scm/git/docs/git-pack-refs.html # https://mirrors.edge.kernel.org/pub/software/scm/git/docs/git-pack-refs.html
@ -176,26 +184,26 @@ def get_version_info(module_version_string="", silent=False):
continue continue
(line_hash, line_ref) = line.split(" ") (line_hash, line_ref) = line.split(" ")
if ref == line_ref: if ref == line_ref:
githash = line_hash git_hash = line_hash
break break
else: else:
githash = head git_hash = head
version_info["git_hash"] = githash
# Fallback to 0 as a timestamp (will be treated as "unknown" in the engine).
version_info["git_timestamp"] = 0
# Get the UNIX timestamp of the build commit. # Get the UNIX timestamp of the build commit.
git_timestamp = 0
if os.path.exists(".git"): if os.path.exists(".git"):
try: try:
version_info["git_timestamp"] = subprocess.check_output( git_timestamp = subprocess.check_output(
["git", "log", "-1", "--pretty=format:%ct", "--no-show-signature", githash] ["git", "log", "-1", "--pretty=format:%ct", "--no-show-signature", git_hash], encoding="utf-8"
).decode("utf-8") )
except (subprocess.CalledProcessError, OSError): except (subprocess.CalledProcessError, OSError):
# `git` not found in PATH. # `git` not found in PATH.
pass pass
return version_info return {
"git_hash": git_hash,
"git_timestamp": git_timestamp,
}
def get_cmdline_bool(option, default): def get_cmdline_bool(option, default):
@ -1417,6 +1425,11 @@ def generate_vs_project(env, original_args, project_name="godot"):
sys.exit() sys.exit()
############################################################
# FILE GENERATION & FORMATTING
############################################################
def generate_copyright_header(filename: str) -> str: def generate_copyright_header(filename: str) -> str:
MARGIN = 70 MARGIN = 70
TEMPLATE = """\ TEMPLATE = """\
@ -1450,15 +1463,14 @@ def generate_copyright_header(filename: str) -> str:
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/ /**************************************************************************/
""" """
filename = filename.split("/")[-1].ljust(MARGIN) if len(filename := os.path.basename(filename).ljust(MARGIN)) > MARGIN:
if len(filename) > MARGIN:
print_warning(f'Filename "{filename}" too large for copyright header.') print_warning(f'Filename "{filename}" too large for copyright header.')
return TEMPLATE % filename return TEMPLATE % filename
@contextlib.contextmanager @contextlib.contextmanager
def generated_wrapper( def generated_wrapper(
path, # FIXME: type with `Union[str, Node, List[Node]]` when pytest conflicts are resolved path: str,
guard: Optional[bool] = None, guard: Optional[bool] = None,
) -> Generator[TextIOBase, None, None]: ) -> Generator[TextIOBase, None, None]:
""" """
@ -1466,26 +1478,11 @@ def generated_wrapper(
for generated scripts. Meant to be invoked via `with` statement similar to for generated scripts. Meant to be invoked via `with` statement similar to
creating a file. creating a file.
- `path`: The path of the file to be created. Can be passed a raw string, an - `path`: The path of the file to be created.
isolated SCons target, or a full SCons target list. If a target list contains
multiple entries, produces a warning & only creates the first entry.
- `guard`: Optional bool to determine if `#pragma once` should be added. If - `guard`: Optional bool to determine if `#pragma once` should be added. If
unassigned, the value is determined by file extension. unassigned, the value is determined by file extension.
""" """
# Handle unfiltered SCons target[s] passed as path.
if not isinstance(path, str):
if isinstance(path, list):
if len(path) > 1:
print_warning(
f"Attempting to use generated wrapper with multiple targets; will only use first entry: {path[0]}"
)
path = path[0]
if not hasattr(path, "get_abspath"):
raise TypeError(f'Expected type "str", "Node" or "List[Node]"; was passed {type(path)}.')
path = path.get_abspath()
path = str(path).replace("\\", "/")
if guard is None: if guard is None:
guard = path.endswith((".h", ".hh", ".hpp", ".hxx", ".inc")) guard = path.endswith((".h", ".hh", ".hpp", ".hxx", ".inc"))
@ -1503,6 +1500,50 @@ def generated_wrapper(
file.write("\n") file.write("\n")
def get_buffer(path: str) -> bytes:
with open(path, "rb") as file:
return file.read()
def compress_buffer(buffer: bytes) -> bytes:
# Use maximum zlib compression level to further reduce file size
# (at the cost of initial build times).
return zlib.compress(buffer, zlib.Z_BEST_COMPRESSION)
def format_buffer(buffer: bytes, indent: int = 0, width: int = 120, initial_indent: bool = False) -> str:
return textwrap.fill(
", ".join(str(byte) for byte in buffer),
width=width,
initial_indent="\t" * indent if initial_indent else "",
subsequent_indent="\t" * indent,
tabsize=4,
)
############################################################
# CSTRING PARSING
############################################################
C_ESCAPABLES = [
("\\", "\\\\"),
("\a", "\\a"),
("\b", "\\b"),
("\f", "\\f"),
("\n", "\\n"),
("\r", "\\r"),
("\t", "\\t"),
("\v", "\\v"),
# ("'", "\\'"), # Skip, as we're only dealing with full strings.
('"', '\\"'),
]
C_ESCAPE_TABLE = str.maketrans(dict((x, y) for x, y in C_ESCAPABLES))
def to_escaped_cstring(value: str) -> str:
return value.translate(C_ESCAPE_TABLE)
def to_raw_cstring(value: Union[str, List[str]]) -> str: def to_raw_cstring(value: Union[str, List[str]]) -> str:
MAX_LITERAL = 16 * 1024 MAX_LITERAL = 16 * 1024
@ -1540,4 +1581,8 @@ def to_raw_cstring(value: Union[str, List[str]]) -> str:
split += [segment] split += [segment]
return " ".join(f'R"<!>({x.decode()})<!>"' for x in split) if len(split) == 1:
return f'R"<!>({split[0].decode()})<!>"'
else:
# Wrap multiple segments in parenthesis to suppress `string-concatenation` warnings on clang.
return "({})".format(" ".join(f'R"<!>({segment.decode()})<!>"' for segment in split))

View File

@ -17,8 +17,9 @@ Export("env_modules")
# Header with MODULE_*_ENABLED defines. # Header with MODULE_*_ENABLED defines.
def modules_enabled_builder(target, source, env): def modules_enabled_builder(target, source, env):
with methods.generated_wrapper(target) as file: modules = sorted(source[0].read())
for module in source[0].read(): with methods.generated_wrapper(str(target[0])) as file:
for module in modules:
file.write(f"#define MODULE_{module.upper()}_ENABLED\n") file.write(f"#define MODULE_{module.upper()}_ENABLED\n")
@ -29,14 +30,26 @@ modules_enabled = env.CommandNoCache(
def register_module_types_builder(target, source, env): def register_module_types_builder(target, source, env):
modules = source[0].read() modules = source[0].read()
mod_inc = "\n".join([f'#include "{p}/register_types.h"' for p in modules.values()]) mod_inc = "\n".join([f'#include "{value}/register_types.h"' for value in modules.values()])
mod_init = "\n".join( mod_init = "\n".join(
[f"#ifdef MODULE_{n.upper()}_ENABLED\n\tinitialize_{n}_module(p_level);\n#endif" for n in modules.keys()] [
f"""\
#ifdef MODULE_{key.upper()}_ENABLED
initialize_{key}_module(p_level);
#endif"""
for key in modules.keys()
]
) )
mod_uninit = "\n".join( mod_uninit = "\n".join(
[f"#ifdef MODULE_{n.upper()}_ENABLED\n\tuninitialize_{n}_module(p_level);\n#endif" for n in modules.keys()] [
f"""\
#ifdef MODULE_{key.upper()}_ENABLED
uninitialize_{key}_module(p_level);
#endif"""
for key in modules.keys()
]
) )
with methods.generated_wrapper(target) as file: with methods.generated_wrapper(str(target[0])) as file:
file.write( file.write(
f"""\ f"""\
#include "register_module_types.h" #include "register_module_types.h"
@ -88,9 +101,10 @@ for name, path in env.module_list.items():
if env["tests"]: if env["tests"]:
def modules_tests_builder(target, source, env): def modules_tests_builder(target, source, env):
with methods.generated_wrapper(target) as file: headers = sorted([os.path.relpath(src.path, methods.base_folder_path).replace("\\", "/") for src in source])
for header in source: with methods.generated_wrapper(str(target[0])) as file:
file.write('#include "{}"\n'.format(os.path.normpath(header.path).replace("\\", "/"))) for header in headers:
file.write(f'#include "{header}"\n')
env.CommandNoCache("modules_tests.gen.h", test_headers, env.Run(modules_tests_builder)) env.CommandNoCache("modules_tests.gen.h", test_headers, env.Run(modules_tests_builder))

View File

@ -1,6 +1,8 @@
#!/usr/bin/env python #!/usr/bin/env python
from misc.utility.scons_hints import * from misc.utility.scons_hints import *
import methods
Import("env") Import("env")
Import("env_modules") Import("env_modules")
@ -8,28 +10,21 @@ env_text_server_adv = env_modules.Clone()
def make_icu_data(target, source, env): def make_icu_data(target, source, env):
dst = target[0].srcnode().abspath buffer = methods.get_buffer(str(source[0]))
with methods.generated_wrapper(str(target[0])) as file:
file.write(f"""\
/* (C) 2016 and later: Unicode, Inc. and others. */
/* License & terms of use: https://www.unicode.org/copyright.html */
with open(dst, "w", encoding="utf-8", newline="\n") as g: #include <unicode/utypes.h>
g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") #include <unicode/udata.h>
g.write("/* (C) 2016 and later: Unicode, Inc. and others. */\n") #include <unicode/uversion.h>
g.write("/* License & terms of use: https://www.unicode.org/copyright.html */\n")
g.write("#ifndef _ICU_DATA_H\n")
g.write("#define _ICU_DATA_H\n")
g.write('#include "unicode/utypes.h"\n')
g.write('#include "unicode/udata.h"\n')
g.write('#include "unicode/uversion.h"\n')
with open(source[0].srcnode().abspath, "rb") as f: extern "C" U_EXPORT const size_t U_ICUDATA_SIZE = {len(buffer)};
buf = f.read() extern "C" U_EXPORT const unsigned char U_ICUDATA_ENTRY_POINT[] = {{
{methods.format_buffer(buffer, 1)}
g.write('extern "C" U_EXPORT const size_t U_ICUDATA_SIZE = ' + str(len(buf)) + ";\n") }};
g.write('extern "C" U_EXPORT const unsigned char U_ICUDATA_ENTRY_POINT[] = {\n') """)
for i in range(len(buf)):
g.write("\t" + str(buf[i]) + ",\n")
g.write("};\n")
g.write("#endif")
# Thirdparty source files # Thirdparty source files

View File

@ -18,10 +18,10 @@ def export_icon_builder(target, source, env):
platform = src_path.parent.parent.stem platform = src_path.parent.parent.stem
with open(str(source[0]), "r") as file: with open(str(source[0]), "r") as file:
svg = file.read() svg = file.read()
with methods.generated_wrapper(target) as file: with methods.generated_wrapper(str(target[0])) as file:
file.write( file.write(
f"""\ f"""\
static const char *_{platform}_{src_name}_svg = {methods.to_raw_cstring(svg)}; inline constexpr const char *_{platform}_{src_name}_svg = {methods.to_raw_cstring(svg)};
""" """
) )
@ -37,7 +37,7 @@ def register_platform_apis_builder(target, source, env):
api_inc = "\n".join([f'#include "{p}/api/api.h"' for p in platforms]) api_inc = "\n".join([f'#include "{p}/api/api.h"' for p in platforms])
api_reg = "\n".join([f"\tregister_{p}_api();" for p in platforms]) api_reg = "\n".join([f"\tregister_{p}_api();" for p in platforms])
api_unreg = "\n".join([f"\tunregister_{p}_api();" for p in platforms]) api_unreg = "\n".join([f"\tunregister_{p}_api();" for p in platforms])
with methods.generated_wrapper(target) as file: with methods.generated_wrapper(str(target[0])) as file:
file.write( file.write(
f"""\ f"""\
#include "register_platform_apis.h" #include "register_platform_apis.h"

View File

@ -9,7 +9,6 @@ env.add_source_files(env.scene_sources, "*.cpp")
SConscript("icons/SCsub") SConscript("icons/SCsub")
env.Depends("#scene/theme/default_font.gen.h", "#thirdparty/fonts/OpenSans_SemiBold.woff2")
env.CommandNoCache( env.CommandNoCache(
"#scene/theme/default_font.gen.h", "#scene/theme/default_font.gen.h",
"#thirdparty/fonts/OpenSans_SemiBold.woff2", "#thirdparty/fonts/OpenSans_SemiBold.woff2",

View File

@ -1,30 +1,21 @@
"""Functions used to generate source files during build time""" """Functions used to generate source files during build time"""
import os import os
import os.path
import methods
def make_fonts_header(target, source, env): def make_fonts_header(target, source, env):
dst = str(target[0]) with methods.generated_wrapper(str(target[0])) as file:
for src in map(str, source):
# Saving uncompressed, since FreeType will reference from memory pointer.
buffer = methods.get_buffer(src)
name = os.path.splitext(os.path.basename(src))[0]
with open(dst, "w", encoding="utf-8", newline="\n") as g: file.write(f"""\
g.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n") inline constexpr int _font_{name}_size = {len(buffer)};
g.write("#ifndef _DEFAULT_FONTS_H\n") inline constexpr unsigned char _font_{name}[] = {{
g.write("#define _DEFAULT_FONTS_H\n") {methods.format_buffer(buffer, 1)}
}};
# Saving uncompressed, since FreeType will reference from memory pointer. """)
for i in range(len(source)):
file = str(source[i])
with open(file, "rb") as f:
buf = f.read()
name = os.path.splitext(os.path.basename(file))[0]
g.write("static const int _font_" + name + "_size = " + str(len(buf)) + ";\n")
g.write("static const unsigned char _font_" + name + "[] = {\n")
for j in range(len(buf)):
g.write("\t" + str(buf[j]) + ",\n")
g.write("};\n")
g.write("#endif")

View File

@ -1,51 +1,35 @@
"""Functions used to generate source files during build time""" """Functions used to generate source files during build time"""
import os import os
from io import StringIO
from methods import to_raw_cstring import methods
# See also `editor/icons/editor_icons_builders.py`. # See also `editor/icons/editor_icons_builders.py`.
def make_default_theme_icons_action(target, source, env): def make_default_theme_icons_action(target, source, env):
dst = str(target[0]) icons_names = []
svg_icons = [str(x) for x in source] icons_raw = []
with StringIO() as icons_string, StringIO() as s: for src in map(str, source):
for svg in svg_icons: with open(src, encoding="utf-8", newline="\n") as file:
with open(svg, "r") as svgf: icons_raw.append(methods.to_raw_cstring(file.read()))
icons_string.write("\t%s,\n" % to_raw_cstring(svgf.read()))
s.write("/* THIS FILE IS GENERATED DO NOT EDIT */\n\n") name = os.path.splitext(os.path.basename(src))[0]
s.write('#include "modules/modules_enabled.gen.h"\n\n') icons_names.append(f'"{name}"')
s.write("#ifndef _DEFAULT_THEME_ICONS_H\n")
s.write("#define _DEFAULT_THEME_ICONS_H\n")
s.write("static const int default_theme_icons_count = {};\n\n".format(len(svg_icons)))
s.write("#ifdef MODULE_SVG_ENABLED\n")
s.write("static const char *default_theme_icons_sources[] = {\n")
s.write(icons_string.getvalue())
s.write("};\n")
s.write("#endif // MODULE_SVG_ENABLED\n\n")
s.write("static const char *default_theme_icons_names[] = {\n")
index = 0 icons_names_str = ",\n\t".join(icons_names)
for f in svg_icons: icons_raw_str = ",\n\t".join(icons_raw)
fname = str(f)
# Trim the `.svg` extension from the string. with methods.generated_wrapper(str(target[0])) as file:
icon_name = os.path.basename(fname)[:-4] file.write(f"""\
#include "modules/modules_enabled.gen.h"
s.write('\t"{0}"'.format(icon_name)) inline constexpr int default_theme_icons_count = {len(icons_names)};
inline constexpr const char *default_theme_icons_sources[] = {{
{icons_raw_str}
}};
if fname != svg_icons[-1]: inline constexpr const char *default_theme_icons_names[] = {{
s.write(",") {icons_names_str}
s.write("\n") }};
""")
index += 1
s.write("};\n")
s.write("#endif\n")
with open(dst, "w", encoding="utf-8", newline="\n") as f:
f.write(s.getvalue())