diff --git a/tools/android.py b/tools/android.py index 0c253541..8454d479 100644 --- a/tools/android.py +++ b/tools/android.py @@ -1,6 +1,7 @@ import os import sys import my_spawn +import common_compiler_flags from SCons.Script import ARGUMENTS @@ -118,3 +119,5 @@ def generate(env): env.Append(LINKFLAGS=["--target=" + arch_info["target"] + env["android_api_level"], "-march=" + arch_info["march"]]) env.Append(CPPDEFINES=["ANDROID_ENABLED", "UNIX_ENABLED"]) + + common_compiler_flags.generate(env) diff --git a/tools/common_compiler_flags.py b/tools/common_compiler_flags.py new file mode 100644 index 00000000..838dbcf0 --- /dev/null +++ b/tools/common_compiler_flags.py @@ -0,0 +1,94 @@ +import os +import subprocess +import sys + + +def using_clang(env): + return "clang" in os.path.basename(env["CC"]) + + +def is_vanilla_clang(env): + if not using_clang(env): + return False + 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.") + return False + return not version.startswith("Apple") + + +def exists(env): + return True + + +def generate(env): + # Require C++17 + if env.get("is_msvc", False): + env.Append(CXXFLAGS=["/std:c++17"]) + else: + env.Append(CXXFLAGS=["-std=c++17"]) + + # Disable exception handling. Godot doesn't use exceptions anywhere, and this + # saves around 20% of binary size and very significant build time. + if env["disable_exceptions"]: + if env.get("is_msvc", False): + env.Append(CPPDEFINES=[("_HAS_EXCEPTIONS", 0)]) + else: + env.Append(CXXFLAGS=["-fno-exceptions"]) + elif env.get("is_msvc", False): + env.Append(CXXFLAGS=["/EHsc"]) + + if not env.get("is_msvc", False): + if env["symbols_visibility"] == "visible": + env.Append(CCFLAGS=["-fvisibility=default"]) + env.Append(LINKFLAGS=["-fvisibility=default"]) + elif env["symbols_visibility"] == "hidden": + env.Append(CCFLAGS=["-fvisibility=hidden"]) + env.Append(LINKFLAGS=["-fvisibility=hidden"]) + + # Set optimize and debug_symbols flags. + # "custom" means do nothing and let users set their own optimization flags. + if env.get("is_msvc", False): + if env["debug_symbols"]: + env.Append(CCFLAGS=["/Zi", "/FS"]) + env.Append(LINKFLAGS=["/DEBUG:FULL"]) + + if env["optimize"] == "speed": + env.Append(CCFLAGS=["/O2"]) + env.Append(LINKFLAGS=["/OPT:REF"]) + elif env["optimize"] == "speed_trace": + env.Append(CCFLAGS=["/O2"]) + env.Append(LINKFLAGS=["/OPT:REF", "/OPT:NOICF"]) + elif env["optimize"] == "size": + env.Append(CCFLAGS=["/O1"]) + env.Append(LINKFLAGS=["/OPT:REF"]) + elif env["optimize"] == "debug" or env["optimize"] == "none": + env.Append(CCFLAGS=["/Od"]) + else: + if env["debug_symbols"]: + # Adding dwarf-4 explicitly makes stacktraces work with clang builds, + # otherwise addr2line doesn't understand them. + env.Append(CCFLAGS=["-gdwarf-4"]) + if env.dev_build: + env.Append(CCFLAGS=["-g3"]) + else: + env.Append(CCFLAGS=["-g2"]) + else: + if using_clang(env) and not is_vanilla_clang(env): + # Apple Clang, its linker doesn't like -s. + env.Append(LINKFLAGS=["-Wl,-S", "-Wl,-x", "-Wl,-dead_strip"]) + else: + env.Append(LINKFLAGS=["-s"]) + + if env["optimize"] == "speed": + env.Append(CCFLAGS=["-O3"]) + # `-O2` is friendlier to debuggers than `-O3`, leading to better crash backtraces. + elif env["optimize"] == "speed_trace": + env.Append(CCFLAGS=["-O2"]) + elif env["optimize"] == "size": + env.Append(CCFLAGS=["-Os"]) + elif env["optimize"] == "debug": + env.Append(CCFLAGS=["-Og"]) + elif env["optimize"] == "none": + env.Append(CCFLAGS=["-O0"]) diff --git a/tools/godotcpp.py b/tools/godotcpp.py index b5bf37c0..5ba2742b 100644 --- a/tools/godotcpp.py +++ b/tools/godotcpp.py @@ -1,9 +1,12 @@ import os, sys, platform from SCons.Variables import EnumVariable, PathVariable, BoolVariable +from SCons.Variables.BoolVariable import _text2bool from SCons.Tool import Tool from SCons.Builder import Builder from SCons.Errors import UserError +from SCons.Script import ARGUMENTS + from binding_generator import scons_generate_bindings, scons_emit_files @@ -14,6 +17,17 @@ def add_sources(sources, dir, extension): sources.append(dir + "/" + f) +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. + """ + cmdline_val = ARGUMENTS.get(option) + if cmdline_val is not None: + return _text2bool(cmdline_val) + else: + return default + + def normalize_path(val, env): return val if os.path.isabs(val) else os.path.join(env.Dir("#").abspath, val) @@ -230,16 +244,23 @@ def options(opts, env): ) ) + opts.Add( + EnumVariable( + "optimize", + "The desired optimization flags", + "speed_trace", + ("none", "custom", "debug", "speed", "speed_trace", "size"), + ) + ) + opts.Add(BoolVariable("debug_symbols", "Build with debugging symbols", True)) + opts.Add(BoolVariable("dev_build", "Developer build with dev-only debugging code (DEV_ENABLED)", False)) + # Add platform options (custom tools can override platforms) for pl in sorted(set(platforms + custom_platforms)): tool = Tool(pl, toolpath=get_platform_tools_paths(env)) if hasattr(tool, "options"): tool.options(opts) - # Targets flags tool (optimizations, debug symbols) - target_tool = Tool("targets", toolpath=["tools"]) - target_tool.options(opts) - def generate(env): # Default num_jobs to local cpu count if not user specified. @@ -286,10 +307,21 @@ def generate(env): print("Building for architecture " + env["arch"] + " on platform " + env["platform"]) - if env.get("use_hot_reload") is None: - env["use_hot_reload"] = env["target"] != "template_release" - if env["use_hot_reload"]: - env.Append(CPPDEFINES=["HOT_RELOAD_ENABLED"]) + # These defaults may be needed by platform tools + env.use_hot_reload = env.get("use_hot_reload", env["target"] != "template_release") + env.editor_build = env["target"] == "editor" + env.dev_build = env["dev_build"] + env.debug_features = env["target"] in ["editor", "template_debug"] + + if env.dev_build: + opt_level = "none" + elif env.debug_features: + opt_level = "speed_trace" + else: # Release + opt_level = "speed" + + env["optimize"] = ARGUMENTS.get("optimize", opt_level) + env["debug_symbols"] = get_cmdline_bool("debug_symbols", env.dev_build) tool = Tool(env["platform"], toolpath=get_platform_tools_paths(env)) @@ -297,32 +329,34 @@ def generate(env): raise ValueError("Required toolchain not found for platform " + env["platform"]) tool.generate(env) - target_tool = Tool("targets", toolpath=["tools"]) - target_tool.generate(env) - # Disable exception handling. Godot doesn't use exceptions anywhere, and this - # saves around 20% of binary size and very significant build time. - if env["disable_exceptions"]: - if env.get("is_msvc", False): - env.Append(CPPDEFINES=[("_HAS_EXCEPTIONS", 0)]) - else: - env.Append(CXXFLAGS=["-fno-exceptions"]) - elif env.get("is_msvc", False): - env.Append(CXXFLAGS=["/EHsc"]) + if env.use_hot_reload: + env.Append(CPPDEFINES=["HOT_RELOAD_ENABLED"]) - if not env.get("is_msvc", False): - if env["symbols_visibility"] == "visible": - env.Append(CCFLAGS=["-fvisibility=default"]) - env.Append(LINKFLAGS=["-fvisibility=default"]) - elif env["symbols_visibility"] == "hidden": - env.Append(CCFLAGS=["-fvisibility=hidden"]) - env.Append(LINKFLAGS=["-fvisibility=hidden"]) + if env.editor_build: + env.Append(CPPDEFINES=["TOOLS_ENABLED"]) - # Require C++17 - if env.get("is_msvc", False): - env.Append(CXXFLAGS=["/std:c++17"]) + # Configuration of build targets: + # - Editor or template + # - Debug features (DEBUG_ENABLED code) + # - Dev only code (DEV_ENABLED code) + # - Optimization level + # - Debug symbols for crash traces / debuggers + # Keep this configuration in sync with SConstruct in upstream Godot. + if env.debug_features: + # DEBUG_ENABLED enables debugging *features* and debug-only code, which is intended + # to give *users* extra debugging information for their game development. + env.Append(CPPDEFINES=["DEBUG_ENABLED"]) + # In upstream Godot this is added in typedefs.h when DEBUG_ENABLED is set. + env.Append(CPPDEFINES=["DEBUG_METHODS_ENABLED"]) + + if env.dev_build: + # DEV_ENABLED enables *engine developer* code which should only be compiled for those + # working on the engine itself. + env.Append(CPPDEFINES=["DEV_ENABLED"]) else: - env.Append(CXXFLAGS=["-std=c++17"]) + # Disable assert() for production targets (only used in thirdparty code). + env.Append(CPPDEFINES=["NDEBUG"]) if env["precision"] == "double": env.Append(CPPDEFINES=["REAL_T_IS_DOUBLE"]) diff --git a/tools/ios.py b/tools/ios.py index 9d37214a..75718684 100644 --- a/tools/ios.py +++ b/tools/ios.py @@ -1,6 +1,7 @@ import os import sys import subprocess +import common_compiler_flags from SCons.Variables import * if sys.version_info < (3,): @@ -104,3 +105,5 @@ def generate(env): env.Append(LINKFLAGS=["-isysroot", env["IOS_SDK_PATH"], "-F" + env["IOS_SDK_PATH"]]) env.Append(CPPDEFINES=["IOS_ENABLED", "UNIX_ENABLED"]) + + common_compiler_flags.generate(env) diff --git a/tools/linux.py b/tools/linux.py index 823b66e1..1783e060 100644 --- a/tools/linux.py +++ b/tools/linux.py @@ -1,3 +1,4 @@ +import common_compiler_flags from SCons.Variables import * from SCons.Tool import clang, clangxx @@ -14,7 +15,7 @@ def generate(env): if env["use_llvm"]: clang.generate(env) clangxx.generate(env) - elif env["use_hot_reload"]: + elif env.use_hot_reload: # Required for extensions to truly unload. env.Append(CXXFLAGS=["-fno-gnu-unique"]) @@ -37,3 +38,5 @@ def generate(env): env.Append(LINKFLAGS=["-march=rv64gc"]) env.Append(CPPDEFINES=["LINUX_ENABLED", "UNIX_ENABLED"]) + + common_compiler_flags.generate(env) diff --git a/tools/macos.py b/tools/macos.py index 0c75e4a7..883d21ec 100644 --- a/tools/macos.py +++ b/tools/macos.py @@ -1,5 +1,6 @@ import os import sys +import common_compiler_flags def has_osxcross(): @@ -70,3 +71,5 @@ def generate(env): ) env.Append(CPPDEFINES=["MACOS_ENABLED", "UNIX_ENABLED"]) + + common_compiler_flags.generate(env) diff --git a/tools/targets.py b/tools/targets.py deleted file mode 100644 index 21611349..00000000 --- a/tools/targets.py +++ /dev/null @@ -1,144 +0,0 @@ -import os -import subprocess -import sys -from SCons.Script import ARGUMENTS -from SCons.Variables import * -from SCons.Variables.BoolVariable import _text2bool - - -# Helper methods - - -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. - """ - cmdline_val = ARGUMENTS.get(option) - if cmdline_val is not None: - return _text2bool(cmdline_val) - else: - return default - - -def using_clang(env): - return "clang" in os.path.basename(env["CC"]) - - -def is_vanilla_clang(env): - if not using_clang(env): - return False - 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.") - return False - return not version.startswith("Apple") - - -# Main tool definition - - -def options(opts): - opts.Add( - EnumVariable( - "optimize", - "The desired optimization flags", - "speed_trace", - ("none", "custom", "debug", "speed", "speed_trace", "size"), - ) - ) - opts.Add(BoolVariable("debug_symbols", "Build with debugging symbols", True)) - opts.Add(BoolVariable("dev_build", "Developer build with dev-only debugging code (DEV_ENABLED)", False)) - - -def exists(env): - return True - - -def generate(env): - # Configuration of build targets: - # - Editor or template - # - Debug features (DEBUG_ENABLED code) - # - Dev only code (DEV_ENABLED code) - # - Optimization level - # - Debug symbols for crash traces / debuggers - - # Keep this configuration in sync with SConstruct in upstream Godot. - - env.editor_build = env["target"] == "editor" - env.dev_build = env["dev_build"] - env.debug_features = env["target"] in ["editor", "template_debug"] - - if env.dev_build: - opt_level = "none" - elif env.debug_features: - opt_level = "speed_trace" - else: # Release - opt_level = "speed" - - env["optimize"] = ARGUMENTS.get("optimize", opt_level) - env["debug_symbols"] = get_cmdline_bool("debug_symbols", env.dev_build) - - if env.editor_build: - env.Append(CPPDEFINES=["TOOLS_ENABLED"]) - - if env.debug_features: - # DEBUG_ENABLED enables debugging *features* and debug-only code, which is intended - # to give *users* extra debugging information for their game development. - env.Append(CPPDEFINES=["DEBUG_ENABLED"]) - # In upstream Godot this is added in typedefs.h when DEBUG_ENABLED is set. - env.Append(CPPDEFINES=["DEBUG_METHODS_ENABLED"]) - - if env.dev_build: - # DEV_ENABLED enables *engine developer* code which should only be compiled for those - # working on the engine itself. - env.Append(CPPDEFINES=["DEV_ENABLED"]) - else: - # Disable assert() for production targets (only used in thirdparty code). - env.Append(CPPDEFINES=["NDEBUG"]) - - # Set optimize and debug_symbols flags. - # "custom" means do nothing and let users set their own optimization flags. - if env.get("is_msvc", False): - if env["debug_symbols"]: - env.Append(CCFLAGS=["/Zi", "/FS"]) - env.Append(LINKFLAGS=["/DEBUG:FULL"]) - - if env["optimize"] == "speed": - env.Append(CCFLAGS=["/O2"]) - env.Append(LINKFLAGS=["/OPT:REF"]) - elif env["optimize"] == "speed_trace": - env.Append(CCFLAGS=["/O2"]) - env.Append(LINKFLAGS=["/OPT:REF", "/OPT:NOICF"]) - elif env["optimize"] == "size": - env.Append(CCFLAGS=["/O1"]) - env.Append(LINKFLAGS=["/OPT:REF"]) - elif env["optimize"] == "debug" or env["optimize"] == "none": - env.Append(CCFLAGS=["/Od"]) - else: - if env["debug_symbols"]: - # Adding dwarf-4 explicitly makes stacktraces work with clang builds, - # otherwise addr2line doesn't understand them. - env.Append(CCFLAGS=["-gdwarf-4"]) - if env.dev_build: - env.Append(CCFLAGS=["-g3"]) - else: - env.Append(CCFLAGS=["-g2"]) - else: - if using_clang(env) and not is_vanilla_clang(env): - # Apple Clang, its linker doesn't like -s. - env.Append(LINKFLAGS=["-Wl,-S", "-Wl,-x", "-Wl,-dead_strip"]) - else: - env.Append(LINKFLAGS=["-s"]) - - if env["optimize"] == "speed": - env.Append(CCFLAGS=["-O3"]) - # `-O2` is friendlier to debuggers than `-O3`, leading to better crash backtraces. - elif env["optimize"] == "speed_trace": - env.Append(CCFLAGS=["-O2"]) - elif env["optimize"] == "size": - env.Append(CCFLAGS=["-Os"]) - elif env["optimize"] == "debug": - env.Append(CCFLAGS=["-Og"]) - elif env["optimize"] == "none": - env.Append(CCFLAGS=["-O0"]) diff --git a/tools/web.py b/tools/web.py index a4620c37..c7440bcd 100644 --- a/tools/web.py +++ b/tools/web.py @@ -1,4 +1,5 @@ import os +import common_compiler_flags from SCons.Util import WhereIs @@ -42,3 +43,5 @@ def generate(env): env.Append(LINKFLAGS=["-s", "SIDE_MODULE=1"]) env.Append(CPPDEFINES=["WEB_ENABLED", "UNIX_ENABLED"]) + + common_compiler_flags.generate(env) diff --git a/tools/windows.py b/tools/windows.py index d5a729c5..a263241a 100644 --- a/tools/windows.py +++ b/tools/windows.py @@ -1,7 +1,6 @@ import sys - import my_spawn - +import common_compiler_flags from SCons.Tool import msvc, mingw from SCons.Variables import * @@ -90,3 +89,5 @@ def generate(env): ) env.Append(CPPDEFINES=["WINDOWS_ENABLED"]) + + common_compiler_flags.generate(env)