From 4aa8ecd6be351130e090c99610067b642609bba4 Mon Sep 17 00:00:00 2001 From: lawnjelly Date: Wed, 18 Jun 2025 12:09:12 +0100 Subject: [PATCH] Scons - add compiler requirements --- SConstruct | 68 ++++++++++++++++++++++++++ methods.py | 139 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 207 insertions(+) diff --git a/SConstruct b/SConstruct index 48632cbaa8e..7578c162a5c 100644 --- a/SConstruct +++ b/SConstruct @@ -469,6 +469,74 @@ if selected_platform in platform_list: if env["lto"] != "none": print("Using LTO: " + env["lto"]) + # Enforce our minimal compiler version requirements + cc_version = methods.get_compiler_version_ex(env) + cc_version_major = cc_version["major"] + cc_version_minor = cc_version["minor"] + cc_version_metadata1 = cc_version["metadata1"] + + if cc_version_major == -1: + print_warning( + "Couldn't detect compiler version, skipping version checks. " + "Build may fail if the compiler doesn't support C++17 fully." + ) + elif methods.using_gcc(env): + if cc_version_major < 9: + print_error( + "Detected GCC version older than 9, which does not fully support " + "C++17, or has bugs when compiling Godot. Supported versions are 9 " + "and later. Use a newer GCC version, or Clang 6 or later by passing " + '"use_llvm=yes" to the SCons command line.' + ) + Exit(255) + elif cc_version_metadata1 == "win32": + print_error( + "Detected mingw version is not using posix threads. Only posix " + "version of mingw is supported. " + 'Use "update-alternatives --config x86_64-w64-mingw32-g++" ' + "to switch to posix threads." + ) + Exit(255) + elif methods.using_clang(env): + # Apple LLVM versions differ from upstream LLVM version \o/, compare + # in https://en.wikipedia.org/wiki/Xcode#Toolchain_versions + if methods.is_apple_clang(env): + if cc_version_major < 10: + print_error( + "Detected Apple Clang version older than 10, which does not fully " + "support C++17. Supported versions are Apple Clang 10 and later." + ) + Exit(255) + else: + if cc_version_major < 6: + print_error( + "Detected Clang version older than 6, which does not fully support " + "C++17. Supported versions are Clang 6 and later." + ) + Exit(255) + + elif env.msvc: + # Ensure latest minor builds of Visual Studio 2017/2019. + # https://github.com/godotengine/godot/pull/94995#issuecomment-2336464574 + if cc_version_major == 16 and cc_version_minor < 11: + print_error( + "Detected Visual Studio 2019 version older than 16.11, which has bugs " + "when compiling Godot. Use a newer VS2019 version, or VS2022." + ) + Exit(255) + if cc_version_major == 15 and cc_version_minor < 9: + print_error( + "Detected Visual Studio 2017 version older than 15.9, which has bugs " + "when compiling Godot. Use a newer VS2017 version, or VS2019/VS2022." + ) + Exit(255) + if cc_version_major < 15: + print_error( + "Detected Visual Studio 2015 or earlier, which is unsupported in Godot. " + "Supported versions are Visual Studio 2017 and later." + ) + Exit(255) + # Set our C and C++ standard requirements. # Prepending to make it possible to override # This needs to come after `configure`, otherwise we don't have env.msvc. diff --git a/methods.py b/methods.py index 37324101441..6b7739b4795 100644 --- a/methods.py +++ b/methods.py @@ -19,6 +19,9 @@ from os.path import normpath, basename # 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)) + +compiler_version_cache = None + # Listing all the folders we have converted # for SCU in scu_builders.py _scu_folders = set() @@ -1068,6 +1071,21 @@ def detect_darwin_sdk_path(platform, env): raise +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(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 version.startswith("Apple") + + def get_compiler_version(env): """ Returns an array of version numbers as ints: [major, minor, patch]. @@ -1090,6 +1108,127 @@ def get_compiler_version(env): return None +def get_compiler_version_ex(env): + """ + Returns a dictionary with various version information: + + - major, minor, patch: Version following semantic versioning system + - metadata1, metadata2: Extra information + - date: Date of the build + """ + + global compiler_version_cache + if compiler_version_cache is not None: + return compiler_version_cache + + import shlex + + ret = { + "major": -1, + "minor": -1, + "patch": -1, + "metadata1": "", + "metadata2": "", + "date": "", + "apple_major": -1, + "apple_minor": -1, + "apple_patch1": -1, + "apple_patch2": -1, + "apple_patch3": -1, + } + + if env.msvc and not using_clang(env): + try: + # 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 find vswhere to determine compiler version.") + return update_compiler_version_cache(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"]), posix=False) + ["--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 update_compiler_version_cache(ret) + + match = re.search( + r"(?:(?<=version )|(?<=\) )|(?<=^))" + r"(?P\d+)" + r"(?:\.(?P\d*))?" + r"(?:\.(?P\d*))?" + r"(?:-(?P[0-9a-zA-Z-]*))?" + r"(?:\+(?P[0-9a-zA-Z-]*))?" + r"(?: (?P[0-9]{8}|[0-9]{6})(?![0-9a-zA-Z]))?", + version, + ) + if match is not None: + for key, value in match.groupdict().items(): + if value is not None: + ret[key] = value + + match_apple = re.search( + r"(?:(?<=clang-)|(?<=\) )|(?<=^))" + r"(?P\d+)" + r"(?:\.(?P\d*))?" + r"(?:\.(?P\d*))?" + r"(?:\.(?P\d*))?" + r"(?:\.(?P\d*))?", + version, + ) + if match_apple is not None: + for key, value in match_apple.groupdict().items(): + if value is not None: + ret[key] = value + + # Transform semantic versioning to integers + for key in [ + "major", + "minor", + "patch", + "apple_major", + "apple_minor", + "apple_patch1", + "apple_patch2", + "apple_patch3", + ]: + ret[key] = int(ret[key] or -1) + return update_compiler_version_cache(ret) + + +def update_compiler_version_cache(value): + global compiler_version_cache + compiler_version_cache = value + return value + + def is_vanilla_clang(env): if not using_clang(env): return False