1import os 2import re 3import glob 4import subprocess 5from collections import OrderedDict 6from compat import iteritems, isbasestring, open_utf8, decode_utf8, qualname 7 8 9def add_source_files(self, sources, files, warn_duplicates=True): 10 # Convert string to list of absolute paths (including expanding wildcard) 11 if isbasestring(files): 12 # Keep SCons project-absolute path as they are (no wildcard support) 13 if files.startswith("#"): 14 if "*" in files: 15 print("ERROR: Wildcards can't be expanded in SCons project-absolute path: '{}'".format(files)) 16 return 17 files = [files] 18 else: 19 dir_path = self.Dir(".").abspath 20 files = sorted(glob.glob(dir_path + "/" + files)) 21 22 # Add each path as compiled Object following environment (self) configuration 23 for path in files: 24 obj = self.Object(path) 25 if obj in sources: 26 if warn_duplicates: 27 print('WARNING: Object "{}" already included in environment sources.'.format(obj)) 28 else: 29 continue 30 sources.append(obj) 31 32 33def disable_warnings(self): 34 # 'self' is the environment 35 if self.msvc: 36 # We have to remove existing warning level defines before appending /w, 37 # otherwise we get: "warning D9025 : overriding '/W3' with '/w'" 38 warn_flags = ["/Wall", "/W4", "/W3", "/W2", "/W1", "/WX"] 39 self.Append(CCFLAGS=["/w"]) 40 self.Append(CFLAGS=["/w"]) 41 self.Append(CXXFLAGS=["/w"]) 42 self["CCFLAGS"] = [x for x in self["CCFLAGS"] if not x in warn_flags] 43 self["CFLAGS"] = [x for x in self["CFLAGS"] if not x in warn_flags] 44 self["CXXFLAGS"] = [x for x in self["CXXFLAGS"] if not x in warn_flags] 45 else: 46 self.Append(CCFLAGS=["-w"]) 47 self.Append(CFLAGS=["-w"]) 48 self.Append(CXXFLAGS=["-w"]) 49 50 51def add_module_version_string(self, s): 52 self.module_version_string += "." + s 53 54 55def update_version(module_version_string=""): 56 57 build_name = "DragonFly_Ports_build" 58 if os.getenv("BUILD_NAME") != None: 59 build_name = os.getenv("BUILD_NAME") 60 print("Using custom build name: " + build_name) 61 62 import version 63 64 # NOTE: It is safe to generate this file here, since this is still executed serially 65 f = open("core/version_generated.gen.h", "w") 66 f.write('#define VERSION_SHORT_NAME "' + str(version.short_name) + '"\n') 67 f.write('#define VERSION_NAME "' + str(version.name) + '"\n') 68 f.write("#define VERSION_MAJOR " + str(version.major) + "\n") 69 f.write("#define VERSION_MINOR " + str(version.minor) + "\n") 70 f.write("#define VERSION_PATCH " + str(version.patch) + "\n") 71 f.write('#define VERSION_STATUS "' + str(version.status) + '"\n') 72 f.write('#define VERSION_BUILD "' + str(build_name) + '"\n') 73 f.write('#define VERSION_MODULE_CONFIG "' + str(version.module_config) + module_version_string + '"\n') 74 f.write("#define VERSION_YEAR " + str(version.year) + "\n") 75 f.write('#define VERSION_WEBSITE "' + str(version.website) + '"\n') 76 f.close() 77 78 # NOTE: It is safe to generate this file here, since this is still executed serially 79 fhash = open("core/version_hash.gen.h", "w") 80 githash = "" 81 gitfolder = ".git" 82 83 if os.path.isfile(".git"): 84 module_folder = open(".git", "r").readline().strip() 85 if module_folder.startswith("gitdir: "): 86 gitfolder = module_folder[8:] 87 88 if os.path.isfile(os.path.join(gitfolder, "HEAD")): 89 head = open_utf8(os.path.join(gitfolder, "HEAD"), "r").readline().strip() 90 if head.startswith("ref: "): 91 head = os.path.join(gitfolder, head[5:]) 92 if os.path.isfile(head): 93 githash = open(head, "r").readline().strip() 94 else: 95 githash = head 96 97 fhash.write('#define VERSION_HASH "' + githash + '"') 98 fhash.close() 99 100 101def parse_cg_file(fname, uniforms, sizes, conditionals): 102 103 fs = open(fname, "r") 104 line = fs.readline() 105 106 while line: 107 108 if re.match(r"^\s*uniform", line): 109 110 res = re.match(r"uniform ([\d\w]*) ([\d\w]*)") 111 type = res.groups(1) 112 name = res.groups(2) 113 114 uniforms.append(name) 115 116 if type.find("texobj") != -1: 117 sizes.append(1) 118 else: 119 t = re.match(r"float(\d)x(\d)", type) 120 if t: 121 sizes.append(int(t.groups(1)) * int(t.groups(2))) 122 else: 123 t = re.match(r"float(\d)", type) 124 sizes.append(int(t.groups(1))) 125 126 if line.find("[branch]") != -1: 127 conditionals.append(name) 128 129 line = fs.readline() 130 131 fs.close() 132 133 134def detect_modules(at_path): 135 module_list = OrderedDict() # name : path 136 137 modules_glob = os.path.join(at_path, "*") 138 files = glob.glob(modules_glob) 139 files.sort() # so register_module_types does not change that often, and also plugins are registered in alphabetic order 140 141 for x in files: 142 if not is_module(x): 143 continue 144 name = os.path.basename(x) 145 path = x.replace("\\", "/") # win32 146 module_list[name] = path 147 148 return module_list 149 150 151def is_module(path): 152 return os.path.isdir(path) and os.path.exists(os.path.join(path, "SCsub")) 153 154 155def write_modules(module_list): 156 includes_cpp = "" 157 register_cpp = "" 158 unregister_cpp = "" 159 160 for name, path in module_list.items(): 161 try: 162 with open(os.path.join(path, "register_types.h")): 163 includes_cpp += '#include "' + path + '/register_types.h"\n' 164 register_cpp += "#ifdef MODULE_" + name.upper() + "_ENABLED\n" 165 register_cpp += "\tregister_" + name + "_types();\n" 166 register_cpp += "#endif\n" 167 unregister_cpp += "#ifdef MODULE_" + name.upper() + "_ENABLED\n" 168 unregister_cpp += "\tunregister_" + name + "_types();\n" 169 unregister_cpp += "#endif\n" 170 except IOError: 171 pass 172 173 modules_cpp = ( 174 """ 175// modules.cpp - THIS FILE IS GENERATED, DO NOT EDIT!!!!!!! 176#include "register_module_types.h" 177 178""" 179 + includes_cpp 180 + """ 181 182void register_module_types() { 183""" 184 + register_cpp 185 + """ 186} 187 188void unregister_module_types() { 189""" 190 + unregister_cpp 191 + """ 192} 193""" 194 ) 195 196 # NOTE: It is safe to generate this file here, since this is still executed serially 197 with open("modules/register_module_types.gen.cpp", "w") as f: 198 f.write(modules_cpp) 199 200 201def convert_custom_modules_path(path): 202 if not path: 203 return path 204 path = os.path.realpath(os.path.expanduser(os.path.expandvars(path))) 205 err_msg = "Build option 'custom_modules' must %s" 206 if not os.path.isdir(path): 207 raise ValueError(err_msg % "point to an existing directory.") 208 if path == os.path.realpath("modules"): 209 raise ValueError(err_msg % "be a directory other than built-in `modules` directory.") 210 if is_module(path): 211 raise ValueError(err_msg % "point to a directory with modules, not a single module.") 212 return path 213 214 215def disable_module(self): 216 self.disabled_modules.append(self.current_module) 217 218 219def use_windows_spawn_fix(self, platform=None): 220 221 if os.name != "nt": 222 return # not needed, only for windows 223 224 # On Windows, due to the limited command line length, when creating a static library 225 # from a very high number of objects SCons will invoke "ar" once per object file; 226 # that makes object files with same names to be overwritten so the last wins and 227 # the library looses symbols defined by overwritten objects. 228 # By enabling quick append instead of the default mode (replacing), libraries will 229 # got built correctly regardless the invocation strategy. 230 # Furthermore, since SCons will rebuild the library from scratch when an object file 231 # changes, no multiple versions of the same object file will be present. 232 self.Replace(ARFLAGS="q") 233 234 def mySubProcess(cmdline, env): 235 236 startupinfo = subprocess.STARTUPINFO() 237 startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW 238 proc = subprocess.Popen( 239 cmdline, 240 stdin=subprocess.PIPE, 241 stdout=subprocess.PIPE, 242 stderr=subprocess.PIPE, 243 startupinfo=startupinfo, 244 shell=False, 245 env=env, 246 ) 247 _, err = proc.communicate() 248 rv = proc.wait() 249 if rv: 250 print("=====") 251 print(err) 252 print("=====") 253 return rv 254 255 def mySpawn(sh, escape, cmd, args, env): 256 257 newargs = " ".join(args[1:]) 258 cmdline = cmd + " " + newargs 259 260 rv = 0 261 env = {str(key): str(value) for key, value in iteritems(env)} 262 if len(cmdline) > 32000 and cmd.endswith("ar"): 263 cmdline = cmd + " " + args[1] + " " + args[2] + " " 264 for i in range(3, len(args)): 265 rv = mySubProcess(cmdline + args[i], env) 266 if rv: 267 break 268 else: 269 rv = mySubProcess(cmdline, env) 270 271 return rv 272 273 self["SPAWN"] = mySpawn 274 275 276def split_lib(self, libname, src_list=None, env_lib=None): 277 env = self 278 279 num = 0 280 cur_base = "" 281 max_src = 64 282 list = [] 283 lib_list = [] 284 285 if src_list is None: 286 src_list = getattr(env, libname + "_sources") 287 288 if type(env_lib) == type(None): 289 env_lib = env 290 291 for f in src_list: 292 fname = "" 293 if type(f) == type(""): 294 fname = env.File(f).path 295 else: 296 fname = env.File(f)[0].path 297 fname = fname.replace("\\", "/") 298 base = "/".join(fname.split("/")[:2]) 299 if base != cur_base and len(list) > max_src: 300 if num > 0: 301 lib = env_lib.add_library(libname + str(num), list) 302 lib_list.append(lib) 303 list = [] 304 num = num + 1 305 cur_base = base 306 list.append(f) 307 308 lib = env_lib.add_library(libname + str(num), list) 309 lib_list.append(lib) 310 311 lib_base = [] 312 env_lib.add_source_files(lib_base, "*.cpp") 313 lib = env_lib.add_library(libname, lib_base) 314 lib_list.insert(0, lib) 315 316 env.Prepend(LIBS=lib_list) 317 318 # When we split modules into arbitrary chunks, we end up with linking issues 319 # due to symbol dependencies split over several libs, which may not be linked 320 # in the required order. We use --start-group and --end-group to tell the 321 # linker that those archives should be searched repeatedly to resolve all 322 # undefined references. 323 # As SCons doesn't give us much control over how inserting libs in LIBS 324 # impacts the linker call, we need to hack our way into the linking commands 325 # LINKCOM and SHLINKCOM to set those flags. 326 327 if "-Wl,--start-group" in env["LINKCOM"] and "-Wl,--start-group" in env["SHLINKCOM"]: 328 # Already added by a previous call, skip. 329 return 330 331 env["LINKCOM"] = str(env["LINKCOM"]).replace("$_LIBFLAGS", "-Wl,--start-group $_LIBFLAGS -Wl,--end-group") 332 env["SHLINKCOM"] = str(env["LINKCOM"]).replace("$_LIBFLAGS", "-Wl,--start-group $_LIBFLAGS -Wl,--end-group") 333 334 335def save_active_platforms(apnames, ap): 336 337 for x in ap: 338 names = ["logo"] 339 if os.path.isfile(x + "/run_icon.png"): 340 names.append("run_icon") 341 342 for name in names: 343 pngf = open(x + "/" + name + ".png", "rb") 344 b = pngf.read(1) 345 str = " /* AUTOGENERATED FILE, DO NOT EDIT */ \n" 346 str += " static const unsigned char _" + x[9:] + "_" + name + "[]={" 347 while len(b) == 1: 348 str += hex(ord(b)) 349 b = pngf.read(1) 350 if len(b) == 1: 351 str += "," 352 353 str += "};\n" 354 355 pngf.close() 356 357 # NOTE: It is safe to generate this file here, since this is still executed serially 358 wf = x + "/" + name + ".gen.h" 359 with open(wf, "w") as pngw: 360 pngw.write(str) 361 362 363def no_verbose(sys, env): 364 365 colors = {} 366 367 # Colors are disabled in non-TTY environments such as pipes. This means 368 # that if output is redirected to a file, it will not contain color codes 369 if sys.stdout.isatty(): 370 colors["cyan"] = "\033[96m" 371 colors["purple"] = "\033[95m" 372 colors["blue"] = "\033[94m" 373 colors["green"] = "\033[92m" 374 colors["yellow"] = "\033[93m" 375 colors["red"] = "\033[91m" 376 colors["end"] = "\033[0m" 377 else: 378 colors["cyan"] = "" 379 colors["purple"] = "" 380 colors["blue"] = "" 381 colors["green"] = "" 382 colors["yellow"] = "" 383 colors["red"] = "" 384 colors["end"] = "" 385 386 compile_source_message = "%sCompiling %s==> %s$SOURCE%s" % ( 387 colors["blue"], 388 colors["purple"], 389 colors["yellow"], 390 colors["end"], 391 ) 392 java_compile_source_message = "%sCompiling %s==> %s$SOURCE%s" % ( 393 colors["blue"], 394 colors["purple"], 395 colors["yellow"], 396 colors["end"], 397 ) 398 compile_shared_source_message = "%sCompiling shared %s==> %s$SOURCE%s" % ( 399 colors["blue"], 400 colors["purple"], 401 colors["yellow"], 402 colors["end"], 403 ) 404 link_program_message = "%sLinking Program %s==> %s$TARGET%s" % ( 405 colors["red"], 406 colors["purple"], 407 colors["yellow"], 408 colors["end"], 409 ) 410 link_library_message = "%sLinking Static Library %s==> %s$TARGET%s" % ( 411 colors["red"], 412 colors["purple"], 413 colors["yellow"], 414 colors["end"], 415 ) 416 ranlib_library_message = "%sRanlib Library %s==> %s$TARGET%s" % ( 417 colors["red"], 418 colors["purple"], 419 colors["yellow"], 420 colors["end"], 421 ) 422 link_shared_library_message = "%sLinking Shared Library %s==> %s$TARGET%s" % ( 423 colors["red"], 424 colors["purple"], 425 colors["yellow"], 426 colors["end"], 427 ) 428 java_library_message = "%sCreating Java Archive %s==> %s$TARGET%s" % ( 429 colors["red"], 430 colors["purple"], 431 colors["yellow"], 432 colors["end"], 433 ) 434 435 env.Append(CXXCOMSTR=[compile_source_message]) 436 env.Append(CCCOMSTR=[compile_source_message]) 437 env.Append(SHCCCOMSTR=[compile_shared_source_message]) 438 env.Append(SHCXXCOMSTR=[compile_shared_source_message]) 439 env.Append(ARCOMSTR=[link_library_message]) 440 env.Append(RANLIBCOMSTR=[ranlib_library_message]) 441 env.Append(SHLINKCOMSTR=[link_shared_library_message]) 442 env.Append(LINKCOMSTR=[link_program_message]) 443 env.Append(JARCOMSTR=[java_library_message]) 444 env.Append(JAVACCOMSTR=[java_compile_source_message]) 445 446 447def detect_visual_c_compiler_version(tools_env): 448 # tools_env is the variable scons uses to call tools that execute tasks, SCons's env['ENV'] that executes tasks... 449 # (see the SCons documentation for more information on what it does)... 450 # in order for this function to be well encapsulated i choose to force it to receive SCons's TOOLS env (env['ENV'] 451 # and not scons setup environment (env)... so make sure you call the right environment on it or it will fail to detect 452 # the proper vc version that will be called 453 454 # There is no flag to give to visual c compilers to set the architecture, ie scons bits argument (32,64,ARM etc) 455 # There are many different cl.exe files that are run, and each one compiles & links to a different architecture 456 # As far as I know, the only way to figure out what compiler will be run when Scons calls cl.exe via Program() 457 # is to check the PATH variable and figure out which one will be called first. Code below does that and returns: 458 # the following string values: 459 460 # "" Compiler not detected 461 # "amd64" Native 64 bit compiler 462 # "amd64_x86" 64 bit Cross Compiler for 32 bit 463 # "x86" Native 32 bit compiler 464 # "x86_amd64" 32 bit Cross Compiler for 64 bit 465 466 # There are other architectures, but Godot does not support them currently, so this function does not detect arm/amd64_arm 467 # and similar architectures/compilers 468 469 # Set chosen compiler to "not detected" 470 vc_chosen_compiler_index = -1 471 vc_chosen_compiler_str = "" 472 473 # Start with Pre VS 2017 checks which uses VCINSTALLDIR: 474 if "VCINSTALLDIR" in tools_env: 475 # print("Checking VCINSTALLDIR") 476 477 # find() works with -1 so big ifs below are needed... the simplest solution, in fact 478 # First test if amd64 and amd64_x86 compilers are present in the path 479 vc_amd64_compiler_detection_index = tools_env["PATH"].find(tools_env["VCINSTALLDIR"] + "BIN\\amd64;") 480 if vc_amd64_compiler_detection_index > -1: 481 vc_chosen_compiler_index = vc_amd64_compiler_detection_index 482 vc_chosen_compiler_str = "amd64" 483 484 vc_amd64_x86_compiler_detection_index = tools_env["PATH"].find(tools_env["VCINSTALLDIR"] + "BIN\\amd64_x86;") 485 if vc_amd64_x86_compiler_detection_index > -1 and ( 486 vc_chosen_compiler_index == -1 or vc_chosen_compiler_index > vc_amd64_x86_compiler_detection_index 487 ): 488 vc_chosen_compiler_index = vc_amd64_x86_compiler_detection_index 489 vc_chosen_compiler_str = "amd64_x86" 490 491 # Now check the 32 bit compilers 492 vc_x86_compiler_detection_index = tools_env["PATH"].find(tools_env["VCINSTALLDIR"] + "BIN;") 493 if vc_x86_compiler_detection_index > -1 and ( 494 vc_chosen_compiler_index == -1 or vc_chosen_compiler_index > vc_x86_compiler_detection_index 495 ): 496 vc_chosen_compiler_index = vc_x86_compiler_detection_index 497 vc_chosen_compiler_str = "x86" 498 499 vc_x86_amd64_compiler_detection_index = tools_env["PATH"].find(tools_env["VCINSTALLDIR"] + "BIN\\x86_amd64;") 500 if vc_x86_amd64_compiler_detection_index > -1 and ( 501 vc_chosen_compiler_index == -1 or vc_chosen_compiler_index > vc_x86_amd64_compiler_detection_index 502 ): 503 vc_chosen_compiler_index = vc_x86_amd64_compiler_detection_index 504 vc_chosen_compiler_str = "x86_amd64" 505 506 # and for VS 2017 and newer we check VCTOOLSINSTALLDIR: 507 if "VCTOOLSINSTALLDIR" in tools_env: 508 509 # Newer versions have a different path available 510 vc_amd64_compiler_detection_index = ( 511 tools_env["PATH"].upper().find(tools_env["VCTOOLSINSTALLDIR"].upper() + "BIN\\HOSTX64\\X64;") 512 ) 513 if vc_amd64_compiler_detection_index > -1: 514 vc_chosen_compiler_index = vc_amd64_compiler_detection_index 515 vc_chosen_compiler_str = "amd64" 516 517 vc_amd64_x86_compiler_detection_index = ( 518 tools_env["PATH"].upper().find(tools_env["VCTOOLSINSTALLDIR"].upper() + "BIN\\HOSTX64\\X86;") 519 ) 520 if vc_amd64_x86_compiler_detection_index > -1 and ( 521 vc_chosen_compiler_index == -1 or vc_chosen_compiler_index > vc_amd64_x86_compiler_detection_index 522 ): 523 vc_chosen_compiler_index = vc_amd64_x86_compiler_detection_index 524 vc_chosen_compiler_str = "amd64_x86" 525 526 vc_x86_compiler_detection_index = ( 527 tools_env["PATH"].upper().find(tools_env["VCTOOLSINSTALLDIR"].upper() + "BIN\\HOSTX86\\X86;") 528 ) 529 if vc_x86_compiler_detection_index > -1 and ( 530 vc_chosen_compiler_index == -1 or vc_chosen_compiler_index > vc_x86_compiler_detection_index 531 ): 532 vc_chosen_compiler_index = vc_x86_compiler_detection_index 533 vc_chosen_compiler_str = "x86" 534 535 vc_x86_amd64_compiler_detection_index = ( 536 tools_env["PATH"].upper().find(tools_env["VCTOOLSINSTALLDIR"].upper() + "BIN\\HOSTX86\\X64;") 537 ) 538 if vc_x86_amd64_compiler_detection_index > -1 and ( 539 vc_chosen_compiler_index == -1 or vc_chosen_compiler_index > vc_x86_amd64_compiler_detection_index 540 ): 541 vc_chosen_compiler_index = vc_x86_amd64_compiler_detection_index 542 vc_chosen_compiler_str = "x86_amd64" 543 544 return vc_chosen_compiler_str 545 546 547def find_visual_c_batch_file(env): 548 from SCons.Tool.MSCommon.vc import get_default_version, get_host_target, find_batch_file 549 550 version = get_default_version(env) 551 (host_platform, target_platform, _) = get_host_target(env) 552 return find_batch_file(env, version, host_platform, target_platform)[0] 553 554 555def generate_cpp_hint_file(filename): 556 if os.path.isfile(filename): 557 # Don't overwrite an existing hint file since the user may have customized it. 558 pass 559 else: 560 try: 561 with open(filename, "w") as fd: 562 fd.write("#define GDCLASS(m_class, m_inherits)\n") 563 except IOError: 564 print("Could not write cpp.hint file.") 565 566 567def generate_vs_project(env, num_jobs): 568 batch_file = find_visual_c_batch_file(env) 569 if batch_file: 570 571 def build_commandline(commands): 572 common_build_prefix = [ 573 'cmd /V /C set "plat=$(PlatformTarget)"', 574 '(if "$(PlatformTarget)"=="x64" (set "plat=x86_amd64"))', 575 'set "tools=yes"', 576 '(if "$(Configuration)"=="release" (set "tools=no"))', 577 'set "custom_modules=%s"' % env["custom_modules"], 578 'call "' + batch_file + '" !plat!', 579 ] 580 581 result = " ^& ".join(common_build_prefix + [commands]) 582 return result 583 584 env.AddToVSProject(env.core_sources) 585 env.AddToVSProject(env.main_sources) 586 env.AddToVSProject(env.modules_sources) 587 env.AddToVSProject(env.scene_sources) 588 env.AddToVSProject(env.servers_sources) 589 env.AddToVSProject(env.editor_sources) 590 591 # windows allows us to have spaces in paths, so we need 592 # to double quote off the directory. However, the path ends 593 # in a backslash, so we need to remove this, lest it escape the 594 # last double quote off, confusing MSBuild 595 env["MSVSBUILDCOM"] = build_commandline( 596 "scons --directory=\"$(ProjectDir.TrimEnd('\\'))\" platform=windows progress=no target=$(Configuration) tools=!tools! custom_modules=!custom_modules! -j" 597 + str(num_jobs) 598 ) 599 env["MSVSREBUILDCOM"] = build_commandline( 600 "scons --directory=\"$(ProjectDir.TrimEnd('\\'))\" platform=windows progress=no target=$(Configuration) tools=!tools! vsproj=yes custom_modules=!custom_modules! -j" 601 + str(num_jobs) 602 ) 603 env["MSVSCLEANCOM"] = build_commandline( 604 "scons --directory=\"$(ProjectDir.TrimEnd('\\'))\" --clean platform=windows progress=no target=$(Configuration) tools=!tools! custom_modules=!custom_modules! -j" 605 + str(num_jobs) 606 ) 607 608 # This version information (Win32, x64, Debug, Release, Release_Debug seems to be 609 # required for Visual Studio to understand that it needs to generate an NMAKE 610 # project. Do not modify without knowing what you are doing. 611 debug_variants = ["debug|Win32"] + ["debug|x64"] 612 release_variants = ["release|Win32"] + ["release|x64"] 613 release_debug_variants = ["release_debug|Win32"] + ["release_debug|x64"] 614 variants = debug_variants + release_variants + release_debug_variants 615 debug_targets = ["bin\\godot.windows.tools.32.exe"] + ["bin\\godot.windows.tools.64.exe"] 616 release_targets = ["bin\\godot.windows.opt.32.exe"] + ["bin\\godot.windows.opt.64.exe"] 617 release_debug_targets = ["bin\\godot.windows.opt.tools.32.exe"] + ["bin\\godot.windows.opt.tools.64.exe"] 618 targets = debug_targets + release_targets + release_debug_targets 619 if not env.get("MSVS"): 620 env["MSVS"]["PROJECTSUFFIX"] = ".vcxproj" 621 env["MSVS"]["SOLUTIONSUFFIX"] = ".sln" 622 env.MSVSProject( 623 target=["#godot" + env["MSVSPROJECTSUFFIX"]], 624 incs=env.vs_incs, 625 srcs=env.vs_srcs, 626 runfile=targets, 627 buildtarget=targets, 628 auto_build_solution=1, 629 variant=variants, 630 ) 631 else: 632 print( 633 "Could not locate Visual Studio batch file for setting up the build environment. Not generating VS project." 634 ) 635 636 637def precious_program(env, program, sources, **args): 638 program = env.ProgramOriginal(program, sources, **args) 639 env.Precious(program) 640 return program 641 642 643def add_shared_library(env, name, sources, **args): 644 library = env.SharedLibrary(name, sources, **args) 645 env.NoCache(library) 646 return library 647 648 649def add_library(env, name, sources, **args): 650 library = env.Library(name, sources, **args) 651 env.NoCache(library) 652 return library 653 654 655def add_program(env, name, sources, **args): 656 program = env.Program(name, sources, **args) 657 env.NoCache(program) 658 return program 659 660 661def CommandNoCache(env, target, sources, command, **args): 662 result = env.Command(target, sources, command, **args) 663 env.NoCache(result) 664 return result 665 666 667def detect_darwin_sdk_path(platform, env): 668 sdk_name = "" 669 if platform == "osx": 670 sdk_name = "macosx" 671 var_name = "MACOS_SDK_PATH" 672 elif platform == "iphone": 673 sdk_name = "iphoneos" 674 var_name = "IPHONESDK" 675 elif platform == "iphonesimulator": 676 sdk_name = "iphonesimulator" 677 var_name = "IPHONESDK" 678 else: 679 raise Exception("Invalid platform argument passed to detect_darwin_sdk_path") 680 681 if not env[var_name]: 682 try: 683 sdk_path = decode_utf8(subprocess.check_output(["xcrun", "--sdk", sdk_name, "--show-sdk-path"]).strip()) 684 if sdk_path: 685 env[var_name] = sdk_path 686 except (subprocess.CalledProcessError, OSError): 687 print("Failed to find SDK path while running xcrun --sdk {} --show-sdk-path.".format(sdk_name)) 688 raise 689 690 691def get_compiler_version(env): 692 """ 693 Returns an array of version numbers as ints: [major, minor, patch]. 694 The return array should have at least two values (major, minor). 695 """ 696 if not env.msvc: 697 # Not using -dumpversion as some GCC distros only return major, and 698 # Clang used to return hardcoded 4.2.1: # https://reviews.llvm.org/D56803 699 try: 700 version = decode_utf8(subprocess.check_output([env.subst(env["CXX"]), "--version"]).strip()) 701 except (subprocess.CalledProcessError, OSError): 702 print("Couldn't parse CXX environment variable to infer compiler version.") 703 return None 704 else: # TODO: Implement for MSVC 705 return None 706 match = re.search("[0-9]+\.[0-9.]+", version) 707 if match is not None: 708 return list(map(int, match.group().split("."))) 709 else: 710 return None 711 712 713def using_gcc(env): 714 return "gcc" in os.path.basename(env["CC"]) 715 716 717def using_clang(env): 718 return "clang" in os.path.basename(env["CC"]) 719 720 721def show_progress(env): 722 import sys 723 from SCons.Script import Progress, Command, AlwaysBuild 724 725 screen = sys.stdout 726 # Progress reporting is not available in non-TTY environments since it 727 # messes with the output (for example, when writing to a file) 728 show_progress = env["progress"] and sys.stdout.isatty() 729 node_count_data = { 730 "count": 0, 731 "max": 0, 732 "interval": 1, 733 "fname": str(env.Dir("#")) + "/.scons_node_count", 734 } 735 736 import time, math 737 738 class cache_progress: 739 # The default is 1 GB cache and 12 hours half life 740 def __init__(self, path=None, limit=1073741824, half_life=43200): 741 self.path = path 742 self.limit = limit 743 self.exponent_scale = math.log(2) / half_life 744 if env["verbose"] and path != None: 745 screen.write( 746 "Current cache limit is {} (used: {})\n".format( 747 self.convert_size(limit), self.convert_size(self.get_size(path)) 748 ) 749 ) 750 self.delete(self.file_list()) 751 752 def __call__(self, node, *args, **kw): 753 if show_progress: 754 # Print the progress percentage 755 node_count_data["count"] += node_count_data["interval"] 756 node_count = node_count_data["count"] 757 node_count_max = node_count_data["max"] 758 if node_count_max > 0 and node_count <= node_count_max: 759 screen.write("\r[%3d%%] " % (node_count * 100 / node_count_max)) 760 screen.flush() 761 elif node_count_max > 0 and node_count > node_count_max: 762 screen.write("\r[100%] ") 763 screen.flush() 764 else: 765 screen.write("\r[Initial build] ") 766 screen.flush() 767 768 def delete(self, files): 769 if len(files) == 0: 770 return 771 if env["verbose"]: 772 # Utter something 773 screen.write("\rPurging %d %s from cache...\n" % (len(files), len(files) > 1 and "files" or "file")) 774 [os.remove(f) for f in files] 775 776 def file_list(self): 777 if self.path is None: 778 # Nothing to do 779 return [] 780 # Gather a list of (filename, (size, atime)) within the 781 # cache directory 782 file_stat = [(x, os.stat(x)[6:8]) for x in glob.glob(os.path.join(self.path, "*", "*"))] 783 if file_stat == []: 784 # Nothing to do 785 return [] 786 # Weight the cache files by size (assumed to be roughly 787 # proportional to the recompilation time) times an exponential 788 # decay since the ctime, and return a list with the entries 789 # (filename, size, weight). 790 current_time = time.time() 791 file_stat = [(x[0], x[1][0], (current_time - x[1][1])) for x in file_stat] 792 # Sort by the most recently accessed files (most sensible to keep) first 793 file_stat.sort(key=lambda x: x[2]) 794 # Search for the first entry where the storage limit is 795 # reached 796 sum, mark = 0, None 797 for i, x in enumerate(file_stat): 798 sum += x[1] 799 if sum > self.limit: 800 mark = i 801 break 802 if mark is None: 803 return [] 804 else: 805 return [x[0] for x in file_stat[mark:]] 806 807 def convert_size(self, size_bytes): 808 if size_bytes == 0: 809 return "0 bytes" 810 size_name = ("bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB") 811 i = int(math.floor(math.log(size_bytes, 1024))) 812 p = math.pow(1024, i) 813 s = round(size_bytes / p, 2) 814 return "%s %s" % (int(s) if i == 0 else s, size_name[i]) 815 816 def get_size(self, start_path="."): 817 total_size = 0 818 for dirpath, dirnames, filenames in os.walk(start_path): 819 for f in filenames: 820 fp = os.path.join(dirpath, f) 821 total_size += os.path.getsize(fp) 822 return total_size 823 824 def progress_finish(target, source, env): 825 with open(node_count_data["fname"], "w") as f: 826 f.write("%d\n" % node_count_data["count"]) 827 progressor.delete(progressor.file_list()) 828 829 try: 830 with open(node_count_data["fname"]) as f: 831 node_count_data["max"] = int(f.readline()) 832 except: 833 pass 834 835 cache_directory = os.environ.get("SCONS_CACHE") 836 # Simple cache pruning, attached to SCons' progress callback. Trim the 837 # cache directory to a size not larger than cache_limit. 838 cache_limit = float(os.getenv("SCONS_CACHE_LIMIT", 1024)) * 1024 * 1024 839 progressor = cache_progress(cache_directory, cache_limit) 840 Progress(progressor, interval=node_count_data["interval"]) 841 842 progress_finish_command = Command("progress_finish", [], progress_finish) 843 AlwaysBuild(progress_finish_command) 844 845 846def dump(env): 847 # Dumps latest build information for debugging purposes and external tools. 848 from json import dump 849 850 def non_serializable(obj): 851 return "<<non-serializable: %s>>" % (qualname(type(obj))) 852 853 with open(".scons_env.json", "w") as f: 854 dump(env.Dictionary(), f, indent=4, default=non_serializable) 855