import methods import os # To match other platforms STACK_SIZE = 8388608 def is_active(): return True def get_name(): return "Windows" def can_build(): if os.name == "nt": # Building natively on Windows # If VCINSTALLDIR is set in the OS environ, use traditional Pandemonium logic to set up MSVC if os.getenv("VCINSTALLDIR"): # MSVC, manual setup return True # Otherwise, let SCons find MSVC if installed, or else Mingw. # Since we're just returning True here, if there's no compiler # installed, we'll get errors when it tries to build with the # null compiler. return True if os.name == "posix": # Cross-compiling with MinGW-w64 (old MinGW32 is not supported) mingw32 = "i686-w64-mingw32-" mingw64 = "x86_64-w64-mingw32-" if os.getenv("MINGW32_PREFIX"): mingw32 = os.getenv("MINGW32_PREFIX") if os.getenv("MINGW64_PREFIX"): mingw64 = os.getenv("MINGW64_PREFIX") test = "gcc --version > /dev/null 2>&1" if os.system(mingw64 + test) == 0 or os.system(mingw32 + test) == 0: return True return False def get_opts(): from SCons.Variables import BoolVariable, EnumVariable mingw32 = "" mingw64 = "" if os.name == "posix": mingw32 = "i686-w64-mingw32-" mingw64 = "x86_64-w64-mingw32-" if os.getenv("MINGW32_PREFIX"): mingw32 = os.getenv("MINGW32_PREFIX") if os.getenv("MINGW64_PREFIX"): mingw64 = os.getenv("MINGW64_PREFIX") return [ ("mingw_prefix_32", "MinGW prefix (Win32)", mingw32), ("mingw_prefix_64", "MinGW prefix (Win64)", mingw64), # Targeted Windows version: 7 (and later), minimum supported version # XP support dropped after EOL due to missing API for IPv6 and other issues # Vista support dropped after EOL due to GH-10243 ("target_win_version", "Targeted Windows version, >= 0x0601 (Windows 7)", "0x0601"), BoolVariable("debug_symbols", "Add debugging symbols to release/release_debug builds", True), EnumVariable("windows_subsystem", "Windows subsystem", "gui", ("gui", "console")), BoolVariable("separate_debug_symbols", "Create a separate file containing debugging symbols", False), ("msvc_version", "MSVC version to use. Ignored if VCINSTALLDIR is set in shell env.", None), BoolVariable("use_mingw", "Use the Mingw compiler, even if MSVC is installed.", False), BoolVariable("use_llvm", "Use the LLVM compiler", False), BoolVariable("use_static_cpp", "Link MinGW/MSVC C++ runtime libraries statically", True), BoolVariable("use_asan", "Use address sanitizer (ASAN)", False), BoolVariable("incremental_link", "Use MSVC incremental linking. May increase or decrease build times.", False), ] def get_flags(): return [] def build_res_file(target, source, env): if env["bits"] == "32": cmdbase = env["mingw_prefix_32"] else: cmdbase = env["mingw_prefix_64"] cmdbase = cmdbase + "windres --include-dir . " import subprocess for x in range(len(source)): cmd = cmdbase + "-i " + str(source[x]) + " -o " + str(target[x]) try: out = subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE).communicate() if len(out[1]): return 1 except Exception: return 1 return 0 def setup_msvc_manual(env): """Set up env to use MSVC manually, using VCINSTALLDIR""" if env["bits"] != "default": print( """ Bits argument is not supported for MSVC compilation. Architecture depends on the Native/Cross Compile Tools Prompt/Developer Console (or Visual Studio settings) that is being used to run SCons. As a consequence, bits argument is disabled. Run scons again without bits argument (example: scons p=windows) and SCons will attempt to detect what MSVC compiler will be executed and inform you. """ ) raise SCons.Errors.UserError("Bits argument should not be used when using VCINSTALLDIR") # Force bits arg # (Actually msys2 mingw can support 64-bit, we could detect that) env["bits"] = "32" env["x86_libtheora_opt_vc"] = True # find compiler manually compiler_version_str = methods.detect_visual_c_compiler_version(env["ENV"]) print("Found MSVC compiler: " + compiler_version_str) # If building for 64bit architecture, disable assembly optimisations for 32 bit builds (theora as of writing)... vc compiler for 64bit can not compile _asm if compiler_version_str == "amd64" or compiler_version_str == "x86_amd64": env["bits"] = "64" env["x86_libtheora_opt_vc"] = False print("Compiled program architecture will be a 64 bit executable (forcing bits=64).") elif compiler_version_str == "x86" or compiler_version_str == "amd64_x86": print("Compiled program architecture will be a 32 bit executable. (forcing bits=32).") else: print( "Failed to manually detect MSVC compiler architecture version... Defaulting to 32bit executable settings (forcing bits=32). Compilation attempt will continue, but SCons can not detect for what architecture this build is compiled for. You should check your settings/compilation setup, or avoid setting VCINSTALLDIR." ) def setup_msvc_auto(env): """Set up MSVC using SCons's auto-detection logic""" # If MSVC_VERSION is set by SCons, we know MSVC is installed. # But we may want a different version or target arch. # The env may have already been set up with default MSVC tools, so # reset a few things so we can set it up with the tools we want. # (Ideally we'd decide on the tool config before configuring any # environment, and just set the env up once, but this function runs # on an existing env so this is the simplest way.) env["MSVC_SETUP_RUN"] = False # Need to set this to re-run the tool env["MSVS_VERSION"] = None env["MSVC_VERSION"] = None env["TARGET_ARCH"] = None if env["bits"] != "default": env["TARGET_ARCH"] = {"32": "x86", "64": "x86_64"}[env["bits"]] if "msvc_version" in env: env["MSVC_VERSION"] = env["msvc_version"] env.Tool("msvc") env.Tool("mssdk") # we want the MS SDK # Note: actual compiler version can be found in env['MSVC_VERSION'], e.g. "14.1" for VS2015 # Get actual target arch into bits (it may be "default" at this point): if env["TARGET_ARCH"] in ("amd64", "x86_64"): env["bits"] = "64" else: env["bits"] = "32" print("Found MSVC version %s, arch %s, bits=%s" % (env["MSVC_VERSION"], env["TARGET_ARCH"], env["bits"])) if env["TARGET_ARCH"] in ("amd64", "x86_64"): env["x86_libtheora_opt_vc"] = False def setup_mingw(env): """Set up env for use with mingw""" # Nothing to do here print("Using MinGW") def configure_msvc(env, manual_msvc_config): """Configure env to work with MSVC""" # Build type if env["target"] == "release": if env["optimize"] == "speed": # optimize for speed (default) env.Append(CCFLAGS=["/O2"]) env.Append(LINKFLAGS=["/OPT:REF"]) elif env["optimize"] == "size": # optimize for size env.Append(CCFLAGS=["/O1"]) env.Append(LINKFLAGS=["/OPT:REF"]) elif env["target"] == "release_debug": if env["optimize"] == "speed": # optimize for speed (default) env.Append(CCFLAGS=["/O2"]) env.Append(LINKFLAGS=["/OPT:REF"]) elif env["optimize"] == "size": # optimize for size env.Append(CCFLAGS=["/O1"]) env.Append(LINKFLAGS=["/OPT:REF"]) elif env["target"] == "debug": env.AppendUnique(CCFLAGS=["/Zi", "/FS", "/Od"]) env.Append(LINKFLAGS=["/DEBUG"]) if env["windows_subsystem"] == "gui": env.Append(LINKFLAGS=["/SUBSYSTEM:WINDOWS"]) else: env.Append(LINKFLAGS=["/SUBSYSTEM:CONSOLE"]) env.AppendUnique(CPPDEFINES=["WINDOWS_SUBSYSTEM_CONSOLE"]) env.Append(LINKFLAGS=["/ENTRY:mainCRTStartup"]) if env["debug_symbols"]: env.AppendUnique(CCFLAGS=["/Zi", "/FS"]) env.AppendUnique(LINKFLAGS=["/DEBUG"]) ## Compile/link flags if env["use_static_cpp"]: env.AppendUnique(CCFLAGS=["/MT"]) else: env.AppendUnique(CCFLAGS=["/MD"]) # MSVC incremental linking is broken and may _increase_ link time (GH-77968). if not env["incremental_link"]: env.Append(LINKFLAGS=["/INCREMENTAL:NO"]) env.AppendUnique(CCFLAGS=["/Gd", "/nologo"]) env.AppendUnique(CCFLAGS=["/utf-8"]) # Force to use Unicode encoding. env.AppendUnique(CXXFLAGS=["/TP"]) # assume all sources are C++ # Once it was thought that only debug builds would be too large, # but this has recently stopped being true. See the mingw function # for notes on why this shouldn't be enabled for gcc env.AppendUnique(CCFLAGS=["/bigobj"]) if manual_msvc_config: # should be automatic if SCons found it if os.getenv("WindowsSdkDir") is not None: env.Prepend(CPPPATH=[os.getenv("WindowsSdkDir") + "/Include"]) else: print("Missing environment variable: WindowsSdkDir") env.AppendUnique( CPPDEFINES=[ "WINDOWS_ENABLED", "WASAPI_ENABLED", "WINMIDI_ENABLED", "TYPED_METHOD_BIND", "WIN32", "MSVC", "WINVER=%s" % env["target_win_version"], "_WIN32_WINNT=%s" % env["target_win_version"], ] ) env.AppendUnique(CPPDEFINES=["NOMINMAX"]) # disable bogus min/max WinDef.h macros if env["bits"] == "64": env.AppendUnique(CPPDEFINES=["_WIN64"]) ## Libs LIBS = [ "winmm", "opengl32", "dsound", "kernel32", "ole32", "oleaut32", "user32", "gdi32", "IPHLPAPI", "Shlwapi", "wsock32", "Ws2_32", "shell32", "advapi32", "dinput8", "dxguid", "imm32", "bcrypt", "Avrt", "dwmapi", ] env.Append(LINKFLAGS=[p + env["LIBSUFFIX"] for p in LIBS]) if manual_msvc_config: if os.getenv("WindowsSdkDir") is not None: env.Append(LIBPATH=[os.getenv("WindowsSdkDir") + "/Lib"]) else: print("Missing environment variable: WindowsSdkDir") ## LTO if env["lto"] == "auto": # No LTO by default for MSVC, doesn't help. env["lto"] = "none" if env["lto"] != "none": if env["lto"] == "thin": print("ThinLTO is only compatible with LLVM, use `use_llvm=yes` or `lto=full`.") sys.exit(255) env.AppendUnique(CCFLAGS=["/GL"]) env.AppendUnique(ARFLAGS=["/LTCG"]) if env["progress"]: env.AppendUnique(LINKFLAGS=["/LTCG:STATUS"]) else: env.AppendUnique(LINKFLAGS=["/LTCG"]) if manual_msvc_config: env.Prepend(CPPPATH=[p for p in os.getenv("INCLUDE").split(";")]) env.Append(LIBPATH=[p for p in os.getenv("LIB").split(";")]) # Sanitizers if env["use_asan"]: env.extra_suffix += ".s" env.Append(LINKFLAGS=["/INFERASANLIBS"]) env.Append(CCFLAGS=["/fsanitize=address"]) # Incremental linking fix env["BUILDERS"]["ProgramOriginal"] = env["BUILDERS"]["Program"] env["BUILDERS"]["Program"] = methods.precious_program env.AppendUnique(LINKFLAGS=["/STACK:" + str(STACK_SIZE)]) def configure_mingw(env): # Workaround for MinGW. See: # http://www.scons.org/wiki/LongCmdLinesOnWin32 env.use_windows_spawn_fix() ## Build type if env["target"] == "release": env.Append(CCFLAGS=["-msse2"]) if env["optimize"] == "speed": # optimize for speed (default) if env["bits"] == "64": env.Append(CCFLAGS=["-O3"]) else: env.Append(CCFLAGS=["-O2"]) else: # optimize for size env.Prepend(CCFLAGS=["-Os"]) if env["debug_symbols"]: env.Prepend(CCFLAGS=["-g2"]) elif env["target"] == "release_debug": env.Append(CCFLAGS=["-O2"]) if env["debug_symbols"]: env.Prepend(CCFLAGS=["-g2"]) if env["optimize"] == "speed": # optimize for speed (default) env.Append(CCFLAGS=["-O2"]) else: # optimize for size env.Prepend(CCFLAGS=["-Os"]) elif env["target"] == "debug": env.Append(CCFLAGS=["-g3"]) # Allow big objects. It's supposed not to have drawbacks but seems to break # GCC LTO, so enabling for debug builds only (which are not built with LTO # and are the only ones with too big objects). env.Append(CCFLAGS=["-Wa,-mbig-obj"]) if env["windows_subsystem"] == "gui": env.Append(LINKFLAGS=["-Wl,--subsystem,windows"]) else: env.Append(LINKFLAGS=["-Wl,--subsystem,console"]) env.AppendUnique(CPPDEFINES=["WINDOWS_SUBSYSTEM_CONSOLE"]) ## Compiler configuration if os.name == "nt": # Force splitting libmodules.a in multiple chunks to work around # issues reaching the linker command line size limit, which also # seem to induce huge slowdown for 'ar' (GH-30892). env["split_libmodules"] = True else: env["PROGSUFFIX"] = env["PROGSUFFIX"] + ".exe" # for linux cross-compilation if env["bits"] == "default": if os.name == "nt": env["bits"] = "64" if "PROGRAMFILES(X86)" in os.environ else "32" else: # default to 64-bit on Linux env["bits"] = "64" mingw_prefix = "" if env["bits"] == "32": if env["use_static_cpp"]: env.Append(LINKFLAGS=["-static"]) env.Append(LINKFLAGS=["-static-libgcc"]) env.Append(LINKFLAGS=["-static-libstdc++"]) mingw_prefix = env["mingw_prefix_32"] else: if env["use_static_cpp"]: env.Append(LINKFLAGS=["-static"]) mingw_prefix = env["mingw_prefix_64"] if env["use_llvm"]: env["CC"] = mingw_prefix + "clang" env["CXX"] = mingw_prefix + "clang++" env["AS"] = mingw_prefix + "as" env["AR"] = mingw_prefix + "ar" env["RANLIB"] = mingw_prefix + "ranlib" else: env["CC"] = mingw_prefix + "gcc" env["CXX"] = mingw_prefix + "g++" env["AS"] = mingw_prefix + "as" env["AR"] = mingw_prefix + "gcc-ar" env["RANLIB"] = mingw_prefix + "gcc-ranlib" env["x86_libtheora_opt_gcc"] = True ## LTO if env["lto"] == "auto": # Full LTO for production with MinGW. env["lto"] = "full" if env["lto"] != "none": if env["lto"] == "thin": if not env["use_llvm"]: print("ThinLTO is only compatible with LLVM, use `use_llvm=yes` or `lto=full`.") sys.exit(255) env.Append(CCFLAGS=["-flto=thin"]) env.Append(LINKFLAGS=["-flto=thin"]) elif not env["use_llvm"] and env.GetOption("num_jobs") > 1: env.Append(CCFLAGS=["-flto"]) env.Append(LINKFLAGS=["-flto=" + str(env.GetOption("num_jobs"))]) else: env.Append(CCFLAGS=["-flto"]) env.Append(LINKFLAGS=["-flto"]) env.Append(LINKFLAGS=["-Wl,--stack," + str(STACK_SIZE)]) ## Compile flags env.Append(CCFLAGS=["-mwindows"]) env.Append(LINKFLAGS=["-Wl,--nxcompat"]) # DEP protection. Not enabling ASLR for now, Mono crashes. env.Append(CPPDEFINES=["WINDOWS_ENABLED", "WASAPI_ENABLED", "WINMIDI_ENABLED"]) env.Append(CPPDEFINES=[("WINVER", env["target_win_version"]), ("_WIN32_WINNT", env["target_win_version"])]) env.Append( LIBS=[ "mingw32", "opengl32", "dsound", "ole32", "d3d9", "winmm", "gdi32", "iphlpapi", "shlwapi", "wsock32", "ws2_32", "kernel32", "oleaut32", "dinput8", "dxguid", "ksuser", "imm32", "bcrypt", "avrt", "uuid", "dwmapi", ] ) env.Append(CPPDEFINES=["MINGW_ENABLED", ("MINGW_HAS_SECURE_API", 1)]) # resrc env.Append(BUILDERS={"RES": env.Builder(action=build_res_file, suffix=".o", src_suffix=".rc")}) def configure(env): # At this point the env has been set up with basic tools/compilers. env.Prepend(CPPPATH=["#platform/windows"]) print("Configuring for Windows: target=%s, bits=%s" % (env["target"], env["bits"])) if os.name == "nt": env["ENV"] = os.environ # this makes build less repeatable, but simplifies some things env["ENV"]["TMP"] = os.environ["TMP"] # First figure out which compiler, version, and target arch we're using if os.getenv("VCINSTALLDIR") and not env["use_mingw"]: # Manual setup of MSVC setup_msvc_manual(env) env.msvc = True manual_msvc_config = True elif env.get("MSVC_VERSION", "") and not env["use_mingw"]: setup_msvc_auto(env) env.msvc = True manual_msvc_config = False else: setup_mingw(env) env.msvc = False # Now set compiler/linker flags if env.msvc: configure_msvc(env, manual_msvc_config) else: # MinGW configure_mingw(env)