#!/usr/bin/env python import os import platform import sys from pathlib import Path EnsureSConsVersion(4, 0) def add_sources(sources, dir, extension): for f in os.listdir(dir): if f.endswith("." + extension): sources.append(dir + "/" + f) def normalize_path(val): return val if os.path.isabs(val) else os.path.join(env.Dir("#").abspath, val) # Try to detect the host platform automatically. # This is used if no `platform` argument is passed if sys.platform == "darwin": default_platform = "macos" elif sys.platform == "win32" or sys.platform == "msys": default_platform = "windows" elif ARGUMENTS.get("platform", ""): default_platform = ARGUMENTS.get("platform") else: raise ValueError("Could not detect platform automatically, please specify with platform=") try: Import("env") except: # Default tools with no platform defaults to gnu toolchain. # We apply platform specific toolchains via our custom tools. env = Environment(tools=["default"], PLATFORM="") env.PrependENVPath("PATH", os.getenv("PATH")) # 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: print("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 print( "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) # 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 = ("macos", "windows") opts.Add( EnumVariable( key="platform", help="Target platform", default=env.get("platform", default_platform), allowed_values=platforms, ignorecase=2, ) ) # Add platform options tools = {} for pl in platforms: tool = Tool(pl, toolpath=["godot-tools"]) if hasattr(tool, "options"): tool.options(opts) tools[pl] = tool # 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( key="arch", help="CPU architecture", default=env.get("arch", ""), allowed_values=architecture_array, map=architecture_aliases, ) ) opts.Add("cppdefines", "Custom defines for the pre-processor") opts.Add("ccflags", "Custom flags for both the C and C++ compilers") opts.Add("cxxflags", "Custom flags for the C++ compiler") opts.Add("cflags", "Custom flags for the C compiler") opts.Add("linkflags", "Custom flags for the linker") opts.Add("extra_suffix", "Custom extra suffix added to the base filename of all generated binary files.", "") opts.Add(BoolVariable("use_asan", "Use address sanitizer (ASAN) in MSVC", False)) # Targets flags tool (optimizations, debug symbols) target_tool = Tool("targets", toolpath=["godot-tools"]) target_tool.options(opts) opts.Update(env) Help(opts.GenerateHelpText(env)) # 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" 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=["godot-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) # 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]) print("Building for architecture " + env["arch"] + " on platform " + env["platform"]) env.Append(CPPDEFINES=env.get("cppdefines", "").split()) env.Append(CCFLAGS=env.get("ccflags", "").split()) env.Append(CXXFLAGS=env.get("cxxflags", "").split()) env.Append(CFLAGS=env.get("cflags", "").split()) env.Append(LINKFLAGS=env.get("linkflags", "").split()) # Require C++17 if env.get("is_msvc", False): env.Append(CXXFLAGS=["/std:c++17"]) else: env.Append(CXXFLAGS=["-std=c++17"]) if env["platform"] == "macos": # CPU architecture. if env["arch"] == "arm64": print("Building for macOS 11.0+.") env.Append(ASFLAGS=["-mmacosx-version-min=11.0"]) env.Append(CCFLAGS=["-mmacosx-version-min=11.0"]) env.Append(LINKFLAGS=["-mmacosx-version-min=11.0"]) elif env["arch"] == "x86_64": print("Building for macOS 10.13+.") env.Append(ASFLAGS=["-mmacosx-version-min=10.13"]) env.Append(CCFLAGS=["-mmacosx-version-min=10.13"]) env.Append(LINKFLAGS=["-mmacosx-version-min=10.13"]) elif env["platform"] == "windows": env.AppendUnique(CPPDEFINES=["WINVER=0x0603", "_WIN32_WINNT=0x0603"]) # Sanitizers. if env.get("use_asan", False) and env.get("is_msvc", False): env["extra_suffix"] += ".san" env.Append(LINKFLAGS=["/INFERASANLIBS"]) env.Append(CCFLAGS=["/fsanitize=address"]) scons_cache_path = os.environ.get("SCONS_CACHE") if scons_cache_path is not None: CacheDir(scons_cache_path) Decider("MD5") mesa_dir = "#godot-mesa" mesa_gen_dir = "#godot-mesa/generated" mesa_absdir = Dir(mesa_dir).abspath mesa_gen_absdir = Dir(mesa_dir + "/generated").abspath custom_build_steps = [ [ "src/compiler", "glsl/ir_expression_operation.py enum > %s/ir_expression_operation.h", "ir_expression_operation.h", ], ["src/compiler/nir", "nir_builder_opcodes_h.py > %s/nir_builder_opcodes.h", "nir_builder_opcodes.h"], ["src/compiler/nir", "nir_constant_expressions.py > %s/nir_constant_expressions.c", "nir_constant_expressions.c"], ["src/compiler/nir", "nir_intrinsics_h.py --out %s/nir_intrinsics.h", "nir_intrinsics.h"], ["src/compiler/nir", "nir_intrinsics_c.py --out %s/nir_intrinsics.c", "nir_intrinsics.c"], ["src/compiler/nir", "nir_intrinsics_indices_h.py --out %s/nir_intrinsics_indices.h", "nir_intrinsics_indices.h"], ["src/compiler/nir", "nir_opcodes_h.py > %s/nir_opcodes.h", "nir_opcodes.h"], ["src/compiler/nir", "nir_opcodes_c.py > %s/nir_opcodes.c", "nir_opcodes.c"], ["src/compiler/nir", "nir_opt_algebraic.py --out %s/nir_opt_algebraic.c", "nir_opt_algebraic.c"], ["src/compiler/spirv", "vtn_generator_ids_h.py spir-v.xml %s/vtn_generator_ids.h", "vtn_generator_ids.h"], [ "src/microsoft/compiler", "dxil_nir_algebraic.py -p ../../../src/compiler/nir > %s/dxil_nir_algebraic.c", "dxil_nir_algebraic.c", ], ["src/util", "format_srgb.py > %s/format_srgb.c", "format_srgb.c"], ["src/util/format", "u_format_table.py u_format.yaml --enums > %s/u_format_gen.h", "u_format_gen.h"], ["src/util/format", "u_format_table.py u_format.yaml --header > %s/u_format_pack.h", "u_format_pack.h"], ["src/util/format", "u_format_table.py u_format.yaml > %s/u_format_table.c", "u_format_table.c"], ["src/compiler", "builtin_types_h.py %s/builtin_types.h", "builtin_types.h"], ["src/compiler", "builtin_types_c.py %s/builtin_types.c", "builtin_types.c"], ] mesa_sources = [] mesa_gen_headers = [] mesa_gen_sources = [] mesa_gen_include_paths = [mesa_gen_dir + "/src"] # See update_mesa.sh for explanation. for v in custom_build_steps: subdir = v[0] cmd = v[1] gen_filename = v[2] in_dir = str(Path(mesa_absdir + "/" + subdir)) out_dir = str(Path(mesa_absdir + "/generated/" + subdir)) script_filename = cmd.split()[0] out_full_path = mesa_dir + "/generated/" + subdir out_file_full_path = out_full_path + "/" + gen_filename if gen_filename.endswith(".h"): mesa_gen_include_paths += [out_full_path] mesa_gen_headers += [out_file_full_path] else: mesa_gen_sources += [out_file_full_path] mesa_private_inc_paths = [v[0] for v in os.walk(mesa_absdir)] mesa_private_inc_paths = [v.replace(mesa_absdir, mesa_dir) for v in mesa_private_inc_paths] mesa_private_inc_paths = [v.replace("\\", "/") for v in mesa_private_inc_paths] # Avoid build results depending on if generated files already exist. mesa_private_inc_paths = [v for v in mesa_private_inc_paths if not v.startswith(mesa_gen_dir)] mesa_private_inc_paths.sort() # Include the list of the generated ones now, so out-of-the-box sources can include generated headers. mesa_private_inc_paths += mesa_gen_include_paths # We have to blacklist some because we are bindly adding every Mesa directory # to the include path and in some cases that causes the wrong header to be included. mesa_blacklist_inc_paths = [ "src/c11", ] mesa_blacklist_inc_paths = [mesa_dir + "/" + v for v in mesa_blacklist_inc_paths] mesa_private_inc_paths = [v for v in mesa_private_inc_paths if v not in mesa_blacklist_inc_paths] # Prepending these to avoid some conflicts, at least with OS GL headers. env.Prepend(CPPPATH=mesa_private_inc_paths) mesa_sources += [v for v in list(Path(mesa_absdir).rglob("*.c"))] mesa_sources += [v for v in list(Path(mesa_absdir).rglob("*.cpp"))] mesa_sources = [str(v).replace(mesa_absdir, mesa_dir) for v in mesa_sources] mesa_sources = [v.replace("\\", "/") for v in mesa_sources] # Avoid build results depending on if generated files already exist. mesa_sources = [v for v in mesa_sources if not v.startswith(mesa_gen_dir)] mesa_sources.sort() # Include the list of the generated ones now. mesa_sources += mesa_gen_sources # Clean C/C++/both flags to avoid std clashes. for key in ["CFLAGS", "CXXFLAGS", "CCFLAGS"]: env[key] = [v for v in env[key] if "/std:" not in v and "-std=" not in v] # Added by ourselves. extra_defines = [ "WINDOWS_NO_FUTEX", ] if env["optimize"] != "debug": extra_defines += ["NDEBUG"] # These defines are inspired by the Meson build scripts in the original repo. extra_defines += [ "__STDC_CONSTANT_MACROS", "__STDC_FORMAT_MACROS", "__STDC_LIMIT_MACROS", ("PACKAGE_VERSION", '\\"' + Path(mesa_absdir + "/VERSION.info").read_text().strip() + '\\"'), ("PACKAGE_BUGREPORT", '\\"https://gitlab.freedesktop.org/mesa/mesa/-/issues\\"'), "PIPE_SUBSYSTEM_WINDOWS_USER", "_USE_MATH_DEFINES", "BLAKE3_NO_SSE2", "BLAKE3_NO_SSE41", "BLAKE3_NO_AVX2", "BLAKE3_NO_AVX512", ("BLAKE3_USE_NEON", 0), ] if env.get("is_msvc", False): extra_defines += [ "_USE_MATH_DEFINES", "VC_EXTRALEAN", "_CRT_SECURE_NO_WARNINGS", "_CRT_SECURE_NO_DEPRECATE", "_SCL_SECURE_NO_WARNINGS", "_SCL_SECURE_NO_DEPRECATE", "_ALLOW_KEYWORD_MACROS", ("_HAS_EXCEPTIONS", 0), "NOMINMAX", "HAVE_STRUCT_TIMESPEC", "HAVE_TIMESPEC_GET", ("_Static_assert", "static_assert"), ] env.Append(CFLAGS=["/std:c11"]) else: env.Append( CPPDEFINES=[ "HAVE_STRUCT_TIMESPEC", ] ) env.Append(CFLAGS=["-std=c11"]) env.Append(CXXFLAGS=["-fno-exceptions"]) if env.get("use_llvm", False): extra_defines += [ ("__MSVCRT_VERSION__", 0x0700), "HAVE_TIMESPEC_GET", "_UCRT", ] env.Append(CPPDEFINES=extra_defines) env.Append(CPPPATH=".") env.Append(CPPPATH="#vulkan/include") suffix = ".{}.{}".format(env["platform"], env["arch"]) if env.get("extra_suffix", "") != "": suffix += "." + env["extra_suffix"] # Expose it when included from another project env["suffix"] = suffix library = None env["OBJSUFFIX"] = suffix + env["OBJSUFFIX"] library_name = "libNIR{}{}".format(suffix, env["LIBSUFFIX"]) library = env.StaticLibrary(name="NIR", target=env.File("bin/%s" % library_name), source=mesa_sources) Return("env")