# MIT License # # Copyright The SCons Foundation # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be included # in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ MS Compilers: detect Visual Studio and/or Visual C/C++ """ import os import SCons.Errors import SCons.Util from .common import ( debug, get_output, is_win64, normalize_env, parse_output, read_reg, ) from .vc import ( find_vc_pdir, get_msvc_version_numeric, reset_installed_vcs, vswhere_freeze_env, ) # Visual Studio express version policy when unqualified version is not installed: # True: use express version for unqualified version (e.g., use 12.0Exp for 12.0) # False: do not use express version for unqualified version _VSEXPRESS_USE_VERSTR = True class VisualStudio: """ An abstract base class for trying to find installed versions of Visual Studio. """ def __init__(self, version, **kw) -> None: self.version = version self.verstr = get_msvc_version_numeric(version) self.vernum = float(self.verstr) self.is_express = True if self.verstr != self.version else False kw['vc_version'] = kw.get('vc_version', version) kw['sdk_version'] = kw.get('sdk_version', version) self.__dict__.update(kw) self._cache = {} def find_batch_file(self): vs_dir = self.get_vs_dir() if not vs_dir: debug('no vs_dir') return None batch_file = os.path.join(vs_dir, self.batch_file_path) batch_file = os.path.normpath(batch_file) if not os.path.isfile(batch_file): debug('%s not on file system', batch_file) return None return batch_file def find_vs_dir_by_vc(self, env): dir = find_vc_pdir(self.vc_version, env) if not dir: debug('no installed VC %s', self.vc_version) return None return os.path.abspath(os.path.join(dir, os.pardir)) def find_vs_dir_by_reg(self, env): root = 'Software\\' if is_win64(): root = root + 'Wow6432Node\\' for key in self.hkeys: if key=='use_dir': return self.find_vs_dir_by_vc(env) key = root + key try: comps = read_reg(key) except OSError: debug('no VS registry key %s', repr(key)) else: debug('found VS in registry: %s', comps) return comps return None def find_vs_dir(self, env): """ Can use registry or location of VC to find vs dir First try to find by registry, and if that fails find via VC dir """ vs_dir = self.find_vs_dir_by_reg(env) if not vs_dir: vs_dir = self.find_vs_dir_by_vc(env) debug('found VS in %s', str(vs_dir)) return vs_dir def find_executable(self, env): vs_dir = self.get_vs_dir(env) if not vs_dir: debug('no vs_dir (%s)', vs_dir) return None executable = os.path.join(vs_dir, self.executable_path) executable = os.path.normpath(executable) if not os.path.isfile(executable): debug('%s not on file system', executable) return None return executable def get_batch_file(self): try: return self._cache['batch_file'] except KeyError: batch_file = self.find_batch_file() self._cache['batch_file'] = batch_file return batch_file def get_executable(self, env=None): try: debug('using cache:%s', self._cache['executable']) return self._cache['executable'] except KeyError: executable = self.find_executable(env) self._cache['executable'] = executable debug('not in cache:%s', executable) return executable def get_vs_dir(self, env=None): try: return self._cache['vs_dir'] except KeyError: vs_dir = self.find_vs_dir(env) self._cache['vs_dir'] = vs_dir return vs_dir def get_supported_arch(self): try: return self._cache['supported_arch'] except KeyError: # RDEVE: for the time being use hardcoded lists # supported_arch = self.find_supported_arch() self._cache['supported_arch'] = self.supported_arch return self.supported_arch def reset(self) -> None: self._cache = {} # The list of supported Visual Studio versions we know how to detect. # # How to look for .bat file ? # - VS 2008 Express (x86): # * from registry key productdir, gives the full path to vsvarsall.bat. In # HKEY_LOCAL_MACHINE): # Software\Microsoft\VCEpress\9.0\Setup\VC\productdir # * from environmnent variable VS90COMNTOOLS: the path is then ..\..\VC # relatively to the path given by the variable. # # - VS 2008 Express (WoW6432: 32 bits on windows x64): # Software\Wow6432Node\Microsoft\VCEpress\9.0\Setup\VC\productdir # # - VS 2005 Express (x86): # * from registry key productdir, gives the full path to vsvarsall.bat. In # HKEY_LOCAL_MACHINE): # Software\Microsoft\VCEpress\8.0\Setup\VC\productdir # * from environmnent variable VS80COMNTOOLS: the path is then ..\..\VC # relatively to the path given by the variable. # # - VS 2005 Express (WoW6432: 32 bits on windows x64): does not seem to have a # productdir ? # # - VS 2003 .Net (pro edition ? x86): # * from registry key productdir. The path is then ..\Common7\Tools\ # relatively to the key. The key is in HKEY_LOCAL_MACHINE): # Software\Microsoft\VisualStudio\7.1\Setup\VC\productdir # * from environmnent variable VS71COMNTOOLS: the path is the full path to # vsvars32.bat # # - VS 98 (VS 6): # * from registry key productdir. The path is then Bin # relatively to the key. The key is in HKEY_LOCAL_MACHINE): # Software\Microsoft\VisualStudio\6.0\Setup\VC98\productdir # # The first version found in the list is the one used by default if # there are multiple versions installed. Barring good reasons to # the contrary, this means we should list versions from most recent # to oldest. Pro versions get listed before Express versions on the # assumption that, by default, you'd rather use the version you paid # good money for in preference to whatever Microsoft makes available # for free. # # If you update this list, update _VCVER and _VCVER_TO_PRODUCT_DIR in # Tool/MSCommon/vc.py, and the MSVC_VERSION documentation in Tool/msvc.xml. SupportedVSList = [ # Visual Studio 2022 VisualStudio('14.3', vc_version='14.3', sdk_version='10.0A', hkeys=[], common_tools_var='VS170COMNTOOLS', executable_path=r'Common7\IDE\devenv.com', # should be a fallback, prefer use vswhere installationPath batch_file_path=r'Common7\Tools\VsDevCmd.bat', supported_arch=['x86', 'amd64', "arm", 'arm64'], ), # Visual Studio 2019 VisualStudio('14.2', vc_version='14.2', sdk_version='10.0A', hkeys=[], common_tools_var='VS160COMNTOOLS', executable_path=r'Common7\IDE\devenv.com', # should be a fallback, prefer use vswhere installationPath batch_file_path=r'Common7\Tools\VsDevCmd.bat', supported_arch=['x86', 'amd64', "arm", 'arm64'], ), # Visual Studio 2017 VisualStudio('14.1', vc_version='14.1', sdk_version='10.0A', hkeys=[], common_tools_var='VS150COMNTOOLS', executable_path=r'Common7\IDE\devenv.com', # should be a fallback, prefer use vswhere installationPath batch_file_path=r'Common7\Tools\VsDevCmd.bat', supported_arch=['x86', 'amd64', "arm", 'arm64'], ), # Visual C++ 2017 Express Edition (for Desktop) VisualStudio('14.1Exp', vc_version='14.1', sdk_version='10.0A', hkeys=[], common_tools_var='VS150COMNTOOLS', executable_path=r'Common7\IDE\WDExpress.exe', # should be a fallback, prefer use vswhere installationPath batch_file_path=r'Common7\Tools\VsDevCmd.bat', supported_arch=['x86', 'amd64', "arm", 'arm64'], ), # Visual Studio 2015 VisualStudio('14.0', vc_version='14.0', sdk_version='10.0', hkeys=[r'Microsoft\VisualStudio\14.0\Setup\VS\ProductDir'], common_tools_var='VS140COMNTOOLS', executable_path=r'Common7\IDE\devenv.com', batch_file_path=r'Common7\Tools\vsvars32.bat', supported_arch=['x86', 'amd64', "arm"], ), # Visual C++ 2015 Express Edition (for Desktop) VisualStudio('14.0Exp', vc_version='14.0', sdk_version='10.0A', hkeys=[r'Microsoft\VisualStudio\14.0\Setup\VS\ProductDir'], common_tools_var='VS140COMNTOOLS', executable_path=r'Common7\IDE\WDExpress.exe', batch_file_path=r'Common7\Tools\vsvars32.bat', supported_arch=['x86', 'amd64', "arm"], ), # Visual Studio 2013 VisualStudio('12.0', vc_version='12.0', sdk_version='8.1A', hkeys=[r'Microsoft\VisualStudio\12.0\Setup\VS\ProductDir'], common_tools_var='VS120COMNTOOLS', executable_path=r'Common7\IDE\devenv.com', batch_file_path=r'Common7\Tools\vsvars32.bat', supported_arch=['x86', 'amd64'], ), # Visual C++ 2013 Express Edition (for Desktop) VisualStudio('12.0Exp', vc_version='12.0', sdk_version='8.1A', hkeys=[r'Microsoft\VisualStudio\12.0\Setup\VS\ProductDir'], common_tools_var='VS120COMNTOOLS', executable_path=r'Common7\IDE\WDExpress.exe', batch_file_path=r'Common7\Tools\vsvars32.bat', supported_arch=['x86', 'amd64'], ), # Visual Studio 2012 VisualStudio('11.0', sdk_version='8.0A', hkeys=[r'Microsoft\VisualStudio\11.0\Setup\VS\ProductDir'], common_tools_var='VS110COMNTOOLS', executable_path=r'Common7\IDE\devenv.com', batch_file_path=r'Common7\Tools\vsvars32.bat', supported_arch=['x86', 'amd64'], ), # Visual C++ 2012 Express Edition (for Desktop) VisualStudio('11.0Exp', vc_version='11.0', sdk_version='8.0A', hkeys=[r'Microsoft\VisualStudio\11.0\Setup\VS\ProductDir'], common_tools_var='VS110COMNTOOLS', executable_path=r'Common7\IDE\WDExpress.exe', batch_file_path=r'Common7\Tools\vsvars32.bat', supported_arch=['x86', 'amd64'], ), # Visual Studio 2010 VisualStudio('10.0', sdk_version='7.0A', hkeys=[r'Microsoft\VisualStudio\10.0\Setup\VS\ProductDir'], common_tools_var='VS100COMNTOOLS', executable_path=r'Common7\IDE\devenv.com', batch_file_path=r'Common7\Tools\vsvars32.bat', supported_arch=['x86', 'amd64'], ), # Visual C++ 2010 Express Edition VisualStudio('10.0Exp', vc_version='10.0', sdk_version='7.0A', hkeys=[r'Microsoft\VCExpress\10.0\Setup\VS\ProductDir'], common_tools_var='VS100COMNTOOLS', executable_path=r'Common7\IDE\VCExpress.exe', batch_file_path=r'Common7\Tools\vsvars32.bat', supported_arch=['x86'], ), # Visual Studio 2008 VisualStudio('9.0', sdk_version='6.0A', hkeys=[r'Microsoft\VisualStudio\9.0\Setup\VS\ProductDir'], common_tools_var='VS90COMNTOOLS', executable_path=r'Common7\IDE\devenv.com', batch_file_path=r'Common7\Tools\vsvars32.bat', supported_arch=['x86', 'amd64'], ), # Visual C++ 2008 Express Edition VisualStudio('9.0Exp', vc_version='9.0', sdk_version='6.0A', hkeys=[r'Microsoft\VCExpress\9.0\Setup\VS\ProductDir'], common_tools_var='VS90COMNTOOLS', executable_path=r'Common7\IDE\VCExpress.exe', batch_file_path=r'Common7\Tools\vsvars32.bat', supported_arch=['x86'], ), # Visual Studio 2005 VisualStudio('8.0', sdk_version='6.0A', hkeys=[r'Microsoft\VisualStudio\8.0\Setup\VS\ProductDir'], common_tools_var='VS80COMNTOOLS', executable_path=r'Common7\IDE\devenv.com', batch_file_path=r'Common7\Tools\vsvars32.bat', default_dirname='Microsoft Visual Studio 8', supported_arch=['x86', 'amd64'], ), # Visual C++ 2005 Express Edition VisualStudio('8.0Exp', vc_version='8.0Exp', sdk_version='6.0A', hkeys=[r'Microsoft\VCExpress\8.0\Setup\VS\ProductDir'], common_tools_var='VS80COMNTOOLS', executable_path=r'Common7\IDE\VCExpress.exe', batch_file_path=r'Common7\Tools\vsvars32.bat', default_dirname='Microsoft Visual Studio 8', supported_arch=['x86'], ), # Visual Studio .NET 2003 VisualStudio('7.1', sdk_version='6.0', hkeys=[r'Microsoft\VisualStudio\7.1\Setup\VS\ProductDir'], common_tools_var='VS71COMNTOOLS', executable_path=r'Common7\IDE\devenv.com', batch_file_path=r'Common7\Tools\vsvars32.bat', default_dirname='Microsoft Visual Studio .NET 2003', supported_arch=['x86'], ), # Visual Studio .NET VisualStudio('7.0', sdk_version='2003R2', hkeys=[r'Microsoft\VisualStudio\7.0\Setup\VS\ProductDir'], common_tools_var='VSCOMNTOOLS', executable_path=r'Common7\IDE\devenv.com', batch_file_path=r'Common7\Tools\vsvars32.bat', default_dirname='Microsoft Visual Studio .NET', supported_arch=['x86'], ), # Visual Studio 6.0 VisualStudio('6.0', sdk_version='2003R1', hkeys=[r'Microsoft\VisualStudio\6.0\Setup\Microsoft Visual Studio\ProductDir', 'use_dir'], common_tools_var='MSDevDir', executable_path=r'Common\MSDev98\Bin\MSDEV.COM', batch_file_path=r'Common7\Tools\vsvars32.bat', default_dirname='Microsoft Visual Studio', supported_arch=['x86'], ), ] SupportedVSMap = {} for vs in SupportedVSList: SupportedVSMap[vs.version] = vs # Finding installed versions of Visual Studio isn't cheap, because it # goes not only to the registry but also to the disk to sanity-check # that there is, in fact, a Visual Studio directory there and that the # registry entry isn't just stale. Find this information once, when # requested, and cache it. InstalledVSList = None InstalledVSMap = None def get_installed_visual_studios(env=None): global InstalledVSList global InstalledVSMap vswhere_freeze_env(env) if InstalledVSList is None: InstalledVSList = [] InstalledVSMap = {} for vs in SupportedVSList: debug('trying to find VS %s', vs.version) if vs.get_executable(env): debug('found VS %s', vs.version) InstalledVSList.append(vs) if vs.is_express and vs.verstr not in InstalledVSMap: if _VSEXPRESS_USE_VERSTR: InstalledVSMap[vs.verstr] = vs InstalledVSMap[vs.version] = vs return InstalledVSList def _get_installed_vss(env=None): get_installed_visual_studios(env) versions = list(InstalledVSMap.keys()) return versions def reset_installed_visual_studios() -> None: global InstalledVSList global InstalledVSMap debug('') InstalledVSList = None InstalledVSMap = None for vs in SupportedVSList: vs.reset() # Need to clear installed VC's as well as they are used in finding # installed VS's reset_installed_vcs() # We may be asked to update multiple construction environments with # SDK information. When doing this, we check on-disk for whether # the SDK has 'mfc' and 'atl' subdirectories. Since going to disk # is expensive, cache results by directory. #SDKEnvironmentUpdates = {} # #def set_sdk_by_directory(env, sdk_dir): # global SDKEnvironmentUpdates # try: # env_tuple_list = SDKEnvironmentUpdates[sdk_dir] # except KeyError: # env_tuple_list = [] # SDKEnvironmentUpdates[sdk_dir] = env_tuple_list # # include_path = os.path.join(sdk_dir, 'include') # mfc_path = os.path.join(include_path, 'mfc') # atl_path = os.path.join(include_path, 'atl') # # if os.path.exists(mfc_path): # env_tuple_list.append(('INCLUDE', mfc_path)) # if os.path.exists(atl_path): # env_tuple_list.append(('INCLUDE', atl_path)) # env_tuple_list.append(('INCLUDE', include_path)) # # env_tuple_list.append(('LIB', os.path.join(sdk_dir, 'lib'))) # env_tuple_list.append(('LIBPATH', os.path.join(sdk_dir, 'lib'))) # env_tuple_list.append(('PATH', os.path.join(sdk_dir, 'bin'))) # # for variable, directory in env_tuple_list: # env.PrependENVPath(variable, directory) def msvs_exists(env=None) -> bool: return len(get_installed_visual_studios(env)) > 0 def get_vs_by_version(msvs, env=None): global InstalledVSMap global SupportedVSMap debug('called') if msvs not in SupportedVSMap: msg = "Visual Studio version %s is not supported" % repr(msvs) raise SCons.Errors.UserError(msg) get_installed_visual_studios(env) vs = InstalledVSMap.get(msvs) debug('InstalledVSMap:%s', InstalledVSMap) debug('found vs:%s', vs) # Some check like this would let us provide a useful error message # if they try to set a Visual Studio version that's not installed. # However, we also want to be able to run tests (like the unit # tests) on systems that don't, or won't ever, have it installed. # It might be worth resurrecting this, with some configurable # setting that the tests can use to bypass the check. #if not vs: # msg = "Visual Studio version %s is not installed" % repr(msvs) # raise SCons.Errors.UserError, msg return vs def get_default_version(env): """Returns the default version string to use for MSVS. If no version was requested by the user through the MSVS environment variable, query all the available visual studios through get_installed_visual_studios, and take the highest one. Return ------ version: str the default version. """ if 'MSVS' not in env or not SCons.Util.is_Dict(env['MSVS']): # get all versions, and remember them for speed later versions = _get_installed_vss(env) env['MSVS'] = {'VERSIONS' : versions} else: versions = env['MSVS'].get('VERSIONS', []) if 'MSVS_VERSION' not in env: if versions: env['MSVS_VERSION'] = versions[0] #use highest version by default else: debug('WARNING: no installed versions found, ' 'using first in SupportedVSList (%s)', SupportedVSList[0].version) env['MSVS_VERSION'] = SupportedVSList[0].version env['MSVS']['VERSION'] = env['MSVS_VERSION'] return env['MSVS_VERSION'] def get_default_arch(env): """Return the default arch to use for MSVS if no version was requested by the user through the MSVS_ARCH environment variable, select x86 Return ------ arch: str """ arch = env.get('MSVS_ARCH', 'x86') msvs = InstalledVSMap.get(env['MSVS_VERSION']) if not msvs: arch = 'x86' elif arch not in msvs.get_supported_arch(): fmt = "Visual Studio version %s does not support architecture %s" raise SCons.Errors.UserError(fmt % (env['MSVS_VERSION'], arch)) return arch def merge_default_version(env) -> None: version = get_default_version(env) arch = get_default_arch(env) # TODO: refers to versions and arch which aren't defined; called nowhere. Drop? def msvs_setup_env(env) -> None: msvs = get_vs_by_version(version, env) if msvs is None: return batfilename = msvs.get_batch_file() # XXX: I think this is broken. This will silently set a bogus tool instead # of failing, but there is no other way with the current scons tool # framework if batfilename is not None: vars = ('LIB', 'LIBPATH', 'PATH', 'INCLUDE') msvs_list = get_installed_visual_studios(env) vscommonvarnames = [vs.common_tools_var for vs in msvs_list] save_ENV = env['ENV'] nenv = normalize_env(env['ENV'], ['COMSPEC'] + vscommonvarnames, force=True) try: output = get_output(batfilename, arch, env=nenv) finally: env['ENV'] = save_ENV vars = parse_output(output, vars) for k, v in vars.items(): env.PrependENVPath(k, v, delete_existing=1) def query_versions(env=None): """Query the system to get available versions of VS. A version is considered when a batfile is found.""" versions = _get_installed_vss(env) return versions # Local Variables: # tab-width:4 # indent-tabs-mode:nil # End: # vim: set expandtab tabstop=4 shiftwidth=4: