Enable SCons to autodetect Windows MSVC compiler

SCons has good compiler detection logic for MSVC compilers. Up to now,
Godot hasn't used it; it depends on passed-in OS environment vars from
a specific Visual Studio cmd.exe windows. This makes it harder to
build from a msys or cygwin shell.

This change allows SCons to autodetect Visual Studio unless it sees
VCINSTALLDIR in the os.environ. It also adds a 'msvc_version' arg for
manual specification of compiler version, and uses the existing 'bits'
arg to specify the target architecture. More detail could be added as
desired. It also adds 'use_mingw' to always use mingw, even if Visual
Studio is installed. That uses the existing mingw setup logic.

If people are used to building Godot in a Visual Studio cmd window,
this should not change the behavior in that case, since VCINSTALLDIR
will be set in those windows. (However, note that you could now unset
that var and build with any other MSVC version or target arch, even in
that window.)

I refactored much of platform/windows/detect.py during this, to
simplify and clarify the logic. I also cleaned up a bunch of env var
settings in windows/detect.py and SConstruct to use modern SCons
idioms and simplify things.

I suspect this will also enable using the Intel compiler on Windows,
though that hasn't been tested.
This commit is contained in:
Gary Oberbrunner 2018-03-02 14:51:29 -05:00
parent a5476f8562
commit d1318ee12c
3 changed files with 248 additions and 195 deletions

View File

@ -65,7 +65,7 @@ platform_arg = ARGUMENTS.get("platform", ARGUMENTS.get("p", False))
if (os.name == "posix"): if (os.name == "posix"):
pass pass
elif (os.name == "nt"): elif (os.name == "nt"):
if (os.getenv("VCINSTALLDIR") == None or platform_arg == "android" or platform_arg == "javascript"): if platform_arg == "android" or platform_arg == "javascript" or ARGUMENTS.get("use_mingw", False):
custom_tools = ['mingw'] custom_tools = ['mingw']
env_base = Environment(tools=custom_tools) env_base = Environment(tools=custom_tools)
@ -92,6 +92,7 @@ env_base.use_ptrcall = False
env_base.split_drivers = False env_base.split_drivers = False
env_base.split_modules = False env_base.split_modules = False
env_base.module_version_string = "" env_base.module_version_string = ""
env_base.msvc = False
# To decide whether to rebuild a file, use the MD5 sum only if the timestamp has changed. # To decide whether to rebuild a file, use the MD5 sum only if the timestamp has changed.
# http://scons.org/doc/production/HTML/scons-user/ch06.html#idm139837621851792 # http://scons.org/doc/production/HTML/scons-user/ch06.html#idm139837621851792
@ -240,14 +241,13 @@ sys.modules.pop('detect')
""" """
if (env_base['target'] == 'debug'): if (env_base['target'] == 'debug'):
env_base.Append(CPPFLAGS=['-DDEBUG_MEMORY_ALLOC']) env_base.Append(CPPDEFINES=['DEBUG_MEMORY_ALLOC', 'SCI_NAMESPACE'])
env_base.Append(CPPFLAGS=['-DSCI_NAMESPACE'])
if (env_base['no_editor_splash']): if (env_base['no_editor_splash']):
env_base.Append(CPPFLAGS=['-DNO_EDITOR_SPLASH']) env_base.Append(CPPDEFINES=['NO_EDITOR_SPLASH'])
if not env_base['deprecated']: if not env_base['deprecated']:
env_base.Append(CPPFLAGS=['-DDISABLE_DEPRECATED']) env_base.Append(CPPDEFINES=['DISABLE_DEPRECATED'])
env_base.platforms = {} env_base.platforms = {}
@ -329,9 +329,7 @@ if selected_platform in platform_list:
if (env["warnings"] == 'yes'): if (env["warnings"] == 'yes'):
print("WARNING: warnings=yes is deprecated; assuming warnings=all") print("WARNING: warnings=yes is deprecated; assuming warnings=all")
env.msvc = 0 if env.msvc:
if (os.name == "nt" and os.getenv("VCINSTALLDIR") and (platform_arg == "windows" or platform_arg == "uwp")): # MSVC, needs to stand out of course
env.msvc = 1
disable_nonessential_warnings = ['/wd4267', '/wd4244', '/wd4305', '/wd4800'] # Truncations, narrowing conversions... disable_nonessential_warnings = ['/wd4267', '/wd4244', '/wd4305', '/wd4800'] # Truncations, narrowing conversions...
if (env["warnings"] == 'extra'): if (env["warnings"] == 'extra'):
env.Append(CCFLAGS=['/Wall']) # Implies /W4 env.Append(CCFLAGS=['/Wall']) # Implies /W4
@ -363,7 +361,7 @@ if selected_platform in platform_list:
print("Tools can only be built with targets 'debug' and 'release_debug'.") print("Tools can only be built with targets 'debug' and 'release_debug'.")
sys.exit(255) sys.exit(255)
suffix += ".opt" suffix += ".opt"
env.Append(CCFLAGS=['-DNDEBUG']) env.Append(CPPDEFINES=['NDEBUG'])
elif (env["target"] == "release_debug"): elif (env["target"] == "release_debug"):
if env["tools"]: if env["tools"]:
@ -420,25 +418,25 @@ if selected_platform in platform_list:
env["SHLIBSUFFIX"] = suffix + env["SHLIBSUFFIX"] env["SHLIBSUFFIX"] = suffix + env["SHLIBSUFFIX"]
if (env.use_ptrcall): if (env.use_ptrcall):
env.Append(CPPFLAGS=['-DPTRCALL_ENABLED']) env.Append(CPPDEFINES=['PTRCALL_ENABLED'])
# to test 64 bits compiltion # to test 64 bits compiltion
# env.Append(CPPFLAGS=['-m64']) # env.Append(CPPFLAGS=['-m64'])
if env['tools']: if env['tools']:
env.Append(CPPFLAGS=['-DTOOLS_ENABLED']) env.Append(CPPDEFINES=['TOOLS_ENABLED'])
if env['disable_3d']: if env['disable_3d']:
env.Append(CPPFLAGS=['-D_3D_DISABLED']) env.Append(CPPDEFINES=['_3D_DISABLED'])
if env['gdscript']: if env['gdscript']:
env.Append(CPPFLAGS=['-DGDSCRIPT_ENABLED']) env.Append(CPPDEFINES=['GDSCRIPT_ENABLED'])
if env['disable_advanced_gui']: if env['disable_advanced_gui']:
env.Append(CPPFLAGS=['-DADVANCED_GUI_DISABLED']) env.Append(CPPDEFINES=['ADVANCED_GUI_DISABLED'])
if env['minizip']: if env['minizip']:
env.Append(CPPFLAGS=['-DMINIZIP_ENABLED']) env.Append(CPPDEFINES=['MINIZIP_ENABLED'])
if env['xml']: if env['xml']:
env.Append(CPPFLAGS=['-DXML_ENABLED']) env.Append(CPPDEFINES=['XML_ENABLED'])
if not env['verbose']: if not env['verbose']:
methods.no_verbose(sys, env) methods.no_verbose(sys, env)
@ -479,10 +477,7 @@ if selected_platform in platform_list:
if ("check_c_headers" in env): if ("check_c_headers" in env):
for header in env["check_c_headers"]: for header in env["check_c_headers"]:
if (conf.CheckCHeader(header[0])): if (conf.CheckCHeader(header[0])):
if (env.msvc): env.AppendUnique(CPPDEFINES=[header[1]])
env.Append(CCFLAGS=['/D' + header[1]])
else:
env.Append(CCFLAGS=['-D' + header[1]])
else: else:

View File

@ -27,6 +27,8 @@ def can_build():
def get_opts(): def get_opts():
return [ return [
('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. Only used on Windows.', False)),
] ]

View File

@ -12,22 +12,16 @@ def get_name():
def can_build(): def can_build():
if (os.name == "nt"): if (os.name == "nt"):
# Building natively on Windows # Building natively on Windows
if (os.getenv("VCINSTALLDIR")): # MSVC # If VCINSTALLDIR is set in the OS environ, use traditional Godot logic to set up MSVC
if (os.getenv("VCINSTALLDIR")): # MSVC, manual setup
return True return True
print("MSVC not detected (no VCINSTALLDIR environment variable), attempting MinGW.") # Otherwise, let SCons find MSVC if installed, or else Mingw.
mingw32 = "" # Since we're just returning True here, if there's no compiler
mingw64 = "" # installed, we'll get errors when it tries to build with the
if (os.getenv("MINGW32_PREFIX")): # null compiler.
mingw32 = os.getenv("MINGW32_PREFIX")
if (os.getenv("MINGW64_PREFIX")):
mingw64 = os.getenv("MINGW64_PREFIX")
test = "gcc --version > NUL 2>&1"
if (os.system(test) == 0 or os.system(mingw32 + test) == 0 or os.system(mingw64 + test) == 0):
return True return True
if (os.name == "posix"): if (os.name == "posix"):
@ -70,6 +64,8 @@ def get_opts():
('target_win_version', 'Targeted Windows version, >= 0x0601 (Windows 7)', '0x0601'), ('target_win_version', 'Targeted Windows version, >= 0x0601 (Windows 7)', '0x0601'),
EnumVariable('debug_symbols', 'Add debug symbols to release version', 'yes', ('yes', 'no', 'full')), EnumVariable('debug_symbols', 'Add debug symbols to release version', 'yes', ('yes', 'no', 'full')),
BoolVariable('separate_debug_symbols', 'Create a separate file with the debug symbols', False), BoolVariable('separate_debug_symbols', 'Create a separate file with the debug 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. Only used on Windows.', False)),
] ]
@ -98,15 +94,76 @@ def build_res_file(target, source, env):
return 0 return 0
def configure(env): 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")
env.Append(CPPPATH=['#platform/windows']) # Force bits arg
# (Actually msys2 mingw can support 64-bit, we could detect that)
env["bits"] = "32"
env["x86_libtheora_opt_vc"] = True
if (os.name == "nt" and os.getenv("VCINSTALLDIR")): # MSVC # find compiler manually
compiler_version_str = methods.detect_visual_c_compiler_version(env['ENV'])
print("Found MSVC compiler: " + compiler_version_str)
env['ENV']['TMP'] = os.environ['TMP'] # 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.")
## Build type 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 env.has_key('msvc_version'):
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")
pass
def configure_msvc(env, manual_msvc_config):
"""Configure env to work with MSVC"""
# Build type
if (env["target"] == "release"): if (env["target"] == "release"):
env.Append(CCFLAGS=['/O2']) env.Append(CCFLAGS=['/O2'])
@ -114,7 +171,8 @@ def configure(env):
env.Append(LINKFLAGS=['/ENTRY:mainCRTStartup']) env.Append(LINKFLAGS=['/ENTRY:mainCRTStartup'])
elif (env["target"] == "release_debug"): elif (env["target"] == "release_debug"):
env.Append(CCFLAGS=['/O2', '/DDEBUG_ENABLED']) env.Append(CCFLAGS=['/O2'])
env.AppendUnique(CPPDEFINES = ['DEBUG_ENABLED'])
env.Append(LINKFLAGS=['/SUBSYSTEM:CONSOLE']) env.Append(LINKFLAGS=['/SUBSYSTEM:CONSOLE'])
elif (env["target"] == "debug_release"): elif (env["target"] == "debug_release"):
@ -124,88 +182,56 @@ def configure(env):
env.Append(LINKFLAGS=['/ENTRY:mainCRTStartup']) env.Append(LINKFLAGS=['/ENTRY:mainCRTStartup'])
elif (env["target"] == "debug"): elif (env["target"] == "debug"):
env.Append(CCFLAGS=['/Z7', '/DDEBUG_ENABLED', '/DDEBUG_MEMORY_ENABLED', '/DD3D_DEBUG_INFO', '/Od', '/EHsc']) env.AppendUnique(CCFLAGS=['/Z7', '/Od', '/EHsc'])
env.AppendUnique(CPPDEFINES = ['DEBUG_ENABLED', 'DEBUG_MEMORY_ENABLED',
'D3D_DEBUG_INFO'])
env.Append(LINKFLAGS=['/SUBSYSTEM:CONSOLE']) env.Append(LINKFLAGS=['/SUBSYSTEM:CONSOLE'])
env.Append(LINKFLAGS=['/DEBUG']) env.Append(LINKFLAGS=['/DEBUG'])
## Architecture ## Compile/link flags
# Note: this detection/override code from here onward should be here instead of in SConstruct because it's platform and compiler specific (MSVC/Windows) env.AppendUnique(CCFLAGS=['/MT', '/Gd', '/GR', '/nologo'])
if (env["bits"] != "default"): env.AppendUnique(CXXFLAGS=['/TP']) # assume all sources are C++
print("Error: bits argument is disabled for MSVC") if manual_msvc_config: # should be automatic if SCons found it
print(""" env.Append(CPPPATH=[os.getenv("WindowsSdkDir") + "/Include"])
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.
""")
sys.exit()
# Forcing bits argument because MSVC does not have a flag to set this through SCons... it's different compilers (cl.exe's) called from the proper command prompt env.AppendUnique(CPPDEFINES = ['WINDOWS_ENABLED', 'OPENGL_ENABLED',
# that decide the architecture that is build for. Scons can only detect the os.getenviron (because vsvarsall.bat sets a lot of stuff for cl.exe to work with) 'RTAUDIO_ENABLED', 'WASAPI_ENABLED',
env["bits"] = "32" 'TYPED_METHOD_BIND', 'WIN32', 'MSVC',
env["x86_libtheora_opt_vc"] = True {'WINVER' : '$target_win_version',
'_WIN32_WINNT': '$target_win_version'}])
## Compiler configuration
env['ENV'] = os.environ
# This detection function needs the tools env (that is env['ENV'], not SCons's env), and that is why it's this far below in the code
compiler_version_str = methods.detect_visual_c_compiler_version(env['ENV'])
print("Detected 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 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.")
## Compile flags
env.Append(CCFLAGS=['/MT', '/Gd', '/GR', '/nologo'])
env.Append(CXXFLAGS=['/TP'])
env.Append(CPPFLAGS=['/DMSVC', '/GR', ])
env.Append(CCFLAGS=['/I' + os.getenv("WindowsSdkDir") + "/Include"])
env.Append(CCFLAGS=['/DWINDOWS_ENABLED'])
env.Append(CCFLAGS=['/DOPENGL_ENABLED'])
env.Append(CCFLAGS=['/DRTAUDIO_ENABLED'])
env.Append(CCFLAGS=['/DWASAPI_ENABLED'])
env.Append(CCFLAGS=['/DTYPED_METHOD_BIND'])
env.Append(CCFLAGS=['/DWIN32'])
env.Append(CCFLAGS=['/DWINVER=%s' % env['target_win_version'], '/D_WIN32_WINNT=%s' % env['target_win_version']])
if env["bits"] == "64": if env["bits"] == "64":
env.Append(CCFLAGS=['/D_WIN64']) env.AppendUnique(CPPDEFINES=['_WIN64'])
LIBS = ['winmm', 'opengl32', 'dsound', 'kernel32', 'ole32', 'oleaut32', 'user32', 'gdi32', 'IPHLPAPI', 'Shlwapi', 'wsock32', 'Ws2_32', 'shell32', 'advapi32', 'dinput8', 'dxguid', 'imm32', 'bcrypt'] ## Libs
LIBS = ['winmm', 'opengl32', 'dsound', 'kernel32', 'ole32', 'oleaut32',
'user32', 'gdi32', 'IPHLPAPI', 'Shlwapi', 'wsock32', 'Ws2_32',
'shell32', 'advapi32', 'dinput8', 'dxguid', 'imm32', 'bcrypt']
env.Append(LINKFLAGS=[p + env["LIBSUFFIX"] for p in LIBS]) env.Append(LINKFLAGS=[p + env["LIBSUFFIX"] for p in LIBS])
if manual_msvc_config:
env.Append(LIBPATH=[os.getenv("WindowsSdkDir") + "/Lib"]) env.Append(LIBPATH=[os.getenv("WindowsSdkDir") + "/Lib"])
if (os.getenv("VCINSTALLDIR")): ## LTO
VC_PATH = os.getenv("VCINSTALLDIR")
else:
VC_PATH = ""
if (env["use_lto"]): if (env["use_lto"]):
env.Append(CCFLAGS=['/GL']) env.AppendUnique(CCFLAGS=['/GL'])
env.Append(ARFLAGS=['/LTCG']) env.AppendUnique(ARFLAGS=['/LTCG'])
if env["progress"]: if env["progress"]:
env.Append(LINKFLAGS=['/LTCG:STATUS']) env.AppendUnique(LINKFLAGS=['/LTCG:STATUS'])
else: else:
env.Append(LINKFLAGS=['/LTCG']) env.AppendUnique(LINKFLAGS=['/LTCG'])
env.Append(CCFLAGS=["/I" + p for p in os.getenv("INCLUDE").split(";")]) if manual_msvc_config:
env.Append(CPPPATH=[p for p in os.getenv("INCLUDE").split(";")])
env.Append(LIBPATH=[p for p in os.getenv("LIB").split(";")]) env.Append(LIBPATH=[p for p in os.getenv("LIB").split(";")])
# Incremental linking fix # Incremental linking fix
env['BUILDERS']['ProgramOriginal'] = env['BUILDERS']['Program'] env['BUILDERS']['ProgramOriginal'] = env['BUILDERS']['Program']
env['BUILDERS']['Program'] = methods.precious_program env['BUILDERS']['Program'] = methods.precious_program
else: # MinGW def configure_mingw(env):
# Workaround for MinGW. See: # Workaround for MinGW. See:
# http://www.scons.org/wiki/LongCmdLinesOnWin32 # http://www.scons.org/wiki/LongCmdLinesOnWin32
env.use_windows_spawn_fix() env.use_windows_spawn_fix()
@ -287,3 +313,33 @@ def configure(env):
# resrc # resrc
env.Append(BUILDERS={'RES': env.Builder(action=build_res_file, suffix='.o', src_suffix='.rc')}) 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.Append(CPPPATH=['#platform/windows'])
print("Configuring for Windows: target=%s, bits=%s" % (env['target'], env['bits']))
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"):
# Manual setup of MSVC
setup_msvc_manual(env)
env.msvc = True
manual_msvc_config = True
elif env.get('MSVC_VERSION', ''):
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)