Last active
February 23, 2023 22:46
-
-
Save Ed94/aaf44828605878ca14f1eebab4af67ee to your computer and use it in GitHub Desktop.
godot-cpp multi-module fast incremental compile sconstruct example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/rusr/bin/env python | |
| # Py Platform | |
| import os | |
| import platform | |
| import shutil | |
| import subprocess | |
| import sys | |
| import time | |
| CacheDir('./build_cache') | |
| DefaultEnvironment(tools=[]) | |
| # The following is an SConstruct designed to build all the compiled modules for Sectored Language project. | |
| # It assumes that godot_cpp has already been built. | |
| #region Helper Functions | |
| # add_sources( sources, SUB-DIRECTORY-NAME, "c or cpp" ) | |
| def add_sources(sources, dir, extension): | |
| for file in os.listdir(dir): | |
| if file.endswith("." + extension): | |
| sources.append(dir + "/" + file) | |
| 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"] = "" | |
| # 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"] | |
| ) | |
| 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]) | |
| start_time = round( time.time() * 1000 ) | |
| def time_mili(): | |
| return round( time.time() * 1000 - start_time ) | |
| def plog( msg ): | |
| print( '{0:10} ms {1}'.format( time_mili() , msg ) ) | |
| #endregion Helper Functions | |
| #region Module Paths | |
| # Module name assuemd to be directory name, change if undesired. | |
| # module_name = os.path.basename(os.getcwd()) | |
| # plog( "Building module: " + module_name ) | |
| module_core = "Core" | |
| module_engine = "Engine" | |
| module_pmdb = "PMDB" | |
| module_project = "Project" | |
| module_simpleregex = "SimpleRegex" | |
| module_textpipeline = "TextPipeline" | |
| module_ux = "UX" | |
| # This is the root directory of your project | |
| # This template assumes the following directories | |
| # Godot project | |
| # Platform (godot, godot_cpp, and other platform/third-party specific source/binaries) | |
| # Source (Native gdextension source modules for the godot project) | |
| # Change these based on your project | |
| path_root = os.path.abspath("../") | |
| path_gdproject = path_root + "/App" | |
| path_platform = path_root + "/Platform" | |
| path_gdengine = path_platform + "/gd" | |
| path_godotcpp = path_platform + "/gd-cpp" | |
| path_source = path_root + "/Source" | |
| path_module_core = path_source + "/" + module_core | |
| path_module_engine = path_source + "/" + module_engine | |
| path_module_pmdb = path_source + "/" + module_pmdb | |
| path_module_project = path_source + "/" + module_project | |
| path_module_simpleregex = path_source + "/" + module_simpleregex | |
| path_module_textpipeline = path_source + "/" + module_textpipeline | |
| path_module_ux = path_source + "/" + module_ux | |
| #endregion Module Paths | |
| #region GDExtension Scons Configuration | |
| #region Default Platform | |
| env_tools = \ | |
| [] | |
| # [ "default" ] | |
| # Try to detect the host platform automatically. | |
| # This is used if no `platform` argument is passed | |
| if sys.platform.startswith("linux"): | |
| default_platform = "linux" | |
| env_tools = [ 'g++', 'ilink' ] | |
| elif sys.platform == "darwin": | |
| default_platform = "macos" | |
| env_tools = [ 'clang', 'applelink' ] | |
| elif sys.platform == "win32" or sys.platform == "msys": | |
| default_platform = "windows" | |
| env_tools = [ 'msvc', 'mslink' ] | |
| elif ARGUMENTS.get("platform", ""): | |
| default_platform = ARGUMENTS.get("platform") | |
| else: | |
| raise ValueError("Could not detect platform automatically, please specify with platform=<platform>") | |
| plog( "Default Platform: " + default_platform) | |
| #endregion Default Platform | |
| env = Environment( tools = env_tools ) | |
| plog( "Created Enviornment" ) | |
| # env['ENV']['TERM'] = os.environ['TERM'] | |
| #region Job Execution | |
| # Default num_jobs to local cpu count if not user specified. | |
| # SCons has a peculiarity where user-specified options won't be overridden | |
| # by SetOption, so we can rely on this to know if we should use our default. | |
| initial_num_jobs = env.GetOption("num_jobs") | |
| altered_num_jobs = initial_num_jobs + 1 | |
| env.SetOption("num_jobs", altered_num_jobs) | |
| if env.GetOption("num_jobs") == altered_num_jobs: | |
| cpu_count = os.cpu_count() | |
| if cpu_count is None: | |
| plog("Couldn't auto-detect CPU count to configure build parallelism. Specify it with the -j argument.") | |
| else: | |
| safer_cpu_count = cpu_count if cpu_count <= 4 else cpu_count - 1 | |
| plog( | |
| "Auto-detected %d CPU cores available for build parallelism. Using %d cores by default. You can override it with the -j argument." | |
| % (cpu_count, safer_cpu_count) | |
| ) | |
| env.SetOption("num_jobs", safer_cpu_count) | |
| #endregion Job Execution | |
| #region Custom options and profile flags. | |
| customs = ["custom.py"] | |
| profile = ARGUMENTS.get("profile", "") | |
| if profile: | |
| if os.path.isfile(profile): | |
| customs.append(profile) | |
| elif os.path.isfile(profile + ".py"): | |
| customs.append(profile + ".py") | |
| opts = Variables(customs, ARGUMENTS) | |
| platforms = ("linux", "macos", "windows", "android", "ios", "javascript") | |
| opts.Add( | |
| EnumVariable( | |
| "platform", | |
| "Target platform", | |
| default_platform, | |
| allowed_values=platforms, | |
| ignorecase=2, | |
| ) | |
| ) | |
| # Editor and template_debug are compatible (i.e. you can use the same binary for Godot editor builds and Godot debug templates). | |
| # Godot release templates are only compatible with "template_release" builds. | |
| # For this reason, we default to template_debug builds, unlike Godot which defaults to editor builds. | |
| opts.Add( EnumVariable("target", "Compilation target", "template_debug", ("editor", "template_release", "template_debug")) ) | |
| opts.Add( PathVariable("gdextension_dir", "Path to the directory containing GDExtension interface header and API JSON file", | |
| path_godotcpp + "/gdextension", | |
| PathVariable.PathIsDir, | |
| ) | |
| ) | |
| opts.Add( PathVariable("custom_api_file", "Path to a custom JSON API file", None, PathVariable.PathIsFile)) | |
| opts.Add( EnumVariable("float", "Floating-point precision", "32", ("32", "64"))) | |
| # CPU architecture options. | |
| architecture_array = ["", "universal", "x86_32", "x86_64", "arm32", "arm64", "rv64", "ppc32", "ppc64", "wasm32"] | |
| architecture_aliases = { | |
| "x64": "x86_64", | |
| "amd64": "x86_64", | |
| "armv7": "arm32", | |
| "armv8": "arm64", | |
| "arm64v8": "arm64", | |
| "aarch64": "arm64", | |
| "rv": "rv64", | |
| "riscv": "rv64", | |
| "riscv64": "rv64", | |
| "ppcle": "ppc32", | |
| "ppc": "ppc32", | |
| "ppc64le": "ppc64", | |
| } | |
| opts.Add(EnumVariable("arch", "CPU architecture", "", architecture_array, architecture_aliases)) | |
| # Add platform options | |
| tools = {} | |
| path_godotcpp_tools = path_godotcpp + "/tools" | |
| for pl in platforms: | |
| tool = Tool( pl, toolpath = [ path_godotcpp_tools ] ) | |
| if hasattr(tool, "options"): | |
| tool.options(opts) | |
| tools[pl] = tool | |
| plog( "Populated platform tools") | |
| # Targets flags tool (optimizations, debug symbols) | |
| target_tool = Tool( "targets", toolpath = [ path_godotcpp_tools ] ) | |
| target_tool.options(opts) | |
| opts.Update(env) | |
| # Disable this if doing incrementals | |
| # Help(opts.GenerateHelpText(env)) | |
| plog( "Build Options updated" ) | |
| #endregion Custom options and profile flags. | |
| #region Toolchain generation | |
| # Process CPU architecture argument. | |
| if env["arch"] == "": | |
| # No architecture specified. Default to arm64 if building for Android, | |
| # universal if building for macOS or iOS, wasm32 if building for web, | |
| # otherwise default to the host architecture. | |
| if env["platform"] in ["macos", "ios"]: | |
| env["arch"] = "universal" | |
| elif env["platform"] == "android": | |
| env["arch"] = "arm64" | |
| elif env["platform"] == "javascript": | |
| env["arch"] = "wasm32" | |
| else: | |
| host_machine = platform.machine().lower() | |
| if host_machine in architecture_array: | |
| env["arch"] = host_machine | |
| elif host_machine in architecture_aliases.keys(): | |
| env["arch"] = architecture_aliases[host_machine] | |
| elif "86" in host_machine: | |
| # Catches x86, i386, i486, i586, i686, etc. | |
| env["arch"] = "x86_32" | |
| else: | |
| print("Unsupported CPU architecture: " + host_machine) | |
| Exit() | |
| tool = Tool( env["platform"], toolpath = [ path_godotcpp_tools ] ) | |
| if tool is None or not tool.exists(env): | |
| raise ValueError("Required toolchain not found for platform " + env["platform"]) | |
| tool.generate(env) | |
| target_tool.generate(env) | |
| #endregion Toolchain generation | |
| # Detect and print a warning listing unknown SCons variables to ease troubleshooting. | |
| unknown = opts.UnknownVariables() | |
| if unknown: | |
| print("WARNING: Unknown SCons variables were passed and will be ignored:") | |
| for item in unknown.items(): | |
| print(" " + item[0] + "=" + item[1]) | |
| plog("Building for architecture " + env["arch"] + " on platform " + env["platform"]) | |
| # Require C++17 | |
| if env.get("is_msvc", False): | |
| env.Append(CXXFLAGS=["/std:c++17"]) | |
| else: | |
| env.Append(CXXFLAGS=["-std=c++17"]) | |
| if env["float"] == "64": | |
| env.Append(CPPDEFINES=["REAL_T_IS_DOUBLE"]) | |
| scons_cache_path = os.environ.get("SCONS_CACHE") | |
| if scons_cache_path is not None: | |
| CacheDir(scons_cache_path) | |
| Decider("MD5") | |
| plog( "scons_cache_path: " + scons_cache_path ) | |
| suffix = ".{}.{}".format(env["platform"], env["target"]) | |
| if env.dev_build: | |
| suffix += ".dev" | |
| if env["float"] == "64": | |
| suffix += ".double" | |
| suffix += "." + env["arch"] | |
| if env["ios_simulator"]: | |
| suffix += ".simulator" | |
| # Expose it when included from another project | |
| env["suffix"] = suffix | |
| env["OBJSUFFIX"] = suffix + env["OBJSUFFIX"] | |
| if not env.get("verbose"): | |
| no_verbose(sys, env) | |
| #endregion GDExtension Scons Configuration | |
| #region GDExtesnion Library Configuration | |
| # GODOT CPP GDEXTENSION INCLUDES | |
| include_godot_cpp_directories = [ | |
| path_godotcpp + "/gdextension", | |
| path_godotcpp + "/include", | |
| path_godotcpp + "/gen/include", | |
| ] | |
| env.Append( CPPPATH = include_godot_cpp_directories ) | |
| path_libgodot = path_godotcpp + "/bin" | |
| libgodot = "libgodot-cpp{}{}".format(suffix, env["LIBSUFFIX"]) | |
| env.Append( LIBPATH = path_libgodot) | |
| env.Append( LIBS = libgodot) | |
| plog( "GDExtension library configured") | |
| #endregion GDExtesnion Library Configuration | |
| #region Module Configuration Helpers | |
| library_targetsig = "{}".format( suffix ) | |
| library_suffixlib = "{}".format( suffix, env["LIBSUFFIX"] ) | |
| library_suffixshared = "{}".format( suffix, env["SHLIBSUFFIX"] ) | |
| def define_shared_library( build_env, module_name, module_sources): | |
| if build_env["platform"] == "macos": | |
| return build_env.SharedLibrary( | |
| path_gdproject + "/Extensions/bin/" + module_name + "{}.{}.framework/" + module_name + ".{}.{}".format( | |
| build_env["platform"], build_env["target"], build_env["platform"], build_env["target"] | |
| ), | |
| source=module_sources, | |
| ) | |
| else: | |
| return build_env.SharedLibrary( | |
| path_gdproject + "/Extensions/bin/" + module_name + "{}{}".format(env["suffix"], build_env["SHLIBSUFFIX"]), | |
| source=module_sources, | |
| ) | |
| def define_symbol_file( build_env, module_name ): | |
| if build_env["platform"] == "windows": | |
| return module_name + library_targetsig + ".pdb" | |
| #endregion | |
| #region Module Configuration | |
| # GENERAL INCLUDES | |
| include_directories = [ | |
| path_platform, | |
| path_godotcpp, | |
| path_source, | |
| ] | |
| env.Append( CPPPATH = include_directories ) | |
| # GENERAL LIBRARIES | |
| library_directories = [ | |
| path_gdproject + "/Extensions/bin", | |
| ] | |
| env.Append( LIBPATH = library_directories ) | |
| env_core = env.Clone() | |
| env_engine = env.Clone() | |
| env_simpleregex = env.Clone() | |
| env_textpipeline = env.Clone() | |
| env_core.Append( CPPPATH = [ path_module_core ] ) | |
| env_engine.Append( CPPPATH = [ path_module_engine ] ) | |
| env_simpleregex.Append( CPPPATH = [ path_module_simpleregex ] ) | |
| env_textpipeline.Append( CPPPATH = [ path_module_simpleregex ] ) | |
| libaries_core = [ | |
| ] | |
| libraries_engine = [ | |
| "Core" + library_suffixlib, | |
| "TextPipeline" + library_suffixlib, | |
| ] | |
| libraries_simpleregex = [ | |
| "Core" + library_suffixlib, | |
| ] | |
| libraries_textpipeline = [ | |
| "Core" + library_suffixlib, | |
| "SimpleRegex" + library_suffixlib, | |
| ] | |
| env_core.Append( LIBS = libaries_core ) | |
| env_engine.Append( LIBS = libraries_engine ) | |
| env_simpleregex.Append( LIBS = libraries_simpleregex ) | |
| env_textpipeline.Append( LIBS = libraries_textpipeline ) | |
| sources_core = [] | |
| sources_engine = [] | |
| sources_simpleregex = [] | |
| sources_textpipeline = [] | |
| add_sources( sources_core, "./" + module_core, "cpp" ) | |
| add_sources( sources_engine, "./" + module_engine, "cpp") | |
| add_sources( sources_simpleregex, "./" + module_simpleregex, "cpp") | |
| add_sources( sources_textpipeline, "./" + module_textpipeline, "cpp") | |
| # These are lists of files to generate for the env. | |
| library_core = define_shared_library( env_core, module_core, sources_core) | |
| library_engine = define_shared_library( env_engine, module_engine, sources_engine) | |
| library_simpleregex = define_shared_library( env_simpleregex, module_simpleregex, sources_simpleregex) | |
| library_textpipeline = define_shared_library( env_textpipeline, module_textpipeline, sources_textpipeline) | |
| plog("Module configured") | |
| #endregion Module Configuration | |
| #region Build Configuraiton | |
| env_simpleregex.Depends( library_simpleregex, library_core ) | |
| env_textpipeline.Depends( library_textpipeline, library_simpleregex ) | |
| env_engine.Depends( library_engine, library_textpipeline ) | |
| env_core.Default( library_core ) | |
| env_engine.Default( library_engine ) | |
| env_simpleregex.Default( library_simpleregex ) | |
| env_textpipeline.Default( library_textpipeline ) | |
| #endregion Building |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment