feat: updated engine version to 4.4-rc1
This commit is contained in:
parent
ee00efde1f
commit
21ba8e33af
5459 changed files with 1128836 additions and 198305 deletions
|
|
@ -1,25 +1,25 @@
|
|||
import atexit
|
||||
import contextlib
|
||||
import glob
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from collections import OrderedDict
|
||||
from enum import Enum
|
||||
from io import StringIO, TextIOWrapper
|
||||
from io import StringIO, TextIOBase
|
||||
from pathlib import Path
|
||||
from typing import Generator, Optional
|
||||
from typing import Generator, List, Optional, Union, cast
|
||||
|
||||
from misc.utility.color import print_error, print_info, print_warning
|
||||
|
||||
# Get the "Godot" folder name ahead of time
|
||||
base_folder_path = str(os.path.abspath(Path(__file__).parent)) + "/"
|
||||
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):
|
||||
|
|
@ -27,67 +27,15 @@ 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))
|
||||
return
|
||||
files = [files]
|
||||
else:
|
||||
# Exclude .gen.cpp files from globbing, to avoid including obsolete ones.
|
||||
# They should instead be added manually.
|
||||
skip_gen_cpp = "*" in files
|
||||
dir_path = self.Dir(".").abspath
|
||||
files = sorted(glob.glob(dir_path + "/" + files))
|
||||
if skip_gen_cpp and not allow_gen:
|
||||
files = [f for f in files if not f.endswith(".gen.cpp")]
|
||||
if isinstance(files, str):
|
||||
# Exclude .gen.cpp files from globbing, to avoid including obsolete ones.
|
||||
# They should instead be added manually.
|
||||
skip_gen_cpp = "*" in files
|
||||
files = self.Glob(files)
|
||||
if skip_gen_cpp and not allow_gen:
|
||||
files = [f for f in files if not str(f).endswith(".gen.cpp")]
|
||||
|
||||
# Add each path as compiled Object following environment (self) configuration
|
||||
for path in files:
|
||||
|
|
@ -98,35 +46,6 @@ def add_source_files_orig(self, sources, files, allow_gen=False):
|
|||
sources.append(obj)
|
||||
|
||||
|
||||
# The section name is used for checking
|
||||
# the hash table to see whether the folder
|
||||
# is included in the SCU build.
|
||||
# It will be something like "core/math".
|
||||
def _find_scu_section_name(subdir):
|
||||
section_path = os.path.abspath(subdir) + "/"
|
||||
|
||||
folders = []
|
||||
folder = ""
|
||||
|
||||
for i in range(8):
|
||||
folder = os.path.dirname(section_path)
|
||||
folder = os.path.basename(folder)
|
||||
if folder == base_folder_only:
|
||||
break
|
||||
folders += [folder]
|
||||
section_path += "../"
|
||||
section_path = os.path.abspath(section_path) + "/"
|
||||
|
||||
section_name = ""
|
||||
for n in range(len(folders)):
|
||||
# section_name += folders[len(folders) - n - 1] + " "
|
||||
section_name += folders[len(folders) - n - 1]
|
||||
if n != (len(folders) - 1):
|
||||
section_name += "/"
|
||||
|
||||
return section_name
|
||||
|
||||
|
||||
def add_source_files_scu(self, sources, files, allow_gen=False):
|
||||
if self["scu_build"] and isinstance(files, str):
|
||||
if "*." not in files:
|
||||
|
|
@ -135,10 +54,9 @@ def add_source_files_scu(self, sources, files, allow_gen=False):
|
|||
# If the files are in a subdirectory, we want to create the scu gen
|
||||
# files inside this subdirectory.
|
||||
subdir = os.path.dirname(files)
|
||||
if subdir != "":
|
||||
subdir += "/"
|
||||
|
||||
section_name = _find_scu_section_name(subdir)
|
||||
subdir = subdir if subdir == "" else subdir + "/"
|
||||
section_name = self.Dir(subdir).tpath
|
||||
section_name = section_name.replace("\\", "/") # win32
|
||||
# if the section name is in the hash table?
|
||||
# i.e. is it part of the SCU build?
|
||||
global _scu_folders
|
||||
|
|
@ -163,12 +81,13 @@ def add_source_files(self, sources, files, allow_gen=False):
|
|||
|
||||
def disable_warnings(self):
|
||||
# 'self' is the environment
|
||||
if self.msvc:
|
||||
if self.msvc and not using_clang(self):
|
||||
# We have to remove existing warning level defines before appending /w,
|
||||
# otherwise we get: "warning D9025 : overriding '/W3' with '/w'"
|
||||
self["CCFLAGS"] = [x for x in self["CCFLAGS"] if not (x.startswith("/W") or x.startswith("/w"))]
|
||||
self["CFLAGS"] = [x for x in self["CFLAGS"] if not (x.startswith("/W") or x.startswith("/w"))]
|
||||
self["CXXFLAGS"] = [x for x in self["CXXFLAGS"] if not (x.startswith("/W") or x.startswith("/w"))]
|
||||
WARN_FLAGS = ["/Wall", "/W4", "/W3", "/W2", "/W1", "/W0"]
|
||||
self["CCFLAGS"] = [x for x in self["CCFLAGS"] if x not in WARN_FLAGS]
|
||||
self["CFLAGS"] = [x for x in self["CFLAGS"] if x not in WARN_FLAGS]
|
||||
self["CXXFLAGS"] = [x for x in self["CXXFLAGS"] if x not in WARN_FLAGS]
|
||||
self.AppendUnique(CCFLAGS=["/w"])
|
||||
else:
|
||||
self.AppendUnique(CCFLAGS=["-w"])
|
||||
|
|
@ -199,7 +118,7 @@ def get_version_info(module_version_string="", silent=False):
|
|||
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}'.")
|
||||
print_info(f"Using custom build name: '{build_name}'.")
|
||||
|
||||
import version
|
||||
|
||||
|
|
@ -221,7 +140,7 @@ def get_version_info(module_version_string="", silent=False):
|
|||
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}'.")
|
||||
print_info(f"Using version status '{version_info['status']}', overriding the original '{version.status}'.")
|
||||
|
||||
# Parse Git hash if we're in a Git repo.
|
||||
githash = ""
|
||||
|
|
@ -277,34 +196,6 @@ def get_version_info(module_version_string="", silent=False):
|
|||
return version_info
|
||||
|
||||
|
||||
def parse_cg_file(fname, uniforms, sizes, conditionals):
|
||||
with open(fname, "r", encoding="utf-8") as fs:
|
||||
line = fs.readline()
|
||||
|
||||
while line:
|
||||
if re.match(r"^\s*uniform", line):
|
||||
res = re.match(r"uniform ([\d\w]*) ([\d\w]*)")
|
||||
type = res.groups(1)
|
||||
name = res.groups(2)
|
||||
|
||||
uniforms.append(name)
|
||||
|
||||
if type.find("texobj") != -1:
|
||||
sizes.append(1)
|
||||
else:
|
||||
t = re.match(r"float(\d)x(\d)", type)
|
||||
if t:
|
||||
sizes.append(int(t.groups(1)) * int(t.groups(2)))
|
||||
else:
|
||||
t = re.match(r"float(\d)", type)
|
||||
sizes.append(int(t.groups(1)))
|
||||
|
||||
if line.find("[branch]") != -1:
|
||||
conditionals.append(name)
|
||||
|
||||
line = fs.readline()
|
||||
|
||||
|
||||
def get_cmdline_bool(option, default):
|
||||
"""We use `ARGUMENTS.get()` to check if options were manually overridden on the command line,
|
||||
and SCons' _text2bool helper to convert them to booleans, otherwise they're handled as strings.
|
||||
|
|
@ -404,10 +295,6 @@ def convert_custom_modules_path(path):
|
|||
return path
|
||||
|
||||
|
||||
def disable_module(self):
|
||||
self.disabled_modules.append(self.current_module)
|
||||
|
||||
|
||||
def module_add_dependencies(self, module, dependencies, optional=False):
|
||||
"""
|
||||
Adds dependencies for a given module.
|
||||
|
|
@ -428,19 +315,21 @@ def module_check_dependencies(self, module):
|
|||
Meant to be used in module `can_build` methods.
|
||||
Returns a boolean (True if dependencies are satisfied).
|
||||
"""
|
||||
missing_deps = []
|
||||
missing_deps = set()
|
||||
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 opt not in self or not self[opt]:
|
||||
missing_deps.append(dep)
|
||||
if opt not in self or not self[opt] or not module_check_dependencies(self, dep):
|
||||
missing_deps.add(dep)
|
||||
|
||||
if missing_deps != []:
|
||||
print_warning(
|
||||
"Disabling '{}' module as the following dependencies are not satisfied: {}".format(
|
||||
module, ", ".join(missing_deps)
|
||||
if missing_deps:
|
||||
if module not in self.disabled_modules:
|
||||
print_warning(
|
||||
"Disabling '{}' module as the following dependencies are not satisfied: {}".format(
|
||||
module, ", ".join(missing_deps)
|
||||
)
|
||||
)
|
||||
)
|
||||
self.disabled_modules.add(module)
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
|
@ -467,16 +356,6 @@ def use_windows_spawn_fix(self, platform=None):
|
|||
if os.name != "nt":
|
||||
return # not needed, only for windows
|
||||
|
||||
# On Windows, due to the limited command line length, when creating a static library
|
||||
# from a very high number of objects SCons will invoke "ar" once per object file;
|
||||
# that makes object files with same names to be overwritten so the last wins and
|
||||
# the library loses symbols defined by overwritten objects.
|
||||
# By enabling quick append instead of the default mode (replacing), libraries will
|
||||
# got built correctly regardless the invocation strategy.
|
||||
# Furthermore, since SCons will rebuild the library from scratch when an object file
|
||||
# changes, no multiple versions of the same object file will be present.
|
||||
self.Replace(ARFLAGS="q")
|
||||
|
||||
def mySubProcess(cmdline, env):
|
||||
startupinfo = subprocess.STARTUPINFO()
|
||||
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
|
||||
|
|
@ -488,29 +367,28 @@ def use_windows_spawn_fix(self, platform=None):
|
|||
"shell": False,
|
||||
"env": env,
|
||||
}
|
||||
if sys.version_info >= (3, 7, 0):
|
||||
popen_args["text"] = True
|
||||
popen_args["text"] = True
|
||||
proc = subprocess.Popen(cmdline, **popen_args)
|
||||
_, err = proc.communicate()
|
||||
rv = proc.wait()
|
||||
if rv:
|
||||
print_error(err)
|
||||
elif len(err) > 0 and not err.isspace():
|
||||
print(err)
|
||||
return rv
|
||||
|
||||
def mySpawn(sh, escape, cmd, args, env):
|
||||
# Used by TEMPFILE.
|
||||
if cmd == "del":
|
||||
os.remove(args[1])
|
||||
return 0
|
||||
|
||||
newargs = " ".join(args[1:])
|
||||
cmdline = cmd + " " + newargs
|
||||
|
||||
rv = 0
|
||||
env = {str(key): str(value) for key, value in iter(env.items())}
|
||||
if len(cmdline) > 32000 and cmd.endswith("ar"):
|
||||
cmdline = cmd + " " + args[1] + " " + args[2] + " "
|
||||
for i in range(3, len(args)):
|
||||
rv = mySubProcess(cmdline + args[i], env)
|
||||
if rv:
|
||||
break
|
||||
else:
|
||||
rv = mySubProcess(cmdline, env)
|
||||
rv = mySubProcess(cmdline, env)
|
||||
|
||||
return rv
|
||||
|
||||
|
|
@ -518,7 +396,9 @@ def use_windows_spawn_fix(self, platform=None):
|
|||
|
||||
|
||||
def no_verbose(env):
|
||||
colors = [ANSI.BLUE, ANSI.BOLD, ANSI.REGULAR, ANSI.RESET]
|
||||
from misc.utility.color import Ansi
|
||||
|
||||
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.
|
||||
|
|
@ -575,40 +455,7 @@ def detect_visual_c_compiler_version(tools_env):
|
|||
vc_chosen_compiler_index = -1
|
||||
vc_chosen_compiler_str = ""
|
||||
|
||||
# Start with Pre VS 2017 checks which uses VCINSTALLDIR:
|
||||
if "VCINSTALLDIR" in tools_env:
|
||||
# print("Checking VCINSTALLDIR")
|
||||
|
||||
# find() works with -1 so big ifs below are needed... the simplest solution, in fact
|
||||
# First test if amd64 and amd64_x86 compilers are present in the path
|
||||
vc_amd64_compiler_detection_index = tools_env["PATH"].find(tools_env["VCINSTALLDIR"] + "BIN\\amd64;")
|
||||
if vc_amd64_compiler_detection_index > -1:
|
||||
vc_chosen_compiler_index = vc_amd64_compiler_detection_index
|
||||
vc_chosen_compiler_str = "amd64"
|
||||
|
||||
vc_amd64_x86_compiler_detection_index = tools_env["PATH"].find(tools_env["VCINSTALLDIR"] + "BIN\\amd64_x86;")
|
||||
if vc_amd64_x86_compiler_detection_index > -1 and (
|
||||
vc_chosen_compiler_index == -1 or vc_chosen_compiler_index > vc_amd64_x86_compiler_detection_index
|
||||
):
|
||||
vc_chosen_compiler_index = vc_amd64_x86_compiler_detection_index
|
||||
vc_chosen_compiler_str = "amd64_x86"
|
||||
|
||||
# Now check the 32 bit compilers
|
||||
vc_x86_compiler_detection_index = tools_env["PATH"].find(tools_env["VCINSTALLDIR"] + "BIN;")
|
||||
if vc_x86_compiler_detection_index > -1 and (
|
||||
vc_chosen_compiler_index == -1 or vc_chosen_compiler_index > vc_x86_compiler_detection_index
|
||||
):
|
||||
vc_chosen_compiler_index = vc_x86_compiler_detection_index
|
||||
vc_chosen_compiler_str = "x86"
|
||||
|
||||
vc_x86_amd64_compiler_detection_index = tools_env["PATH"].find(tools_env["VCINSTALLDIR"] + "BIN\\x86_amd64;")
|
||||
if vc_x86_amd64_compiler_detection_index > -1 and (
|
||||
vc_chosen_compiler_index == -1 or vc_chosen_compiler_index > vc_x86_amd64_compiler_detection_index
|
||||
):
|
||||
vc_chosen_compiler_index = vc_x86_amd64_compiler_detection_index
|
||||
vc_chosen_compiler_str = "x86_amd64"
|
||||
|
||||
# and for VS 2017 and newer we check VCTOOLSINSTALLDIR:
|
||||
# VS 2017 and newer should set VCTOOLSINSTALLDIR
|
||||
if "VCTOOLSINSTALLDIR" in tools_env:
|
||||
# Newer versions have a different path available
|
||||
vc_amd64_compiler_detection_index = (
|
||||
|
|
@ -705,23 +552,6 @@ def glob_recursive(pattern, node="."):
|
|||
return results
|
||||
|
||||
|
||||
def add_to_vs_project(env, sources):
|
||||
for x in sources:
|
||||
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]
|
||||
basename = basename.replace("\\\\", "/")
|
||||
if os.path.isfile(basename + ".h"):
|
||||
env.vs_incs += [basename + ".h"]
|
||||
elif os.path.isfile(basename + ".hpp"):
|
||||
env.vs_incs += [basename + ".hpp"]
|
||||
if os.path.isfile(basename + ".c"):
|
||||
env.vs_srcs += [basename + ".c"]
|
||||
elif os.path.isfile(basename + ".cpp"):
|
||||
env.vs_srcs += [basename + ".cpp"]
|
||||
|
||||
|
||||
def precious_program(env, program, sources, **args):
|
||||
program = env.ProgramOriginal(program, sources, **args)
|
||||
env.Precious(program)
|
||||
|
|
@ -782,15 +612,19 @@ def detect_darwin_sdk_path(platform, env):
|
|||
raise
|
||||
|
||||
|
||||
def is_vanilla_clang(env):
|
||||
def is_apple_clang(env):
|
||||
import shlex
|
||||
|
||||
if env["platform"] not in ["macos", "ios"]:
|
||||
return False
|
||||
if not using_clang(env):
|
||||
return False
|
||||
try:
|
||||
version = subprocess.check_output([env.subst(env["CXX"]), "--version"]).strip().decode("utf-8")
|
||||
version = subprocess.check_output(shlex.split(env.subst(env["CXX"])) + ["--version"]).strip().decode("utf-8")
|
||||
except (subprocess.CalledProcessError, OSError):
|
||||
print_warning("Couldn't parse CXX environment variable to infer compiler version.")
|
||||
return False
|
||||
return not version.startswith("Apple")
|
||||
return version.startswith("Apple")
|
||||
|
||||
|
||||
def get_compiler_version(env):
|
||||
|
|
@ -801,13 +635,15 @@ def get_compiler_version(env):
|
|||
- metadata1, metadata2: Extra information
|
||||
- date: Date of the build
|
||||
"""
|
||||
import shlex
|
||||
|
||||
ret = {
|
||||
"major": -1,
|
||||
"minor": -1,
|
||||
"patch": -1,
|
||||
"metadata1": None,
|
||||
"metadata2": None,
|
||||
"date": None,
|
||||
"metadata1": "",
|
||||
"metadata2": "",
|
||||
"date": "",
|
||||
"apple_major": -1,
|
||||
"apple_minor": -1,
|
||||
"apple_patch1": -1,
|
||||
|
|
@ -815,21 +651,48 @@ def get_compiler_version(env):
|
|||
"apple_patch3": -1,
|
||||
}
|
||||
|
||||
if not env.msvc:
|
||||
# Not using -dumpversion as some GCC distros only return major, and
|
||||
# Clang used to return hardcoded 4.2.1: # https://reviews.llvm.org/D56803
|
||||
if env.msvc and not using_clang(env):
|
||||
try:
|
||||
version = (
|
||||
subprocess.check_output([env.subst(env["CXX"]), "--version"], shell=(os.name == "nt"))
|
||||
.strip()
|
||||
.decode("utf-8")
|
||||
)
|
||||
# FIXME: `-latest` works for most cases, but there are edge-cases where this would
|
||||
# benefit from a more nuanced search.
|
||||
# https://github.com/godotengine/godot/pull/91069#issuecomment-2358956731
|
||||
# https://github.com/godotengine/godot/pull/91069#issuecomment-2380836341
|
||||
args = [
|
||||
env["VSWHERE"],
|
||||
"-latest",
|
||||
"-prerelease",
|
||||
"-products",
|
||||
"*",
|
||||
"-requires",
|
||||
"Microsoft.Component.MSBuild",
|
||||
"-utf8",
|
||||
]
|
||||
version = subprocess.check_output(args, encoding="utf-8").strip()
|
||||
for line in version.splitlines():
|
||||
split = line.split(":", 1)
|
||||
if split[0] == "catalog_productDisplayVersion":
|
||||
sem_ver = split[1].split(".")
|
||||
ret["major"] = int(sem_ver[0])
|
||||
ret["minor"] = int(sem_ver[1])
|
||||
ret["patch"] = int(sem_ver[2].split()[0])
|
||||
# Could potentially add section for determining preview version, but
|
||||
# that can wait until metadata is actually used for something.
|
||||
if split[0] == "catalog_buildVersion":
|
||||
ret["metadata1"] = split[1]
|
||||
except (subprocess.CalledProcessError, OSError):
|
||||
print_warning("Couldn't parse CXX environment variable to infer compiler version.")
|
||||
return ret
|
||||
else:
|
||||
# TODO: Implement for MSVC
|
||||
print_warning("Couldn't find vswhere to determine compiler version.")
|
||||
return ret
|
||||
|
||||
# Not using -dumpversion as some GCC distros only return major, and
|
||||
# Clang used to return hardcoded 4.2.1: # https://reviews.llvm.org/D56803
|
||||
try:
|
||||
version = subprocess.check_output(
|
||||
shlex.split(env.subst(env["CXX"])) + ["--version"], shell=(os.name == "nt"), encoding="utf-8"
|
||||
).strip()
|
||||
except (subprocess.CalledProcessError, OSError):
|
||||
print_warning("Couldn't parse CXX environment variable to infer compiler version.")
|
||||
return ret
|
||||
|
||||
match = re.search(
|
||||
r"(?:(?<=version )|(?<=\) )|(?<=^))"
|
||||
r"(?P<major>\d+)"
|
||||
|
|
@ -887,135 +750,167 @@ def using_emcc(env):
|
|||
|
||||
|
||||
def show_progress(env):
|
||||
# Ninja has its own progress/tracking tool that clashes with ours.
|
||||
if env["ninja"]:
|
||||
# Has its own progress/tracking tool that clashes with ours
|
||||
return
|
||||
|
||||
import sys
|
||||
NODE_COUNT_FILENAME = f"{base_folder_path}.scons_node_count"
|
||||
|
||||
from SCons.Script import AlwaysBuild, Command, Progress
|
||||
class ShowProgress:
|
||||
def __init__(self):
|
||||
self.count = 0
|
||||
self.max = 0
|
||||
try:
|
||||
with open(NODE_COUNT_FILENAME, "r", encoding="utf-8") as f:
|
||||
self.max = int(f.readline())
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
screen = sys.stdout
|
||||
# Progress reporting is not available in non-TTY environments since it
|
||||
# messes with the output (for example, when writing to a file)
|
||||
show_progress = env["progress"] and sys.stdout.isatty()
|
||||
node_count = 0
|
||||
node_count_max = 0
|
||||
node_count_interval = 1
|
||||
node_count_fname = str(env.Dir("#")) + "/.scons_node_count"
|
||||
|
||||
import math
|
||||
import time
|
||||
|
||||
class cache_progress:
|
||||
# The default is 1 GB cache and 12 hours half life
|
||||
def __init__(self, path=None, limit=1073741824, half_life=43200):
|
||||
self.path = path
|
||||
self.limit = limit
|
||||
self.exponent_scale = math.log(2) / half_life
|
||||
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))
|
||||
)
|
||||
)
|
||||
self.delete(self.file_list())
|
||||
# Progress reporting is not available in non-TTY environments since it
|
||||
# messes with the output (for example, when writing to a file).
|
||||
self.display = cast(bool, self.max and env["progress"] and sys.stdout.isatty())
|
||||
if self.display and not self.max:
|
||||
print_info("Performing initial build, progress percentage unavailable!")
|
||||
|
||||
def __call__(self, node, *args, **kw):
|
||||
nonlocal node_count, node_count_max, node_count_interval, node_count_fname, show_progress
|
||||
if show_progress:
|
||||
# Print the progress percentage
|
||||
node_count += node_count_interval
|
||||
if node_count_max > 0 and node_count <= node_count_max:
|
||||
screen.write("\r[%3d%%] " % (node_count * 100 / node_count_max))
|
||||
screen.flush()
|
||||
elif node_count_max > 0 and node_count > node_count_max:
|
||||
screen.write("\r[100%] ")
|
||||
screen.flush()
|
||||
else:
|
||||
screen.write("\r[Initial build] ")
|
||||
screen.flush()
|
||||
self.count += 1
|
||||
if self.display:
|
||||
percent = int(min(self.count * 100 / self.max, 100))
|
||||
sys.stdout.write(f"\r[{percent:3d}%] ")
|
||||
sys.stdout.flush()
|
||||
|
||||
def delete(self, files):
|
||||
if len(files) == 0:
|
||||
return
|
||||
if env["verbose"]:
|
||||
# Utter something
|
||||
screen.write("\rPurging %d %s from cache...\n" % (len(files), len(files) > 1 and "files" or "file"))
|
||||
[os.remove(f) for f in files]
|
||||
from SCons.Script import Progress
|
||||
from SCons.Script.Main import GetBuildFailures
|
||||
|
||||
def file_list(self):
|
||||
if self.path is None:
|
||||
# Nothing to do
|
||||
return []
|
||||
# Gather a list of (filename, (size, atime)) within the
|
||||
# cache directory
|
||||
file_stat = [(x, os.stat(x)[6:8]) for x in glob.glob(os.path.join(self.path, "*", "*"))]
|
||||
if file_stat == []:
|
||||
# Nothing to do
|
||||
return []
|
||||
# Weight the cache files by size (assumed to be roughly
|
||||
# proportional to the recompilation time) times an exponential
|
||||
# decay since the ctime, and return a list with the entries
|
||||
# (filename, size, weight).
|
||||
current_time = time.time()
|
||||
file_stat = [(x[0], x[1][0], (current_time - x[1][1])) for x in file_stat]
|
||||
# Sort by the most recently accessed files (most sensible to keep) first
|
||||
file_stat.sort(key=lambda x: x[2])
|
||||
# Search for the first entry where the storage limit is
|
||||
# reached
|
||||
sum, mark = 0, None
|
||||
for i, x in enumerate(file_stat):
|
||||
sum += x[1]
|
||||
if sum > self.limit:
|
||||
mark = i
|
||||
break
|
||||
if mark is None:
|
||||
return []
|
||||
else:
|
||||
return [x[0] for x in file_stat[mark:]]
|
||||
progressor = ShowProgress()
|
||||
Progress(progressor)
|
||||
|
||||
def convert_size(self, size_bytes):
|
||||
if size_bytes == 0:
|
||||
return "0 bytes"
|
||||
size_name = ("bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
|
||||
i = int(math.floor(math.log(size_bytes, 1024)))
|
||||
p = math.pow(1024, i)
|
||||
s = round(size_bytes / p, 2)
|
||||
return "%s %s" % (int(s) if i == 0 else s, size_name[i])
|
||||
|
||||
def get_size(self, start_path="."):
|
||||
total_size = 0
|
||||
for dirpath, dirnames, filenames in os.walk(start_path):
|
||||
for f in filenames:
|
||||
fp = os.path.join(dirpath, f)
|
||||
total_size += os.path.getsize(fp)
|
||||
return total_size
|
||||
|
||||
def progress_finish(target, source, env):
|
||||
nonlocal node_count, progressor
|
||||
def progress_finish():
|
||||
if GetBuildFailures() or not progressor.count:
|
||||
return
|
||||
try:
|
||||
with open(node_count_fname, "w", encoding="utf-8", newline="\n") as f:
|
||||
f.write("%d\n" % node_count)
|
||||
progressor.delete(progressor.file_list())
|
||||
except Exception:
|
||||
with open(NODE_COUNT_FILENAME, "w", encoding="utf-8", newline="\n") as f:
|
||||
f.write(f"{progressor.count}\n")
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
try:
|
||||
with open(node_count_fname, "r", encoding="utf-8") as f:
|
||||
node_count_max = int(f.readline())
|
||||
except Exception:
|
||||
pass
|
||||
atexit.register(progress_finish)
|
||||
|
||||
cache_directory = os.environ.get("SCONS_CACHE")
|
||||
# Simple cache pruning, attached to SCons' progress callback. Trim the
|
||||
# cache directory to a size not larger than cache_limit.
|
||||
cache_limit = float(os.getenv("SCONS_CACHE_LIMIT", 1024)) * 1024 * 1024
|
||||
progressor = cache_progress(cache_directory, cache_limit)
|
||||
Progress(progressor, interval=node_count_interval)
|
||||
|
||||
progress_finish_command = Command("progress_finish", [], progress_finish)
|
||||
AlwaysBuild(progress_finish_command)
|
||||
def convert_size(size_bytes: int) -> str:
|
||||
if size_bytes == 0:
|
||||
return "0 bytes"
|
||||
SIZE_NAMES = ["bytes", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"]
|
||||
index = math.floor(math.log(size_bytes, 1024))
|
||||
power = math.pow(1024, index)
|
||||
size = round(size_bytes / power, 2)
|
||||
return f"{size} {SIZE_NAMES[index]}"
|
||||
|
||||
|
||||
def get_size(start_path: str = ".") -> int:
|
||||
total_size = 0
|
||||
for dirpath, _, filenames in os.walk(start_path):
|
||||
for file in filenames:
|
||||
path = os.path.join(dirpath, file)
|
||||
total_size += os.path.getsize(path)
|
||||
return total_size
|
||||
|
||||
|
||||
def clean_cache(cache_path: str, cache_limit: int, verbose: bool) -> None:
|
||||
if not cache_limit:
|
||||
return
|
||||
|
||||
files = glob.glob(os.path.join(cache_path, "*", "*"))
|
||||
if not files:
|
||||
return
|
||||
|
||||
# Store files in list of (filename, size, atime).
|
||||
stats = []
|
||||
for file in files:
|
||||
try:
|
||||
stats.append((file, *os.stat(file)[6:8]))
|
||||
except OSError:
|
||||
print_error(f'Failed to access cache file "{file}"; skipping.')
|
||||
|
||||
# Sort by most recent access (most sensible to keep) first. Search for the first entry where
|
||||
# the cache limit is reached.
|
||||
stats.sort(key=lambda x: x[2], reverse=True)
|
||||
sum = 0
|
||||
for index, stat in enumerate(stats):
|
||||
sum += stat[1]
|
||||
if sum > cache_limit:
|
||||
purge = [x[0] for x in stats[index:]]
|
||||
count = len(purge)
|
||||
for file in purge:
|
||||
try:
|
||||
os.remove(file)
|
||||
except OSError:
|
||||
print_error(f'Failed to remove cache file "{file}"; skipping.')
|
||||
count -= 1
|
||||
if verbose and count:
|
||||
print_info(f"Purged {count} file{'s' if count else ''} from cache.")
|
||||
break
|
||||
|
||||
|
||||
def prepare_cache(env) -> None:
|
||||
if env.GetOption("clean"):
|
||||
return
|
||||
|
||||
cache_path = ""
|
||||
if env["cache_path"]:
|
||||
cache_path = cast(str, env["cache_path"])
|
||||
elif os.environ.get("SCONS_CACHE"):
|
||||
print_warning("Environment variable `SCONS_CACHE` is deprecated; use `cache_path` argument instead.")
|
||||
cache_path = cast(str, os.environ.get("SCONS_CACHE"))
|
||||
|
||||
if not cache_path:
|
||||
return
|
||||
|
||||
env.CacheDir(cache_path)
|
||||
print(f'SCons cache enabled... (path: "{cache_path}")')
|
||||
|
||||
if env["cache_limit"]:
|
||||
cache_limit = float(env["cache_limit"])
|
||||
elif os.environ.get("SCONS_CACHE_LIMIT"):
|
||||
print_warning("Environment variable `SCONS_CACHE_LIMIT` is deprecated; use `cache_limit` argument instead.")
|
||||
cache_limit = float(os.getenv("SCONS_CACHE_LIMIT", "0")) / 1024 # Old method used MiB, convert to GiB
|
||||
|
||||
# Convert GiB to bytes; treat negative numbers as 0 (unlimited).
|
||||
cache_limit = max(0, int(cache_limit * 1024 * 1024 * 1024))
|
||||
if env["verbose"]:
|
||||
print(
|
||||
"Current cache limit is {} (used: {})".format(
|
||||
convert_size(cache_limit) if cache_limit else "∞",
|
||||
convert_size(get_size(cache_path)),
|
||||
)
|
||||
)
|
||||
|
||||
atexit.register(clean_cache, cache_path, cache_limit, env["verbose"])
|
||||
|
||||
|
||||
def prepare_purge(env):
|
||||
from SCons.Script.Main import GetBuildFailures
|
||||
|
||||
def purge_flaky_files():
|
||||
paths_to_keep = [env["ninja_file"]]
|
||||
for build_failure in GetBuildFailures():
|
||||
path = build_failure.node.path
|
||||
if os.path.isfile(path) and path not in paths_to_keep:
|
||||
os.remove(path)
|
||||
|
||||
atexit.register(purge_flaky_files)
|
||||
|
||||
|
||||
def prepare_timer():
|
||||
import time
|
||||
|
||||
def print_elapsed_time(time_at_start: float):
|
||||
time_elapsed = time.time() - time_at_start
|
||||
time_formatted = time.strftime("%H:%M:%S", time.gmtime(time_elapsed))
|
||||
time_centiseconds = round((time_elapsed % 1) * 100)
|
||||
print_info(f"Time elapsed: {time_formatted}.{time_centiseconds}")
|
||||
|
||||
atexit.register(print_elapsed_time, time.time())
|
||||
|
||||
|
||||
def dump(env):
|
||||
|
|
@ -1086,6 +981,30 @@ def generate_vs_project(env, original_args, project_name="godot"):
|
|||
return v[0] if len(v) == 1 else f"{v[0]}={v[1]}"
|
||||
return v
|
||||
|
||||
def get_dependencies(file, env, exts, headers, sources, others):
|
||||
for child in file.children():
|
||||
if isinstance(child, str):
|
||||
child = env.File(x)
|
||||
fname = ""
|
||||
try:
|
||||
fname = child.path
|
||||
except AttributeError:
|
||||
# It's not a file.
|
||||
pass
|
||||
|
||||
if fname:
|
||||
parts = os.path.splitext(fname)
|
||||
if len(parts) > 1:
|
||||
ext = parts[1].lower()
|
||||
if ext in exts["sources"]:
|
||||
sources += [fname]
|
||||
elif ext in exts["headers"]:
|
||||
headers += [fname]
|
||||
elif ext in exts["others"]:
|
||||
others += [fname]
|
||||
|
||||
get_dependencies(child, env, exts, headers, sources, others)
|
||||
|
||||
filtered_args = original_args.copy()
|
||||
|
||||
# Ignore the "vsproj" option to not regenerate the VS project on every build
|
||||
|
|
@ -1147,35 +1066,35 @@ def generate_vs_project(env, original_args, project_name="godot"):
|
|||
sys.path.remove(tmppath)
|
||||
sys.modules.pop("msvs")
|
||||
|
||||
extensions = {}
|
||||
extensions["headers"] = [".h", ".hh", ".hpp", ".hxx", ".inc"]
|
||||
extensions["sources"] = [".c", ".cc", ".cpp", ".cxx", ".m", ".mm", ".java"]
|
||||
extensions["others"] = [".natvis", ".glsl", ".rc"]
|
||||
|
||||
headers = []
|
||||
headers_dirs = []
|
||||
for file in glob_recursive_2("*.h", headers_dirs):
|
||||
headers.append(str(file).replace("/", "\\"))
|
||||
for file in glob_recursive_2("*.hpp", headers_dirs):
|
||||
headers.append(str(file).replace("/", "\\"))
|
||||
for ext in extensions["headers"]:
|
||||
for file in glob_recursive_2("*" + ext, headers_dirs):
|
||||
headers.append(str(file).replace("/", "\\"))
|
||||
|
||||
sources = []
|
||||
sources_dirs = []
|
||||
for file in glob_recursive_2("*.cpp", sources_dirs):
|
||||
sources.append(str(file).replace("/", "\\"))
|
||||
for file in glob_recursive_2("*.c", sources_dirs):
|
||||
sources.append(str(file).replace("/", "\\"))
|
||||
for ext in extensions["sources"]:
|
||||
for file in glob_recursive_2("*" + ext, sources_dirs):
|
||||
sources.append(str(file).replace("/", "\\"))
|
||||
|
||||
others = []
|
||||
others_dirs = []
|
||||
for file in glob_recursive_2("*.natvis", others_dirs):
|
||||
others.append(str(file).replace("/", "\\"))
|
||||
for file in glob_recursive_2("*.glsl", others_dirs):
|
||||
others.append(str(file).replace("/", "\\"))
|
||||
for ext in extensions["others"]:
|
||||
for file in glob_recursive_2("*" + ext, others_dirs):
|
||||
others.append(str(file).replace("/", "\\"))
|
||||
|
||||
skip_filters = False
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
md5 = hashlib.md5(
|
||||
json.dumps(headers + headers_dirs + sources + sources_dirs + others + others_dirs, sort_keys=True).encode(
|
||||
"utf-8"
|
||||
)
|
||||
json.dumps(sorted(headers + headers_dirs + sources + sources_dirs + others + others_dirs)).encode("utf-8")
|
||||
).hexdigest()
|
||||
|
||||
if os.path.exists(f"{project_name}.vcxproj.filters"):
|
||||
|
|
@ -1232,58 +1151,13 @@ def generate_vs_project(env, original_args, project_name="godot"):
|
|||
with open(f"{project_name}.vcxproj.filters", "w", encoding="utf-8", newline="\r\n") as f:
|
||||
f.write(filters_template)
|
||||
|
||||
envsources = []
|
||||
|
||||
envsources += env.core_sources
|
||||
envsources += env.drivers_sources
|
||||
envsources += env.main_sources
|
||||
envsources += env.modules_sources
|
||||
envsources += env.scene_sources
|
||||
envsources += env.servers_sources
|
||||
if env.editor_build:
|
||||
envsources += env.editor_sources
|
||||
envsources += env.platform_sources
|
||||
|
||||
headers_active = []
|
||||
sources_active = []
|
||||
others_active = []
|
||||
for x in envsources:
|
||||
fname = ""
|
||||
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 Exception:
|
||||
fname = x.path
|
||||
pass
|
||||
|
||||
if fname:
|
||||
fname = fname.replace("\\\\", "/")
|
||||
parts = os.path.splitext(fname)
|
||||
basename = parts[0]
|
||||
ext = parts[1]
|
||||
idx = fname.find(env["OBJSUFFIX"])
|
||||
if ext in [".h", ".hpp"]:
|
||||
headers_active += [fname]
|
||||
elif ext in [".c", ".cpp"]:
|
||||
sources_active += [fname]
|
||||
elif idx > 0:
|
||||
basename = fname[:idx]
|
||||
if os.path.isfile(basename + ".h"):
|
||||
headers_active += [basename + ".h"]
|
||||
elif os.path.isfile(basename + ".hpp"):
|
||||
headers_active += [basename + ".hpp"]
|
||||
elif basename.endswith(".gen") and os.path.isfile(basename[:-4] + ".h"):
|
||||
headers_active += [basename[:-4] + ".h"]
|
||||
if os.path.isfile(basename + ".c"):
|
||||
sources_active += [basename + ".c"]
|
||||
elif os.path.isfile(basename + ".cpp"):
|
||||
sources_active += [basename + ".cpp"]
|
||||
else:
|
||||
fname = os.path.relpath(os.path.abspath(fname), env.Dir("").abspath)
|
||||
others_active += [fname]
|
||||
get_dependencies(
|
||||
env.File(f"#bin/godot{env['PROGSUFFIX']}"), env, extensions, headers_active, sources_active, others_active
|
||||
)
|
||||
|
||||
all_items = []
|
||||
properties = []
|
||||
|
|
@ -1327,7 +1201,7 @@ def generate_vs_project(env, original_args, project_name="godot"):
|
|||
vsconf = ""
|
||||
for a in vs_configuration["arches"]:
|
||||
if arch == a["architecture"]:
|
||||
vsconf = f'{target}|{a["platform"]}'
|
||||
vsconf = f"{target}|{a['platform']}"
|
||||
break
|
||||
|
||||
condition = "'$(GodotConfiguration)|$(GodotPlatform)'=='" + vsconf + "'"
|
||||
|
|
@ -1343,7 +1217,7 @@ def generate_vs_project(env, original_args, project_name="godot"):
|
|||
properties.append(
|
||||
"<ActiveProjectItemList_%s>;%s;</ActiveProjectItemList_%s>" % (x, ";".join(itemlist[x]), x)
|
||||
)
|
||||
output = f'bin\\godot{env["PROGSUFFIX"]}'
|
||||
output = f"bin\\godot{env['PROGSUFFIX']}"
|
||||
|
||||
with open("misc/msvs/props.template", "r", encoding="utf-8") as file:
|
||||
props_template = file.read()
|
||||
|
|
@ -1569,7 +1443,7 @@ def generate_copyright_header(filename: str) -> str:
|
|||
"""
|
||||
filename = filename.split("/")[-1].ljust(MARGIN)
|
||||
if len(filename) > MARGIN:
|
||||
print(f'WARNING: Filename "{filename}" too large for copyright header.')
|
||||
print_warning(f'Filename "{filename}" too large for copyright header.')
|
||||
return TEMPLATE % filename
|
||||
|
||||
|
||||
|
|
@ -1579,7 +1453,7 @@ def generated_wrapper(
|
|||
guard: Optional[bool] = None,
|
||||
prefix: str = "",
|
||||
suffix: str = "",
|
||||
) -> Generator[TextIOWrapper, None, None]:
|
||||
) -> Generator[TextIOBase, None, None]:
|
||||
"""
|
||||
Wrapper class to automatically handle copyright headers and header guards
|
||||
for generated scripts. Meant to be invoked via `with` statement similar to
|
||||
|
|
@ -1601,8 +1475,7 @@ def generated_wrapper(
|
|||
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]}"
|
||||
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"):
|
||||
|
|
@ -1641,3 +1514,43 @@ def generated_wrapper(
|
|||
file.write(f"\n\n#endif // {header_guard}")
|
||||
|
||||
file.write("\n")
|
||||
|
||||
|
||||
def to_raw_cstring(value: Union[str, List[str]]) -> str:
|
||||
MAX_LITERAL = 16 * 1024
|
||||
|
||||
if isinstance(value, list):
|
||||
value = "\n".join(value) + "\n"
|
||||
|
||||
split: List[bytes] = []
|
||||
offset = 0
|
||||
encoded = value.encode()
|
||||
|
||||
while offset <= len(encoded):
|
||||
segment = encoded[offset : offset + MAX_LITERAL]
|
||||
offset += MAX_LITERAL
|
||||
if len(segment) == MAX_LITERAL:
|
||||
# Try to segment raw strings at double newlines to keep readable.
|
||||
pretty_break = segment.rfind(b"\n\n")
|
||||
if pretty_break != -1:
|
||||
segment = segment[: pretty_break + 1]
|
||||
offset -= MAX_LITERAL - pretty_break - 1
|
||||
# If none found, ensure we end with valid utf8.
|
||||
# https://github.com/halloleo/unicut/blob/master/truncate.py
|
||||
elif segment[-1] & 0b10000000:
|
||||
last_11xxxxxx_index = [i for i in range(-1, -5, -1) if segment[i] & 0b11000000 == 0b11000000][0]
|
||||
last_11xxxxxx = segment[last_11xxxxxx_index]
|
||||
if not last_11xxxxxx & 0b00100000:
|
||||
last_char_length = 2
|
||||
elif not last_11xxxxxx & 0b0010000:
|
||||
last_char_length = 3
|
||||
elif not last_11xxxxxx & 0b0001000:
|
||||
last_char_length = 4
|
||||
|
||||
if last_char_length > -last_11xxxxxx_index:
|
||||
segment = segment[:last_11xxxxxx_index]
|
||||
offset += last_11xxxxxx_index
|
||||
|
||||
split += [segment]
|
||||
|
||||
return " ".join(f'R"<!>({x.decode()})<!>"' for x in split)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue