summaryrefslogtreecommitdiffstats
path: root/methods.py
diff options
context:
space:
mode:
Diffstat (limited to 'methods.py')
-rw-r--r--methods.py437
1 files changed, 216 insertions, 221 deletions
diff --git a/methods.py b/methods.py
index 01b127ea30..f3798d121a 100644
--- a/methods.py
+++ b/methods.py
@@ -1,13 +1,14 @@
+import contextlib
+import glob
import os
-import sys
import re
-import glob
import subprocess
+import sys
from collections import OrderedDict
-from collections.abc import Mapping
-from typing import Iterator
+from enum import Enum
+from io import StringIO, TextIOWrapper
from pathlib import Path
-from os.path import normpath, basename
+from typing import Generator, Optional
# Get the "Godot" folder name ahead of time
base_folder_path = str(os.path.abspath(Path(__file__).parent)) + "/"
@@ -15,6 +16,10 @@ base_folder_only = os.path.basename(os.path.normpath(base_folder_path))
# Listing all the folders we have converted
# for SCU in scu_builders.py
_scu_folders = set()
+# Colors are disabled in non-TTY environments such as pipes. This means
+# that if output is redirected to a file, it won't contain color codes.
+# Colors are always enabled on continuous integration.
+_colorize = bool(sys.stdout.isatty() or os.environ.get("CI"))
def set_scu_folders(scu_folders):
@@ -22,13 +27,57 @@ def set_scu_folders(scu_folders):
_scu_folders = scu_folders
+class ANSI(Enum):
+ """
+ Enum class for adding ansi colorcodes directly into strings.
+ Automatically converts values to strings representing their
+ internal value, or an empty string in a non-colorized scope.
+ """
+
+ RESET = "\x1b[0m"
+
+ BOLD = "\x1b[1m"
+ ITALIC = "\x1b[3m"
+ UNDERLINE = "\x1b[4m"
+ STRIKETHROUGH = "\x1b[9m"
+ REGULAR = "\x1b[22;23;24;29m"
+
+ BLACK = "\x1b[30m"
+ RED = "\x1b[31m"
+ GREEN = "\x1b[32m"
+ YELLOW = "\x1b[33m"
+ BLUE = "\x1b[34m"
+ MAGENTA = "\x1b[35m"
+ CYAN = "\x1b[36m"
+ WHITE = "\x1b[37m"
+
+ PURPLE = "\x1b[38;5;93m"
+ PINK = "\x1b[38;5;206m"
+ ORANGE = "\x1b[38;5;214m"
+ GRAY = "\x1b[38;5;244m"
+
+ def __str__(self) -> str:
+ global _colorize
+ return str(self.value) if _colorize else ""
+
+
+def print_warning(*values: object) -> None:
+ """Prints a warning message with formatting."""
+ print(f"{ANSI.YELLOW}{ANSI.BOLD}WARNING:{ANSI.REGULAR}", *values, ANSI.RESET, file=sys.stderr)
+
+
+def print_error(*values: object) -> None:
+ """Prints an error message with formatting."""
+ print(f"{ANSI.RED}{ANSI.BOLD}ERROR:{ANSI.REGULAR}", *values, ANSI.RESET, file=sys.stderr)
+
+
def add_source_files_orig(self, sources, files, allow_gen=False):
# Convert string to list of absolute paths (including expanding wildcard)
if isinstance(files, (str, bytes)):
# Keep SCons project-absolute path as they are (no wildcard support)
if files.startswith("#"):
if "*" in files:
- print("ERROR: Wildcards can't be expanded in SCons project-absolute path: '{}'".format(files))
+ print_error("Wildcards can't be expanded in SCons project-absolute path: '{}'".format(files))
return
files = [files]
else:
@@ -44,7 +93,7 @@ def add_source_files_orig(self, sources, files, allow_gen=False):
for path in files:
obj = self.Object(path)
if obj in sources:
- print('WARNING: Object "{}" already included in environment sources.'.format(obj))
+ print_warning('Object "{}" already included in environment sources.'.format(obj))
continue
sources.append(obj)
@@ -147,7 +196,7 @@ def add_module_version_string(self, s):
def get_version_info(module_version_string="", silent=False):
build_name = "custom_build"
- if os.getenv("BUILD_NAME") != None:
+ if os.getenv("BUILD_NAME") is not None:
build_name = str(os.getenv("BUILD_NAME"))
if not silent:
print(f"Using custom build name: '{build_name}'.")
@@ -169,7 +218,7 @@ def get_version_info(module_version_string="", silent=False):
# For dev snapshots (alpha, beta, RC, etc.) we do not commit status change to Git,
# so this define provides a way to override it without having to modify the source.
- if os.getenv("GODOT_VERSION_STATUS") != None:
+ if os.getenv("GODOT_VERSION_STATUS") is not None:
version_info["status"] = str(os.getenv("GODOT_VERSION_STATUS"))
if not silent:
print(f"Using version status '{version_info['status']}', overriding the original '{version.status}'.")
@@ -228,79 +277,6 @@ def get_version_info(module_version_string="", silent=False):
return version_info
-_cleanup_env = None
-_cleanup_bool = False
-
-
-def write_file_if_needed(path, string):
- """Generates a file only if it doesn't already exist or the content has changed.
-
- Utilizes a dedicated SCons environment to ensure the files are properly removed
- during cleanup; will not attempt to create files during cleanup.
-
- - `path` - Path to the file in question; used to create cleanup logic.
- - `string` - Content to compare against an existing file.
- """
- global _cleanup_env
- global _cleanup_bool
-
- if _cleanup_env is None:
- from SCons.Environment import Environment
-
- _cleanup_env = Environment()
- _cleanup_bool = _cleanup_env.GetOption("clean")
-
- _cleanup_env.Clean("#", path)
- if _cleanup_bool:
- return
-
- try:
- with open(path, "r", encoding="utf-8", newline="\n") as f:
- if f.read() == string:
- return
- except FileNotFoundError:
- pass
-
- with open(path, "w", encoding="utf-8", newline="\n") as f:
- f.write(string)
-
-
-def generate_version_header(module_version_string=""):
- version_info = get_version_info(module_version_string)
-
- version_info_header = """\
-/* THIS FILE IS GENERATED DO NOT EDIT */
-#ifndef VERSION_GENERATED_GEN_H
-#define VERSION_GENERATED_GEN_H
-#define VERSION_SHORT_NAME "{short_name}"
-#define VERSION_NAME "{name}"
-#define VERSION_MAJOR {major}
-#define VERSION_MINOR {minor}
-#define VERSION_PATCH {patch}
-#define VERSION_STATUS "{status}"
-#define VERSION_BUILD "{build}"
-#define VERSION_MODULE_CONFIG "{module_config}"
-#define VERSION_WEBSITE "{website}"
-#define VERSION_DOCS_BRANCH "{docs_branch}"
-#define VERSION_DOCS_URL "https://docs.godotengine.org/en/" VERSION_DOCS_BRANCH
-#endif // VERSION_GENERATED_GEN_H
-""".format(
- **version_info
- )
-
- version_hash_data = """\
-/* THIS FILE IS GENERATED DO NOT EDIT */
-#include "core/version.h"
-const char *const VERSION_HASH = "{git_hash}";
-const uint64_t VERSION_TIMESTAMP = {git_timestamp};
-""".format(
- **version_info
- )
-
- write_file_if_needed("core/version_generated.gen.h", version_info_header)
- write_file_if_needed("core/version_hash.gen.cpp", version_hash_data)
-
-
def parse_cg_file(fname, uniforms, sizes, conditionals):
with open(fname, "r", encoding="utf-8") as fs:
line = fs.readline()
@@ -416,63 +392,6 @@ def is_module(path):
return True
-def write_disabled_classes(class_list):
- file_contents = ""
-
- file_contents += "/* THIS FILE IS GENERATED DO NOT EDIT */\n"
- file_contents += "#ifndef DISABLED_CLASSES_GEN_H\n"
- file_contents += "#define DISABLED_CLASSES_GEN_H\n\n"
- for c in class_list:
- cs = c.strip()
- if cs != "":
- file_contents += "#define ClassDB_Disable_" + cs + " 1\n"
- file_contents += "\n#endif\n"
-
- write_file_if_needed("core/disabled_classes.gen.h", file_contents)
-
-
-def write_modules(modules):
- includes_cpp = ""
- initialize_cpp = ""
- uninitialize_cpp = ""
-
- for name, path in modules.items():
- try:
- with open(os.path.join(path, "register_types.h")):
- includes_cpp += '#include "' + path + '/register_types.h"\n'
- initialize_cpp += "#ifdef MODULE_" + name.upper() + "_ENABLED\n"
- initialize_cpp += "\tinitialize_" + name + "_module(p_level);\n"
- initialize_cpp += "#endif\n"
- uninitialize_cpp += "#ifdef MODULE_" + name.upper() + "_ENABLED\n"
- uninitialize_cpp += "\tuninitialize_" + name + "_module(p_level);\n"
- uninitialize_cpp += "#endif\n"
- except OSError:
- pass
-
- modules_cpp = """// register_module_types.gen.cpp
-/* THIS FILE IS GENERATED DO NOT EDIT */
-#include "register_module_types.h"
-
-#include "modules/modules_enabled.gen.h"
-
-%s
-
-void initialize_modules(ModuleInitializationLevel p_level) {
-%s
-}
-
-void uninitialize_modules(ModuleInitializationLevel p_level) {
-%s
-}
-""" % (
- includes_cpp,
- initialize_cpp,
- uninitialize_cpp,
- )
-
- write_file_if_needed("modules/register_module_types.gen.cpp", modules_cpp)
-
-
def convert_custom_modules_path(path):
if not path:
return path
@@ -513,11 +432,11 @@ def module_check_dependencies(self, module):
required_deps = self.module_dependencies[module][0] if module in self.module_dependencies else []
for dep in required_deps:
opt = "module_{}_enabled".format(dep)
- if not opt in self or not self[opt]:
+ if opt not in self or not self[opt]:
missing_deps.append(dep)
if missing_deps != []:
- print(
+ print_warning(
"Disabling '{}' module as the following dependencies are not satisfied: {}".format(
module, ", ".join(missing_deps)
)
@@ -528,7 +447,6 @@ def module_check_dependencies(self, module):
def sort_module_list(env):
- out = OrderedDict()
deps = {k: v[0] + list(filter(lambda x: x in env.module_list, v[1])) for k, v in env.module_dependencies.items()}
frontier = list(env.module_list.keys())
@@ -576,9 +494,7 @@ def use_windows_spawn_fix(self, platform=None):
_, err = proc.communicate()
rv = proc.wait()
if rv:
- print("=====")
- print(err)
- print("=====")
+ print_error(err)
return rv
def mySpawn(sh, escape, cmd, args, env):
@@ -601,65 +517,36 @@ def use_windows_spawn_fix(self, platform=None):
self["SPAWN"] = mySpawn
-def no_verbose(sys, env):
- colors = {}
-
- # Colors are disabled in non-TTY environments such as pipes. This means
- # that if output is redirected to a file, it will not contain color codes
- if sys.stdout.isatty():
- colors["blue"] = "\033[0;94m"
- colors["bold_blue"] = "\033[1;94m"
- colors["reset"] = "\033[0m"
- else:
- colors["blue"] = ""
- colors["bold_blue"] = ""
- colors["reset"] = ""
+def no_verbose(env):
+ colors = [ANSI.BLUE, ANSI.BOLD, ANSI.REGULAR, ANSI.RESET]
# There is a space before "..." to ensure that source file names can be
# Ctrl + clicked in the VS Code terminal.
- compile_source_message = "{}Compiling {}$SOURCE{} ...{}".format(
- colors["blue"], colors["bold_blue"], colors["blue"], colors["reset"]
- )
- java_compile_source_message = "{}Compiling {}$SOURCE{} ...{}".format(
- colors["blue"], colors["bold_blue"], colors["blue"], colors["reset"]
- )
- compile_shared_source_message = "{}Compiling shared {}$SOURCE{} ...{}".format(
- colors["blue"], colors["bold_blue"], colors["blue"], colors["reset"]
- )
- link_program_message = "{}Linking Program {}$TARGET{} ...{}".format(
- colors["blue"], colors["bold_blue"], colors["blue"], colors["reset"]
- )
- link_library_message = "{}Linking Static Library {}$TARGET{} ...{}".format(
- colors["blue"], colors["bold_blue"], colors["blue"], colors["reset"]
- )
- ranlib_library_message = "{}Ranlib Library {}$TARGET{} ...{}".format(
- colors["blue"], colors["bold_blue"], colors["blue"], colors["reset"]
- )
- link_shared_library_message = "{}Linking Shared Library {}$TARGET{} ...{}".format(
- colors["blue"], colors["bold_blue"], colors["blue"], colors["reset"]
- )
- java_library_message = "{}Creating Java Archive {}$TARGET{} ...{}".format(
- colors["blue"], colors["bold_blue"], colors["blue"], colors["reset"]
- )
- compiled_resource_message = "{}Creating Compiled Resource {}$TARGET{} ...{}".format(
- colors["blue"], colors["bold_blue"], colors["blue"], colors["reset"]
- )
- generated_file_message = "{}Generating {}$TARGET{} ...{}".format(
- colors["blue"], colors["bold_blue"], colors["blue"], colors["reset"]
- )
-
- env.Append(CXXCOMSTR=[compile_source_message])
- env.Append(CCCOMSTR=[compile_source_message])
- env.Append(SHCCCOMSTR=[compile_shared_source_message])
- env.Append(SHCXXCOMSTR=[compile_shared_source_message])
- env.Append(ARCOMSTR=[link_library_message])
- env.Append(RANLIBCOMSTR=[ranlib_library_message])
- env.Append(SHLINKCOMSTR=[link_shared_library_message])
- env.Append(LINKCOMSTR=[link_program_message])
- env.Append(JARCOMSTR=[java_library_message])
- env.Append(JAVACCOMSTR=[java_compile_source_message])
- env.Append(RCCOMSTR=[compiled_resource_message])
- env.Append(GENCOMSTR=[generated_file_message])
+ compile_source_message = "{}Compiling {}$SOURCE{} ...{}".format(*colors)
+ java_compile_source_message = "{}Compiling {}$SOURCE{} ...{}".format(*colors)
+ compile_shared_source_message = "{}Compiling shared {}$SOURCE{} ...{}".format(*colors)
+ link_program_message = "{}Linking Program {}$TARGET{} ...{}".format(*colors)
+ link_library_message = "{}Linking Static Library {}$TARGET{} ...{}".format(*colors)
+ ranlib_library_message = "{}Ranlib Library {}$TARGET{} ...{}".format(*colors)
+ link_shared_library_message = "{}Linking Shared Library {}$TARGET{} ...{}".format(*colors)
+ java_library_message = "{}Creating Java Archive {}$TARGET{} ...{}".format(*colors)
+ compiled_resource_message = "{}Creating Compiled Resource {}$TARGET{} ...{}".format(*colors)
+ zip_archive_message = "{}Archiving {}$TARGET{} ...{}".format(*colors)
+ generated_file_message = "{}Generating {}$TARGET{} ...{}".format(*colors)
+
+ env["CXXCOMSTR"] = compile_source_message
+ env["CCCOMSTR"] = compile_source_message
+ env["SHCCCOMSTR"] = compile_shared_source_message
+ env["SHCXXCOMSTR"] = compile_shared_source_message
+ env["ARCOMSTR"] = link_library_message
+ env["RANLIBCOMSTR"] = ranlib_library_message
+ env["SHLINKCOMSTR"] = link_shared_library_message
+ env["LINKCOMSTR"] = link_program_message
+ env["JARCOMSTR"] = java_library_message
+ env["JAVACCOMSTR"] = java_compile_source_message
+ env["RCCOMSTR"] = compiled_resource_message
+ env["ZIPCOMSTR"] = zip_archive_message
+ env["GENCOMSTR"] = generated_file_message
def detect_visual_c_compiler_version(tools_env):
@@ -761,7 +648,7 @@ def detect_visual_c_compiler_version(tools_env):
def find_visual_c_batch_file(env):
- from SCons.Tool.MSCommon.vc import get_default_version, get_host_target, find_batch_file, find_vc_pdir
+ from SCons.Tool.MSCommon.vc import find_batch_file, find_vc_pdir, get_default_version, get_host_target
msvc_version = get_default_version(env)
@@ -790,7 +677,7 @@ def generate_cpp_hint_file(filename):
with open(filename, "w", encoding="utf-8", newline="\n") as fd:
fd.write("#define GDCLASS(m_class, m_inherits)\n")
except OSError:
- print("Could not write cpp.hint file.")
+ print_warning("Could not write cpp.hint file.")
def glob_recursive(pattern, node="."):
@@ -807,10 +694,7 @@ def glob_recursive(pattern, node="."):
def add_to_vs_project(env, sources):
for x in sources:
- if type(x) == type(""):
- fname = env.File(x).path
- else:
- fname = env.File(x)[0].path
+ fname = env.File(x).path if isinstance(x, str) else env.File(x)[0].path
pieces = fname.split(".")
if len(pieces) > 0:
basename = pieces[0]
@@ -881,7 +765,7 @@ def detect_darwin_sdk_path(platform, env):
if sdk_path:
env[var_name] = sdk_path
except (subprocess.CalledProcessError, OSError):
- print("Failed to find SDK path while running xcrun --sdk {} --show-sdk-path.".format(sdk_name))
+ print_error("Failed to find SDK path while running xcrun --sdk {} --show-sdk-path.".format(sdk_name))
raise
@@ -891,7 +775,7 @@ def is_vanilla_clang(env):
try:
version = subprocess.check_output([env.subst(env["CXX"]), "--version"]).strip().decode("utf-8")
except (subprocess.CalledProcessError, OSError):
- print("Couldn't parse CXX environment variable to infer compiler version.")
+ print_warning("Couldn't parse CXX environment variable to infer compiler version.")
return False
return not version.startswith("Apple")
@@ -928,7 +812,7 @@ def get_compiler_version(env):
.decode("utf-8")
)
except (subprocess.CalledProcessError, OSError):
- print("Couldn't parse CXX environment variable to infer compiler version.")
+ print_warning("Couldn't parse CXX environment variable to infer compiler version.")
return ret
else:
# TODO: Implement for MSVC
@@ -995,7 +879,8 @@ def show_progress(env):
return
import sys
- from SCons.Script import Progress, Command, AlwaysBuild
+
+ from SCons.Script import AlwaysBuild, Command, Progress
screen = sys.stdout
# Progress reporting is not available in non-TTY environments since it
@@ -1006,7 +891,8 @@ def show_progress(env):
node_count_interval = 1
node_count_fname = str(env.Dir("#")) + "/.scons_node_count"
- import time, math
+ import math
+ import time
class cache_progress:
# The default is 1 GB cache and 12 hours half life
@@ -1014,7 +900,7 @@ def show_progress(env):
self.path = path
self.limit = limit
self.exponent_scale = math.log(2) / half_life
- if env["verbose"] and path != None:
+ if env["verbose"] and path is not None:
screen.write(
"Current cache limit is {} (used: {})\n".format(
self.convert_size(limit), self.convert_size(self.get_size(path))
@@ -1160,11 +1046,11 @@ def generate_vs_project(env, original_args, project_name="godot"):
if type(f) is Node.FS.Dir:
results += glob_recursive_2(pattern, dirs, f)
r = Glob(str(node) + "/" + pattern, source=True)
- if len(r) > 0 and not str(node) in dirs:
+ if len(r) > 0 and str(node) not in dirs:
d = ""
for part in str(node).split("\\"):
d += part
- if not d in dirs:
+ if d not in dirs:
dirs.append(d)
d += "\\"
results += r
@@ -1177,7 +1063,7 @@ def generate_vs_project(env, original_args, project_name="godot"):
if val is not None:
try:
return _text2bool(val)
- except:
+ except (ValueError, AttributeError):
return default
else:
return default
@@ -1350,13 +1236,13 @@ def generate_vs_project(env, original_args, project_name="godot"):
others_active = []
for x in envsources:
fname = ""
- if type(x) == type(""):
+ if isinstance(x, str):
fname = env.File(x).path
else:
# Some object files might get added directly as a File object and not a list.
try:
fname = env.File(x)[0].path
- except:
+ except Exception:
fname = x.path
pass
@@ -1435,7 +1321,7 @@ def generate_vs_project(env, original_args, project_name="godot"):
itemlist = {}
for item in activeItems:
key = os.path.dirname(item).replace("\\", "_")
- if not key in itemlist:
+ if key not in itemlist:
itemlist[key] = [item]
else:
itemlist[key] += [item]
@@ -1576,14 +1462,14 @@ def generate_vs_project(env, original_args, project_name="godot"):
if godot_platform != "windows":
configurations += [
f'<ProjectConfiguration Include="editor|{proj_plat}">',
- f" <Configuration>editor</Configuration>",
+ " <Configuration>editor</Configuration>",
f" <Platform>{proj_plat}</Platform>",
"</ProjectConfiguration>",
]
properties += [
f"<PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='editor|{proj_plat}'\">",
- f" <GodotConfiguration>editor</GodotConfiguration>",
+ " <GodotConfiguration>editor</GodotConfiguration>",
f" <GodotPlatform>{proj_plat}</GodotPlatform>",
"</PropertyGroup>",
]
@@ -1633,3 +1519,112 @@ def generate_vs_project(env, original_args, project_name="godot"):
if get_bool(original_args, "vsproj_gen_only", True):
sys.exit()
+
+
+def generate_copyright_header(filename: str) -> str:
+ MARGIN = 70
+ TEMPLATE = """\
+/**************************************************************************/
+/* %s*/
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+"""
+ filename = filename.split("/")[-1].ljust(MARGIN)
+ if len(filename) > MARGIN:
+ print(f'WARNING: Filename "{filename}" too large for copyright header.')
+ return TEMPLATE % filename
+
+
+@contextlib.contextmanager
+def generated_wrapper(
+ path, # FIXME: type with `Union[str, Node, List[Node]]` when pytest conflicts are resolved
+ guard: Optional[bool] = None,
+ prefix: str = "",
+ suffix: str = "",
+) -> Generator[TextIOWrapper, None, None]:
+ """
+ Wrapper class to automatically handle copyright headers and header guards
+ for generated scripts. Meant to be invoked via `with` statement similar to
+ creating a file.
+
+ - `path`: The path of the file to be created. Can be passed a raw string, an
+ 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 a header guard should be added. If
+ unassigned, header guards are determined by the file extension.
+ - `prefix`: Custom prefix to prepend to a header guard. Produces a warning if
+ provided a value when `guard` evaluates to `False`.
+ - `suffix`: Custom suffix to append to a header guard. Produces a warning if
+ provided a value when `guard` evaluates to `False`.
+ """
+
+ # Handle unfiltered SCons target[s] passed as path.
+ if not isinstance(path, str):
+ if isinstance(path, list):
+ if len(path) > 1:
+ print_warning(
+ "Attempting to use generated wrapper with multiple targets; "
+ f"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:
+ guard = path.endswith((".h", ".hh", ".hpp", ".inc"))
+ if not guard and (prefix or suffix):
+ print_warning(f'Trying to assign header guard prefix/suffix while `guard` is disabled: "{path}".')
+
+ header_guard = ""
+ if guard:
+ if prefix:
+ prefix += "_"
+ if suffix:
+ suffix = f"_{suffix}"
+ split = path.split("/")[-1].split(".")
+ header_guard = (f"{prefix}{split[0]}{suffix}.{'.'.join(split[1:])}".upper()
+ .replace(".", "_").replace("-", "_").replace(" ", "_").replace("__", "_")) # fmt: skip
+
+ with open(path, "wt", encoding="utf-8", newline="\n") as file:
+ file.write(generate_copyright_header(path))
+ file.write("\n/* THIS FILE IS GENERATED. EDITS WILL BE LOST. */\n\n")
+
+ if guard:
+ file.write(f"#ifndef {header_guard}\n")
+ file.write(f"#define {header_guard}\n\n")
+
+ with StringIO(newline="\n") as str_io:
+ yield str_io
+ file.write(str_io.getvalue().strip() or "/* NO CONTENT */")
+
+ if guard:
+ file.write(f"\n\n#endif // {header_guard}")
+
+ file.write("\n")