feat: updated engine
This commit is contained in:
parent
cbe99774ff
commit
f4cf6b3999
6607 changed files with 910135 additions and 430025 deletions
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Script used to dump char ranges for specific properties from
|
||||
# the Unicode Character Database to the `char_range.inc` file.
|
||||
# the Unicode Character Database to the `char_range.cpp` file.
|
||||
# NOTE: This script is deliberately not integrated into the build system;
|
||||
# you should run it manually whenever you want to update the data.
|
||||
from __future__ import annotations
|
||||
|
|
@ -16,7 +16,7 @@ if __name__ == "__main__":
|
|||
|
||||
from methods import generate_copyright_header
|
||||
|
||||
URL: Final[str] = "https://www.unicode.org/Public/16.0.0/ucd/DerivedCoreProperties.txt"
|
||||
URL: Final[str] = "https://www.unicode.org/Public/17.0.0/ucd/DerivedCoreProperties.txt"
|
||||
|
||||
|
||||
xid_start: list[tuple[int, int]] = []
|
||||
|
|
@ -89,7 +89,8 @@ def parse_unicode_data() -> None:
|
|||
|
||||
|
||||
def make_array(array_name: str, range_list: list[tuple[int, int]]) -> str:
|
||||
result: str = f"\n\nconstexpr inline CharRange {array_name}[] = {{\n"
|
||||
result: str = f"\n\nconst int {array_name}_size = {len(range_list)};\n"
|
||||
result += f"const CharRange {array_name}[{array_name}_size] = {{\n"
|
||||
|
||||
for start, end in range_list:
|
||||
result += f"\t{{ 0x{start:x}, 0x{end:x} }},\n"
|
||||
|
|
@ -102,22 +103,16 @@ def make_array(array_name: str, range_list: list[tuple[int, int]]) -> str:
|
|||
def generate_char_range_inc() -> None:
|
||||
parse_unicode_data()
|
||||
|
||||
source: str = generate_copyright_header("char_range.inc")
|
||||
source: str = generate_copyright_header("char_range.cpp")
|
||||
|
||||
source += f"""
|
||||
// This file was generated using the `misc/scripts/char_range_fetch.py` script.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/typedefs.h"
|
||||
#include "core/string/char_utils.h"
|
||||
|
||||
// Unicode Derived Core Properties
|
||||
// Source: {URL}
|
||||
|
||||
struct CharRange {{
|
||||
\tchar32_t start;
|
||||
\tchar32_t end;
|
||||
}};"""
|
||||
// Source: {URL}\
|
||||
"""
|
||||
|
||||
source += make_array("xid_start", xid_start)
|
||||
source += make_array("xid_continue", xid_continue)
|
||||
|
|
@ -127,11 +122,11 @@ struct CharRange {{
|
|||
|
||||
source += "\n"
|
||||
|
||||
char_range_path: str = os.path.join(os.path.dirname(__file__), "../../core/string/char_range.inc")
|
||||
char_range_path: str = os.path.join(os.path.dirname(__file__), "../../core/string/char_range.cpp")
|
||||
with open(char_range_path, "w", newline="\n") as f:
|
||||
f.write(source)
|
||||
|
||||
print("`char_range.inc` generated successfully.")
|
||||
print("`char_range.cpp` generated successfully.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
|
|
@ -45,7 +46,10 @@ if file_contents.find("ERROR: LeakSanitizer:") != -1:
|
|||
# this possibility should also be handled as a potential error, even if
|
||||
# LeakSanitizer doesn't report anything
|
||||
|
||||
if file_contents.find("ObjectDB instances leaked at exit") != -1:
|
||||
if (
|
||||
file_contents.find("ObjectDB instance was leaked at exit") != -1
|
||||
or file_contents.find("ObjectDB instances were leaked at exit") != -1
|
||||
):
|
||||
print("ERROR: Memory leak was found")
|
||||
sys.exit(54)
|
||||
|
||||
|
|
@ -57,6 +61,12 @@ if file_contents.find("Assertion failed") != -1:
|
|||
print("ERROR: Assertion failed in project, check execution log for more info")
|
||||
sys.exit(55)
|
||||
|
||||
if os.environ.get("GODOT_CHECK_CI_LOG_ALL_ERRORS"):
|
||||
# If any occurrence of "ERROR:" is found in the log, we consider it a failure.
|
||||
if file_contents.find("ERROR:") != -1:
|
||||
print("ERROR: 'ERROR:' found in log and GODOT_CHECK_CI_LOG_ALL_ERRORS is set.")
|
||||
sys.exit(56)
|
||||
|
||||
# For now Godot leaks a lot of rendering stuff so for now we just show info
|
||||
# about it and this needs to be re-enabled after fixing this memory leaks.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import glob
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
|
|
@ -30,4 +31,4 @@ projects = {
|
|||
# Run dotnet format on all projects with more than 0 modified files.
|
||||
for path, files in projects.items():
|
||||
if files:
|
||||
os.system(f"dotnet format {path} --include {files}")
|
||||
subprocess.run(["dotnet", "format", path, "--include", files])
|
||||
|
|
|
|||
49
engine/misc/scripts/install_accesskit.py
Executable file
49
engine/misc/scripts/install_accesskit.py
Executable file
|
|
@ -0,0 +1,49 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
if __name__ != "__main__":
|
||||
raise SystemExit(f'Utility script "{__file__}" should not be used as a module!')
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import urllib.request
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../"))
|
||||
|
||||
|
||||
# Base Godot dependencies path
|
||||
# If cross-compiling (no LOCALAPPDATA), we install in `bin`
|
||||
deps_folder = os.getenv("LOCALAPPDATA")
|
||||
if deps_folder and not os.getenv("MSYSTEM"):
|
||||
deps_folder = os.path.join(deps_folder, "Godot", "build_deps")
|
||||
else:
|
||||
deps_folder = os.path.join("bin", "build_deps")
|
||||
|
||||
# AccessKit
|
||||
ac_version = "0.21.2"
|
||||
|
||||
# Create dependencies folder
|
||||
if not os.path.exists(deps_folder):
|
||||
os.makedirs(deps_folder)
|
||||
|
||||
ac_filename = "accesskit-c-" + ac_version + ".zip"
|
||||
ac_archive = os.path.join(deps_folder, "accesskit.zip")
|
||||
ac_folder = os.path.join(deps_folder, "accesskit")
|
||||
|
||||
if os.path.isfile(ac_archive):
|
||||
os.remove(ac_archive)
|
||||
|
||||
print(f"Downloading AccessKit {ac_filename} ...")
|
||||
urllib.request.urlretrieve(
|
||||
f"https://github.com/godotengine/godot-accesskit-c-static/releases/download/{ac_version}/{ac_filename}",
|
||||
ac_archive,
|
||||
)
|
||||
if os.path.exists(ac_folder):
|
||||
print(f"Removing existing local AccessKit installation in {ac_folder} ...")
|
||||
shutil.rmtree(ac_folder)
|
||||
print(f"Extracting AccessKit {ac_filename} to {ac_folder} ...")
|
||||
shutil.unpack_archive(ac_archive, deps_folder)
|
||||
os.remove(ac_archive)
|
||||
os.rename(os.path.join(deps_folder, "accesskit-c-" + ac_version), ac_folder)
|
||||
|
||||
print("AccessKit installed successfully.\n")
|
||||
73
engine/misc/scripts/install_angle.py
Executable file
73
engine/misc/scripts/install_angle.py
Executable file
|
|
@ -0,0 +1,73 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import sys
|
||||
import urllib.request
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../"))
|
||||
|
||||
from misc.utility.color import Ansi, color_print
|
||||
|
||||
# Base Godot dependencies path
|
||||
# If cross-compiling (no LOCALAPPDATA), we install in `bin`
|
||||
deps_folder = os.getenv("LOCALAPPDATA")
|
||||
if deps_folder and not os.getenv("MSYSTEM"):
|
||||
deps_folder = os.path.join(deps_folder, "Godot", "build_deps")
|
||||
else:
|
||||
deps_folder = os.path.join("bin", "build_deps")
|
||||
|
||||
# ANGLE
|
||||
# Check for latest version: https://github.com/godotengine/godot-angle-static/releases/latest
|
||||
angle_version = "chromium/7219"
|
||||
angle_folder = os.path.join(deps_folder, "angle")
|
||||
|
||||
# Create dependencies folder
|
||||
if not os.path.exists(deps_folder):
|
||||
os.makedirs(deps_folder)
|
||||
|
||||
# Mesa NIR
|
||||
print(f"Downloading ANGLE {angle_version} ...")
|
||||
archs = [
|
||||
"arm64-llvm",
|
||||
"x86_32-gcc",
|
||||
"x86_32-llvm",
|
||||
"x86_64-gcc",
|
||||
"x86_64-llvm",
|
||||
]
|
||||
if platform.system() == "Windows":
|
||||
# Only download MSVC libraries if we can build using it.
|
||||
archs.append("arm64-msvc")
|
||||
archs.append("x86_32-msvc")
|
||||
archs.append("x86_64-msvc")
|
||||
elif platform.system() == "Darwin":
|
||||
# Only download macOS/iOS libraries if we can build for these platforms.
|
||||
archs.append("arm64-ios")
|
||||
archs.append("arm64-ios-sim")
|
||||
archs.append("arm64-macos")
|
||||
archs.append("x86_64-macos")
|
||||
|
||||
for arch in archs:
|
||||
angle_filename = f"godot-angle-static-{arch}-release.zip"
|
||||
angle_archive = os.path.join(deps_folder, angle_filename)
|
||||
angle_folder = os.path.join(deps_folder, f"angle-{arch}")
|
||||
|
||||
if os.path.isfile(angle_archive):
|
||||
os.remove(angle_archive)
|
||||
print(f"Downloading ANGLE {angle_filename} ...")
|
||||
urllib.request.urlretrieve(
|
||||
f"https://github.com/godotengine/godot-angle-static/releases/download/{angle_version}/{angle_filename}",
|
||||
angle_archive,
|
||||
)
|
||||
if os.path.exists(angle_folder):
|
||||
print(f"Removing existing local ANGLE installation in {angle_folder} ...")
|
||||
shutil.rmtree(angle_folder)
|
||||
print(f"Extracting ANGLE {angle_filename} to {angle_folder} ...")
|
||||
shutil.unpack_archive(angle_archive, angle_folder)
|
||||
os.remove(angle_archive)
|
||||
print("ANGLE installed successfully.\n")
|
||||
|
||||
# Complete message
|
||||
color_print(f'{Ansi.GREEN}All ANGLE components were installed to "{deps_folder}" successfully!')
|
||||
color_print(f'{Ansi.GREEN}You can now build Godot with statically linked ANGLE by running "scons angle=yes".')
|
||||
|
|
@ -25,7 +25,7 @@ args = parser.parse_args()
|
|||
# Base Godot dependencies path
|
||||
# If cross-compiling (no LOCALAPPDATA), we install in `bin`
|
||||
deps_folder = os.getenv("LOCALAPPDATA")
|
||||
if deps_folder:
|
||||
if deps_folder and not os.getenv("MSYSTEM"):
|
||||
deps_folder = os.path.join(deps_folder, "Godot", "build_deps")
|
||||
else:
|
||||
deps_folder = os.path.join("bin", "build_deps")
|
||||
|
|
@ -33,7 +33,7 @@ else:
|
|||
# Mesa NIR
|
||||
# Sync with `drivers/d3d12/SCsub` when updating Mesa.
|
||||
# Check for latest version: https://github.com/godotengine/godot-nir-static/releases/latest
|
||||
mesa_version = "25.3.1-1"
|
||||
mesa_version = "25.3.1-3"
|
||||
# WinPixEventRuntime
|
||||
# Check for latest version: https://www.nuget.org/api/v2/package/WinPixEventRuntime (check downloaded filename)
|
||||
pix_version = "1.0.240308001"
|
||||
|
|
|
|||
47
engine/misc/scripts/install_perfetto.py
Executable file
47
engine/misc/scripts/install_perfetto.py
Executable file
|
|
@ -0,0 +1,47 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import urllib.request
|
||||
from zipfile import ZipFile
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../"))
|
||||
|
||||
from misc.utility.color import Ansi, color_print
|
||||
|
||||
|
||||
def get_latest_tag():
|
||||
import json
|
||||
|
||||
url = "https://api.github.com/repos/google/perfetto/releases/latest"
|
||||
with urllib.request.urlopen(url) as response:
|
||||
data = json.load(response)
|
||||
return data["tag_name"]
|
||||
|
||||
|
||||
# Perfetto
|
||||
# Check for latest version: https://github.com/google/perfetto/releases/latest
|
||||
perfetto_tag = get_latest_tag()
|
||||
perfetto_filename = "perfetto-cpp-sdk-src.zip"
|
||||
perfetto_folder = "thirdparty/perfetto"
|
||||
|
||||
perfetto_archive_destination = os.path.join(tempfile.gettempdir(), perfetto_filename)
|
||||
|
||||
if os.path.isfile(perfetto_archive_destination):
|
||||
os.remove(perfetto_archive_destination)
|
||||
|
||||
print(f"Downloading Perfetto {perfetto_tag} ...")
|
||||
urllib.request.urlretrieve(
|
||||
f"https://github.com/google/perfetto/releases/download/{perfetto_tag}/{perfetto_filename}",
|
||||
perfetto_archive_destination,
|
||||
)
|
||||
|
||||
print(f"Extracting Perfetto {perfetto_tag} to {perfetto_folder} ...")
|
||||
with ZipFile(perfetto_archive_destination, "r") as zip_file:
|
||||
zip_file.extractall(perfetto_folder)
|
||||
os.remove(perfetto_archive_destination)
|
||||
print("Perfetto installed successfully.\n")
|
||||
|
||||
# Complete message
|
||||
color_print(f'{Ansi.GREEN}Perfetto was installed to "{perfetto_folder}" successfully!')
|
||||
48
engine/misc/scripts/install_winrt.py
Executable file
48
engine/misc/scripts/install_winrt.py
Executable file
|
|
@ -0,0 +1,48 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
if __name__ != "__main__":
|
||||
raise SystemExit(f'Utility script "{__file__}" should not be used as a module!')
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import urllib.request
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../"))
|
||||
|
||||
|
||||
# Base Godot dependencies path
|
||||
# If cross-compiling (no LOCALAPPDATA), we install in `bin`
|
||||
deps_folder = os.getenv("LOCALAPPDATA")
|
||||
if deps_folder and not os.getenv("MSYSTEM"):
|
||||
deps_folder = os.path.join(deps_folder, "Godot", "build_deps")
|
||||
else:
|
||||
deps_folder = os.path.join("bin", "build_deps")
|
||||
|
||||
# WinRT
|
||||
winrt_version = "72"
|
||||
|
||||
# Create dependencies folder
|
||||
if not os.path.exists(deps_folder):
|
||||
os.makedirs(deps_folder)
|
||||
|
||||
winrt_filename = "winrt-headers.zip"
|
||||
winrt_archive = os.path.join(deps_folder, winrt_filename)
|
||||
winrt_folder = os.path.join(deps_folder, "winrt_mingw")
|
||||
|
||||
if os.path.isfile(winrt_archive):
|
||||
os.remove(winrt_archive)
|
||||
|
||||
print(f"Downloading WinRT {winrt_filename} ...")
|
||||
urllib.request.urlretrieve(
|
||||
f"https://github.com/godotengine/winrt-mingw/releases/download/{winrt_version}/{winrt_filename}",
|
||||
winrt_archive,
|
||||
)
|
||||
if os.path.exists(winrt_folder):
|
||||
print(f"Removing existing local WinRT installation in {winrt_folder} ...")
|
||||
shutil.rmtree(winrt_folder)
|
||||
print(f"Extracting WinRT {winrt_filename} to {winrt_folder} ...")
|
||||
shutil.unpack_archive(winrt_archive, winrt_folder)
|
||||
os.remove(winrt_archive)
|
||||
|
||||
print("WinRT installed successfully.\n")
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
for s in 16 24 32 48 64 128 256 512 1024; do
|
||||
convert -resize ${s}x$s -antialias \
|
||||
-background transparent \
|
||||
../../icon.svg icon$s.png
|
||||
../../misc/logo/icon.svg icon$s.png
|
||||
done
|
||||
|
||||
# 16px tga file for library
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ if __name__ == "__main__":
|
|||
|
||||
from methods import generate_copyright_header
|
||||
|
||||
URL: Final[str] = "https://www.unicode.org/Public/16.0.0/ucd/UnicodeData.txt"
|
||||
URL: Final[str] = "https://www.unicode.org/Public/17.0.0/ucd/UnicodeData.txt"
|
||||
|
||||
|
||||
lower_to_upper: list[tuple[str, str]] = []
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Script used to dump char ranges from
|
||||
# the Unicode Character Database to the `char_range.inc` file.
|
||||
# the Unicode Character Database to the `unicode_ranges.inc` file.
|
||||
# NOTE: This script is deliberately not integrated into the build system;
|
||||
# you should run it manually whenever you want to update the data.
|
||||
from __future__ import annotations
|
||||
|
|
@ -16,7 +16,7 @@ if __name__ == "__main__":
|
|||
|
||||
from methods import generate_copyright_header
|
||||
|
||||
URL: Final[str] = "https://www.unicode.org/Public/16.0.0/ucd/Blocks.txt"
|
||||
URL: Final[str] = "https://www.unicode.org/Public/17.0.0/ucd/Blocks.txt"
|
||||
|
||||
|
||||
ranges: list[tuple[str, str, str]] = []
|
||||
|
|
|
|||
148
engine/misc/scripts/validate_codeowners.py
Executable file
148
engine/misc/scripts/validate_codeowners.py
Executable file
|
|
@ -0,0 +1,148 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
if __name__ != "__main__":
|
||||
raise SystemExit(f'Utility script "{__file__}" should not be used as a module!')
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, "./")
|
||||
|
||||
try:
|
||||
from methods import print_error, print_info
|
||||
except ImportError:
|
||||
raise SystemExit(f"Utility script {__file__} must be run from repository root!")
|
||||
|
||||
|
||||
def glob_to_regex(glob: str) -> re.Pattern[str]:
|
||||
"""Convert a CODEOWNERS glob to a RegEx pattern."""
|
||||
|
||||
# Heavily inspired by: https://github.com/hmarr/codeowners/blob/main/match.go
|
||||
|
||||
# Handle specific edgecases first.
|
||||
if "***" in glob:
|
||||
raise SyntaxError("Pattern cannot contain three consecutive asterisks")
|
||||
if glob == "/":
|
||||
raise SyntaxError('Standalone "/" will not match anything')
|
||||
if not glob:
|
||||
raise ValueError("Empty pattern")
|
||||
|
||||
segments = glob.split("/")
|
||||
if not segments[0]:
|
||||
# Leading slash; relative to root.
|
||||
segments = segments[1:]
|
||||
else:
|
||||
# Check for single-segment pattern, which matches relative to any descendent path.
|
||||
# This is equivalent to a leading `**/`.
|
||||
if len(segments) == 1 or (len(segments) == 2 and not segments[1]):
|
||||
if segments[0] != "**":
|
||||
segments.insert(0, "**")
|
||||
|
||||
if len(segments) > 1 and not segments[-1]:
|
||||
# A trailing slash is equivalent to `/**`.
|
||||
segments[-1] = "**"
|
||||
|
||||
last_index = len(segments) - 1
|
||||
need_slash = False
|
||||
pattern = r"\A"
|
||||
|
||||
for index, segment in enumerate(segments):
|
||||
if segment == "**":
|
||||
if index == 0 and index == last_index:
|
||||
pattern += r".+" # Pattern is just `**`; match everything.
|
||||
elif index == 0:
|
||||
pattern += r"(?:.+/)?" # Pattern starts with `**`; match any leading path segment.
|
||||
need_slash = False
|
||||
elif index == last_index:
|
||||
pattern += r"/.*" # Pattern ends with `**`; match any trailing path segment.
|
||||
else:
|
||||
pattern += r"(?:/.+)?" # Pattern contains `**`; match zero or more path segments.
|
||||
need_slash = True
|
||||
|
||||
elif segment == "*":
|
||||
if need_slash:
|
||||
pattern += "/"
|
||||
# Regular wildcard; match any non-separator characters.
|
||||
pattern += r"[^/]+"
|
||||
need_slash = True
|
||||
|
||||
else:
|
||||
if need_slash:
|
||||
pattern += "/"
|
||||
|
||||
escape = False
|
||||
for char in segment:
|
||||
if escape:
|
||||
escape = False
|
||||
pattern += re.escape(char)
|
||||
continue
|
||||
elif char == "\\":
|
||||
escape = True
|
||||
elif char == "*":
|
||||
# Multi-character wildcard.
|
||||
pattern += r"[^/]*"
|
||||
elif char == "?":
|
||||
# Single-character wildcard.
|
||||
pattern += r"[^/]"
|
||||
else:
|
||||
# Regular character
|
||||
pattern += re.escape(char)
|
||||
|
||||
if index == last_index:
|
||||
pattern += r"(?:/.*)?" # No trailing slash; match descendent paths.
|
||||
need_slash = True
|
||||
|
||||
pattern += r"\Z"
|
||||
return re.compile(pattern)
|
||||
|
||||
|
||||
RE_CODEOWNERS = re.compile(r"^(?P<code>[^#](?:\\ |[^\s])+) +(?P<owners>(?:[^#][^\s]+ ?)+)")
|
||||
|
||||
|
||||
def parse_codeowners() -> list[tuple[re.Pattern[str], list[str]]]:
|
||||
codeowners = []
|
||||
with open(".github/CODEOWNERS", encoding="utf-8", newline="\n") as file:
|
||||
for line in reversed(file.readlines()): # Lower items have higher precedence.
|
||||
if match := RE_CODEOWNERS.match(line):
|
||||
codeowners.append((glob_to_regex(match["code"]), match["owners"].split()))
|
||||
return codeowners
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Utility script for validating CODEOWNERS assignment.")
|
||||
parser.add_argument("files", nargs="*", help="A list of files to validate. If excluded, checks all owned files.")
|
||||
parser.add_argument("-u", "--unowned", action="store_true", help="Only output files without an owner.")
|
||||
args = parser.parse_args()
|
||||
|
||||
files: list[str] = args.files
|
||||
if not files:
|
||||
files = subprocess.run(["git", "ls-files"], text=True, capture_output=True).stdout.splitlines()
|
||||
|
||||
ret = 0
|
||||
codeowners = parse_codeowners()
|
||||
|
||||
for file in files:
|
||||
matched = False
|
||||
for code, owners in codeowners:
|
||||
if code.match(file):
|
||||
matched = True
|
||||
if not args.unowned:
|
||||
print_info(f"{file}: {owners}")
|
||||
break
|
||||
if not matched:
|
||||
print_error(f"{file}: <UNOWNED>")
|
||||
ret += 1
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
try:
|
||||
raise SystemExit(main())
|
||||
except KeyboardInterrupt:
|
||||
import os
|
||||
import signal
|
||||
|
||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||
os.kill(os.getpid(), signal.SIGINT)
|
||||
|
|
@ -69,7 +69,7 @@ while read -r dir; do
|
|||
get_expected_output "$dir"
|
||||
|
||||
# Download the reference extension_api.json
|
||||
wget -nv --retry-on-http-error=503 --tries=5 --timeout=60 -cO "$reference_file" "https://raw.githubusercontent.com/godotengine/godot-cpp/godot-$reference_tag/gdextension/extension_api.json" || has_problems=1
|
||||
wget -nv --retry-on-http-error=503 --tries=5 --timeout=60 -cO "$reference_file" "https://raw.githubusercontent.com/godotengine/godot-headers/godot-$reference_tag/extension_api.json" || has_problems=1
|
||||
# Validate the current API against the reference
|
||||
"$1" --headless --validate-extension-api "$reference_file" 2>&1 | tee "$validate" | awk '!/^Validate extension JSON:/' - || true
|
||||
# Collect the expected and actual validation errors
|
||||
|
|
|
|||
111
engine/misc/scripts/validate_includes.py
Executable file
111
engine/misc/scripts/validate_includes.py
Executable file
|
|
@ -0,0 +1,111 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
if __name__ != "__main__":
|
||||
raise ImportError(f'Utility script "{__file__}" should not be used as a module!')
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
if Path(os.getcwd()).as_posix() != (ROOT := Path(__file__).parent.parent.parent).as_posix():
|
||||
raise RuntimeError(f'Utility script "{__file__}" must be run from the repository root!')
|
||||
|
||||
import argparse
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
|
||||
BASE_FOLDER = Path(__file__).resolve().parent.parent.parent
|
||||
RE_INCLUDES = re.compile(
|
||||
r"^#(?P<keyword>include|import) " # Both `include` and `import` keywords valid.
|
||||
r'(?P<sequence>[<"])' # Handle both styles of char wrappers. Does NOT handle macro expansion.
|
||||
r'(?P<path>.+?)[>"]', # Resolve path of include itself. Can safely assume the sequence matches.
|
||||
re.RegexFlag.MULTILINE,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class IncludeData:
|
||||
path: str
|
||||
is_angle: bool
|
||||
is_import: bool
|
||||
|
||||
@staticmethod
|
||||
def from_match(match: re.Match[str]):
|
||||
items = match.groupdict()
|
||||
return IncludeData(
|
||||
items["path"],
|
||||
items["sequence"] == "<",
|
||||
items["keyword"] == "import",
|
||||
)
|
||||
|
||||
def copy(self):
|
||||
return IncludeData(self.path, self.is_angle, self.is_import)
|
||||
|
||||
def __str__(self):
|
||||
return "#{keyword} {rbracket}{path}{lbracket}".format(
|
||||
keyword="import" if self.is_import else "include",
|
||||
rbracket="<" if self.is_angle else '"',
|
||||
path=self.path,
|
||||
lbracket=">" if self.is_angle else '"',
|
||||
)
|
||||
|
||||
|
||||
def validate_includes(path: Path) -> int:
|
||||
ret = 0
|
||||
content = path.read_text(encoding="utf-8")
|
||||
|
||||
for data in map(IncludeData.from_match, RE_INCLUDES.finditer(content)):
|
||||
original_data = data.copy()
|
||||
|
||||
if "\\" in data.path:
|
||||
data.path = data.path.replace("\\", "/")
|
||||
|
||||
if data.path.startswith("thirdparty/"):
|
||||
data.is_angle = True
|
||||
|
||||
if (relative_path := path.parent / data.path).exists():
|
||||
# Relative includes are only permitted under certain circumstances.
|
||||
|
||||
if relative_path.name.split(".")[0] == path.name.split(".")[0]:
|
||||
# Identical leading names permitted
|
||||
pass
|
||||
|
||||
elif ("modules" in relative_path.parts and "modules" in path.parts) or (
|
||||
"platform" in relative_path.parts and "platform" in path.parts
|
||||
):
|
||||
# Modules and platforms can use relative includes if constrained to the module/platform itself.
|
||||
pass
|
||||
|
||||
else:
|
||||
data.path = relative_path.resolve().relative_to(BASE_FOLDER).as_posix()
|
||||
|
||||
if original_data != data:
|
||||
content = content.replace(f"{original_data}", f"{data}")
|
||||
ret += 1
|
||||
|
||||
if ret:
|
||||
with open(path, "w", encoding="utf-8", newline="\n") as file:
|
||||
file.write(content)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Validate C/C++ includes, correcting if necessary")
|
||||
parser.add_argument("files", nargs="+", help="A list of files to validate")
|
||||
args = parser.parse_args()
|
||||
|
||||
ret = 0
|
||||
|
||||
for file in map(Path, args.files):
|
||||
ret += validate_includes(file)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
try:
|
||||
raise SystemExit(main())
|
||||
except KeyboardInterrupt:
|
||||
import signal
|
||||
|
||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||
os.kill(os.getpid(), signal.SIGINT)
|
||||
Loading…
Add table
Add a link
Reference in a new issue