diff --git a/CHANGES.txt b/CHANGES.txt index 9076faa536..e56e8e3ed0 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -47,6 +47,60 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER msvs. Moving any of these tools that used relative imports to the scons site tools folder would fail on import (i.e., the relative import paths become invalid when moved). + - The detection of the msvc compiler executable (cl.exe) has been modified. The + compiler detection no longer considers the host os environment path. In addition, + existence of the msvc compiler executable is checked in the detection dictionary + and the scons ENV path before the detection dictionary is merged into the scons + ENV. Different warnings are produced when the msvc compiler is not detected in the + detection dictionary based on whether or not an msvc compiler was detected in the + scons ENV path (i.e., already exists in the user's ENV path prior to detection). + - For msvc version specifications without an 'Exp' suffix, an express installation + is used when no other edition is detected for the msvc version. + - VS2015 Express (14.1Exp) may not have been detected. The registry keys written + during installation appear to be different than for earlier express versions. + VS2015 Express should be correctly detected now. + - VS2015 Express (14.1Exp) does not support the sdk version argument. VS2015 Express + does not support the store argument for target architectures other than x86. + Script argument validation now takes into account these restrictions. + - VS2015 BuildTools (14.0) does not support the sdk version argument and does not + support the store argument. Script argument validation now takes into account + these restrictions. + - The Windows SDK for Windows 7 and .NET Framework 4" (SDK 7.1) populates the + registry keys in a manner in which the msvc detection would report that VS2010 + (10.0) is installed when only the SDK was installed. The installed files are + intended to be used via the sdk batch file setenv.cmd. The installed msvc + batch files will fail. The msvc detection logic now ignores SDK-only VS2010 + installations. Similar protection is implemented for the sdk-only installs that + populate the installation folder and registry keys for VS2008 (9.0), if necessary. + - For VS2005 (8.0) to VS2015 (14.0), vsvarsall.bat is employed to dispatch to a + dependent batch file when configuring the msvc environment. Previously, only the + existence of the compiler executable was verified. In certain installations, the + dependent batch file (e.g., vcvars64.bat) may not exist while the compiler + executable does exist resulting in build failures. The existence of vcvarsall.bat, + the dependent batch file, and the compiler executable are now validated. + - MSVC configuration data specific to versions VS2005 (8.0) to VS2008 (9.0) was added + as the dependent batch files have different names than the batch file names used + for VS2010 (10.0) and later. The VS2008 (9.0) Visual C++ For Python installation + is handled as a special case as the dependent batch files are: (a) not used and (b) + in different locations. + - When VS2008 (9.0) Visual C++ For Python is installed using the ALLUSERS=1 option + (i.e., msiexec /i VCForPython27.msi ALLUSERS=1), the registry keys are written to + HKEY_LOCAL_MACHINE rather than HKEY_CURRENT_USER. An entry was added to query the + Visual C++ For Python keys in HKLM following the HKCU query. + - The registry detection of VS2015 (14.0), and earlier, is now cached at runtime and + is only evaluated once for each msvc version. + - The vswhere detection of VS2017 (14.1), and later, is now cached at runtime and is + only evaluated once for each vswhere executable. The detected msvc instances for + all vswhere executables are merged (i.e., detected msvc instances are the union + of the results from all vswhere executables). There is a single invocation for + each vswhere executable that processes the output for all installations. Prior + implementations called the vswhere executable for each supported msvc version. + - Previously, the installed vcs list was constructed once and cached at runtime. If + a vswhere executable was specified via the construction variable VSWHERE and found + additional msvc installations, the new installations were not reflected in the + installed vcs list. During runtime, if a user-specified vswhere executable finds + new msvc installations, internal runtime caches are cleared and the installed vcs + list is reconstructed. From Vitaly Cheptsov: - Fix race condition in `Mkdir` which can happen when two `SConscript` diff --git a/RELEASE.txt b/RELEASE.txt index c0925bb5e0..f9684eaee7 100644 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -59,6 +59,14 @@ CHANGED/ENHANCED EXISTING FUNCTIONALITY batch file exists but an individual msvc toolset may not support the host/target architecture combination. For example, when using VS2022 on arm64, the arm64 native tools are only installed for the 14.3x toolsets. +- MSVC: When the msvc compiler executable is not found during setup of the msvc + environment, the warning message issued takes into account whether or not a + possibly erroneous compiler executable was already present in the scons environment + path. +- MSVC: For msvc version specifications without an 'Exp' suffix, an express + installation is used when no other edition is detected for the msvc version. + This was the behavior for VS2008 (9.0) through VS2015 (14.0). This behavior + was extended to VS2017 (14.1) and VS2008 (8.0). - Extend range of recognized Java versions to 20. - Builder calls (like Program()) now accept pathlib objects in source lists. - The Help() function now takes an additional keyword argument keep_local: @@ -102,6 +110,26 @@ FIXES - MSVC: Erroneous construction of the installed msvc list (as described above) caused an index error in the msvc support code. An explicit check was added to prevent indexing into an empty list. Fixes #4312. +- MSVC: The search for the msvc compiler executable (cl.exe) no longer inspects the + OS system path in certain situations when setting up the msvc environment. +- MSVC: VS2015 Express (14.1Exp) may not have been detected. VS2015 Express should + be correctly detected. +- MSVC: VS2010 (10.0) could be inadvertently detected due to an sdk-only install + of Windows SDK 7.1. An sdk-only install of VS2010 is ignored as the msvc batch + files will fail. The installed files are intended to be used in conjunction with + the SDK batch file. Similar protection was added for VS2008 (9.0). +- MSVC: For VS2005 (8.0) to VS2015 (14.0), detection of installed files was expanded + to include the primary msvc batch file, dependent msvc batch file, and compiler + executable. In certain installations, the dependent msvc batch file may not exist + while the compiler executable does exists resulting in a build failure. +- MSVC: VS2008 (9.0) Visual C++ For Python was not detected when installed using the + ALLUSERS=1 option (i.e., msiexec /i VCForPython27.msi ALLUSERS=1). When installed + for all users, VS2008 (9.0) Visual C++ For Python is now correctly detected. +- MSVC: The installed vcs list was constructed and cached during the initial + invocation. If a vswhere executable was specified via the construction variable + VSWHERE and found additional msvc installations, the new installations were not + reflected in the installed vcs list. Now, when a user-specified vswhere executable + finds new msvc installations, the installed vcs list is reconstructed. - MSCommon: Test SConfTests.py would fail when mscommon debugging was enabled via the MSVC_MSCOMMON_DEBUG environment variable. The mscommon logging filter class registered with the python logging module was refactored to prevent test failure. @@ -126,6 +154,12 @@ IMPROVEMENTS msvs. Moving any of these tools that used relative imports to the scons site tools folder would fail on import (i.e., the relative import paths become invalid when moved). +- MSVC: VS2015 Express (14.1Exp) does not support the sdk version argument. VS2015 + Express does not support the store argument for target architectures other than + x86. Script argument validation now takes into account these restrictions. +- MSVC: VS2015 BuildTools (14.0) does not support the sdk version argument and + does not support the store argument. Script argument validation now takes into + account these restrictions. PACKAGING --------- diff --git a/SCons/Tool/MSCommon/MSVC/Config.py b/SCons/Tool/MSCommon/MSVC/Config.py index 7c0f1fe6ff..e858dbaf26 100644 --- a/SCons/Tool/MSCommon/MSVC/Config.py +++ b/SCons/Tool/MSCommon/MSVC/Config.py @@ -36,6 +36,10 @@ from . import Dispatcher Dispatcher.register_modulename(__name__) +# MSVC 9.0 preferred query order: +# True: VCForPython, VisualStudio +# False: VisualStudio, VCForPython +_VC90_Prefer_VCForPython = True UNDEFINED = object() @@ -84,7 +88,6 @@ 'vc_runtime', 'vc_runtime_numeric', 'vc_runtime_alias_list', - 'vc_runtime_vsdef_list', ]) MSVC_RUNTIME_DEFINITION_LIST = [] @@ -92,6 +95,8 @@ MSVC_RUNTIME_INTERNAL = {} MSVC_RUNTIME_EXTERNAL = {} +MSVC_RUNTIME_VSPRODUCTDEF_LIST_MAP = {} + for vc_runtime, vc_runtime_numeric, vc_runtime_alias_list in [ ('140', 140, ['ucrt']), ('120', 120, ['msvcr120']), @@ -106,8 +111,7 @@ vc_runtime_def = MSVC_RUNTIME_DEFINITION( vc_runtime = vc_runtime, vc_runtime_numeric = vc_runtime_numeric, - vc_runtime_alias_list = vc_runtime_alias_list, - vc_runtime_vsdef_list = [], + vc_runtime_alias_list = tuple(vc_runtime_alias_list), ) MSVC_RUNTIME_DEFINITION_LIST.append(vc_runtime_def) @@ -118,7 +122,9 @@ for vc_runtime_alias in vc_runtime_alias_list: MSVC_RUNTIME_EXTERNAL[vc_runtime_alias] = vc_runtime_def -MSVC_BUILDTOOLS_DEFINITION = namedtuple('MSVCBuildtools', [ + MSVC_RUNTIME_VSPRODUCTDEF_LIST_MAP[vc_runtime_def] = [] + +MSVC_BUILDTOOLS_DEFINITION = namedtuple('MSVCBuildTools', [ 'vc_buildtools', 'vc_buildtools_numeric', 'vc_version', @@ -185,8 +191,20 @@ MSVC_SDK_VERSIONS = set() -VISUALSTUDIO_DEFINITION = namedtuple('VisualStudioDefinition', [ +# vswhere: +# map vs major version to vc version (no suffix) +# build set of supported vc versions (including suffix) +VSWHERE_VSMAJOR_TO_VCVERSION = {} + +VSWHERE_SUPPORTED_VCVER = set() +VSWHERE_SUPPORTED_PRODUCTS = set() + +REGISTRY_SUPPORTED_VCVER = set() +REGISTRY_SUPPORTED_PRODUCTS = set() + +MSVS_PRODUCT_DEFINITION = namedtuple('MSVSProductDefinition', [ 'vs_product', + 'vs_product_numeric', 'vs_product_alias_list', 'vs_version', 'vs_version_major', @@ -200,14 +218,14 @@ 'vc_buildtools_all', ]) -VISUALSTUDIO_DEFINITION_LIST = [] +MSVS_PRODUCT_DEFINITION_LIST = [] VS_PRODUCT_ALIAS = { '1998': ['6'] } # vs_envvar: VisualStudioVersion defined in environment for MSVS 2012 and later -# MSVS 2010 and earlier cl_version -> vs_def is a 1:1 mapping +# MSVS 2010 and earlier cl_version -> vs_product_def is a 1:1 mapping # SDK attached to product or buildtools? for vs_product, vs_version, vs_envvar, vs_express, vs_lookup, vc_sdk, vc_ucrt, vc_uwp, vc_buildtools_all in [ ('2022', '17.0', True, False, 'vswhere' , ['10.0', '8.1'], ['10'], 'uwp', ['v143', 'v142', 'v141', 'v140']), @@ -224,57 +242,272 @@ ('1998', '6.0', False, False, 'registry', None, None, None, ['v60']), ]: + vs_product_numeric = int(vs_product) + vs_version_major = vs_version.split('.')[0] vc_buildtools_def = MSVC_BUILDTOOLS_INTERNAL[vc_buildtools_all[0]] - vs_def = VISUALSTUDIO_DEFINITION( + if vs_product not in VS_PRODUCT_ALIAS: + vs_product_alias_list = [] + else: + vs_product_alias_list = [ + vs_product_alias + for vs_product_alias in VS_PRODUCT_ALIAS[vs_product] + if vs_product_alias + ] + + vs_product_def = MSVS_PRODUCT_DEFINITION( vs_product = vs_product, - vs_product_alias_list = [], + vs_product_numeric = vs_product_numeric, + vs_product_alias_list = tuple(vs_product_alias_list), vs_version = vs_version, vs_version_major = vs_version_major, vs_envvar = vs_envvar, vs_express = vs_express, vs_lookup = vs_lookup, - vc_sdk_versions = vc_sdk, - vc_ucrt_versions = vc_ucrt, + vc_sdk_versions = tuple(vc_sdk) if vc_sdk else vc_sdk, + vc_ucrt_versions = tuple(vc_ucrt) if vc_ucrt else vc_ucrt, vc_uwp = vc_uwp, vc_buildtools_def = vc_buildtools_def, - vc_buildtools_all = vc_buildtools_all, + vc_buildtools_all = tuple(vc_buildtools_all), ) - VISUALSTUDIO_DEFINITION_LIST.append(vs_def) + MSVS_PRODUCT_DEFINITION_LIST.append(vs_product_def) - vc_buildtools_def.vc_runtime_def.vc_runtime_vsdef_list.append(vs_def) + MSVC_RUNTIME_VSPRODUCTDEF_LIST_MAP[vc_buildtools_def.vc_runtime_def].append(vs_product_def) vc_version = vc_buildtools_def.vc_version - MSVS_VERSION_INTERNAL[vs_product] = vs_def - MSVS_VERSION_EXTERNAL[vs_product] = vs_def - MSVS_VERSION_EXTERNAL[vs_version] = vs_def + MSVS_VERSION_INTERNAL[vs_product] = vs_product_def + MSVS_VERSION_EXTERNAL[vs_product] = vs_product_def + MSVS_VERSION_EXTERNAL[vs_version] = vs_product_def - MSVC_VERSION_INTERNAL[vc_version] = vs_def - MSVC_VERSION_EXTERNAL[vs_product] = vs_def - MSVC_VERSION_EXTERNAL[vc_version] = vs_def - MSVC_VERSION_EXTERNAL[vc_buildtools_def.vc_buildtools] = vs_def + MSVC_VERSION_INTERNAL[vc_version] = vs_product_def + MSVC_VERSION_EXTERNAL[vs_product] = vs_product_def + MSVC_VERSION_EXTERNAL[vc_version] = vs_product_def + MSVC_VERSION_EXTERNAL[vc_buildtools_def.vc_buildtools] = vs_product_def - if vs_product in VS_PRODUCT_ALIAS: - for vs_product_alias in VS_PRODUCT_ALIAS[vs_product]: - vs_def.vs_product_alias_list.append(vs_product_alias) - MSVS_VERSION_EXTERNAL[vs_product_alias] = vs_def - MSVC_VERSION_EXTERNAL[vs_product_alias] = vs_def + for vs_product_alias in vs_product_alias_list: + MSVS_VERSION_EXTERNAL[vs_product_alias] = vs_product_def + MSVC_VERSION_EXTERNAL[vs_product_alias] = vs_product_def - MSVC_VERSION_SUFFIX[vc_version] = vs_def + MSVC_VERSION_SUFFIX[vc_version] = vs_product_def if vs_express: - MSVC_VERSION_SUFFIX[vc_version + 'Exp'] = vs_def + MSVC_VERSION_SUFFIX[vc_version + 'Exp'] = vs_product_def - MSVS_VERSION_MAJOR_MAP[vs_version_major] = vs_def + MSVS_VERSION_MAJOR_MAP[vs_version_major] = vs_product_def - CL_VERSION_MAP[vc_buildtools_def.cl_version] = vs_def + CL_VERSION_MAP[vc_buildtools_def.cl_version] = vs_product_def if vc_sdk: MSVC_SDK_VERSIONS.update(vc_sdk) + if vs_lookup == 'vswhere': + VSWHERE_VSMAJOR_TO_VCVERSION[vs_version_major] = vc_version + VSWHERE_SUPPORTED_VCVER.add(vc_version) + if vs_express: + VSWHERE_SUPPORTED_VCVER.add(vc_version + 'Exp') + VSWHERE_SUPPORTED_PRODUCTS.add(vs_product) + elif vs_lookup == 'registry': + REGISTRY_SUPPORTED_VCVER.add(vc_version) + if vs_express: + REGISTRY_SUPPORTED_VCVER.add(vc_version + 'Exp') + REGISTRY_SUPPORTED_PRODUCTS.add(vs_product) + +MSVS_VERSION_SYMBOLS = list(MSVC_VERSION_EXTERNAL.keys()) +MSVC_VERSIONS = list(MSVC_VERSION_SUFFIX.keys()) + +# MSVS Channel: Release, Preview, Any + +MSVS_CHANNEL_DEFINITION = namedtuple('MSVSChannel', [ + 'vs_channel_id', + 'vs_channel_rank', + 'vs_channel_suffix', + 'vs_channel_symbols', +]) + +MSVS_CHANNEL_DEFINITION_LIST = [] + +MSVS_CHANNEL_INTERNAL = {} +MSVS_CHANNEL_EXTERNAL = {} +MSVS_CHANNEL_SYMBOLS = [] + +for vs_channel_rank, vs_channel_suffix, vs_channel_symbols in ( + (1, 'Rel', ['Release']), + (2, 'Pre', ['Preview']), + (3, 'Any', ['Any']) +): + + vs_channel_id = vs_channel_symbols[0] + + if vs_channel_suffix not in vs_channel_symbols: + vs_channel_symbols = vs_channel_symbols + [vs_channel_suffix] + + vs_channel_def = MSVS_CHANNEL_DEFINITION( + vs_channel_id=vs_channel_id, + vs_channel_rank=vs_channel_rank, + vs_channel_suffix=vs_channel_suffix, + vs_channel_symbols=tuple(vs_channel_symbols), + ) + + MSVS_CHANNEL_DEFINITION_LIST.append(vs_channel_def) + + MSVS_CHANNEL_INTERNAL[vs_channel_id] = vs_channel_def + MSVS_CHANNEL_INTERNAL[vs_channel_rank] = vs_channel_def + MSVS_CHANNEL_INTERNAL[vs_channel_suffix] = vs_channel_def + + for symbol in vs_channel_symbols: + MSVS_CHANNEL_SYMBOLS.append(symbol) + for sym in [symbol, symbol.lower(), symbol.upper()]: + MSVS_CHANNEL_EXTERNAL[sym] = vs_channel_def + +MSVS_CHANNEL_RELEASE = MSVS_CHANNEL_INTERNAL['Release'] +MSVS_CHANNEL_PREVIEW = MSVS_CHANNEL_INTERNAL['Preview'] +MSVS_CHANNEL_ANY = MSVS_CHANNEL_INTERNAL['Any'] + +MSVS_CHANNEL_MEMBERLISTS = { + MSVS_CHANNEL_RELEASE: [MSVS_CHANNEL_RELEASE, MSVS_CHANNEL_ANY], + MSVS_CHANNEL_PREVIEW: [MSVS_CHANNEL_PREVIEW, MSVS_CHANNEL_ANY], +} + +# VS Component Id + +MSVS_COMPONENTID_DEFINITION = namedtuple('MSVSComponentId', [ + 'vs_component_id', + 'vs_component_suffix', + 'vs_component_isexpress', + 'vs_component_symbols', +]) + +MSVS_COMPONENTID_DEFINITION_LIST = [] + +MSVS_COMPONENTID_INTERNAL = {} +MSVS_COMPONENTID_EXTERNAL = {} +MSVS_COMPONENTID_SYMBOLS = [] + +for vs_component_isexpress, vs_component_suffix, vs_component_symbols in ( + (False, 'Ent', ['Enterprise']), + (False, 'Pro', ['Professional']), + (False, 'Com', ['Community']), + (False, 'Dev', ['Develop']), + (False, 'Py', ['Python', 'VCForPython']), + (False, 'Cmd', ['CmdLine', 'CommandLine']), + (False, 'BT', ['BuildTools']), + (True, 'Exp', ['Express', 'WDExpress']), +): + + vs_component_id = vs_component_symbols[0] + + if vs_component_suffix not in vs_component_symbols: + vs_component_symbols = vs_component_symbols + [vs_component_suffix] + + vs_componentid_def = MSVS_COMPONENTID_DEFINITION( + vs_component_id=vs_component_id, + vs_component_suffix=vs_component_suffix, + vs_component_isexpress=vs_component_isexpress, + vs_component_symbols=tuple(vs_component_symbols), + ) + + MSVS_COMPONENTID_DEFINITION_LIST.append(vs_componentid_def) + + MSVS_COMPONENTID_INTERNAL[vs_component_id] = vs_componentid_def + MSVS_COMPONENTID_EXTERNAL[vs_component_id] = vs_componentid_def + + for symbol in vs_component_symbols: + MSVS_COMPONENTID_SYMBOLS.append(symbol) + for sym in [symbol, symbol.lower(), symbol.upper()]: + MSVS_COMPONENTID_EXTERNAL[sym] = vs_componentid_def + +MSVS_COMPONENTID_ENTERPRISE = MSVS_COMPONENTID_INTERNAL['Enterprise'] +MSVS_COMPONENTID_PROFESSIONAL = MSVS_COMPONENTID_INTERNAL['Professional'] +MSVS_COMPONENTID_COMMUNITY = MSVS_COMPONENTID_INTERNAL['Community'] +MSVS_COMPONENTID_DEVELOP = MSVS_COMPONENTID_INTERNAL['Develop'] +MSVS_COMPONENTID_PYTHON = MSVS_COMPONENTID_INTERNAL['Python'] +MSVS_COMPONENTID_CMDLINE = MSVS_COMPONENTID_INTERNAL['CmdLine'] +MSVS_COMPONENTID_BUILDTOOLS = MSVS_COMPONENTID_INTERNAL['BuildTools'] +MSVS_COMPONENTID_EXPRESS = MSVS_COMPONENTID_INTERNAL['Express'] + +# VS Component Id + +MSVS_COMPONENT_DEFINITION = namedtuple('MSVSComponent', [ + 'vs_componentid_def', + 'vs_lookup', + 'vs_component_rank', +]) + +MSVS_COMPONENT_DEFINITION_LIST = [] + +MSVS_COMPONENT_INTERNAL = {} + +VSWHERE_COMPONENT_INTERNAL = {} +VSWHERE_COMPONENT_SYMBOLS = [] + +REGISTRY_COMPONENT_INTERNAL = {} +REGISTRY_COMPONENT_SYMBOLS = [] + +if _VC90_Prefer_VCForPython: + _REG_VPY = 140 + _REG_DEV = 130 +else: + _REG_VPY = 130 + _REG_DEV = 140 + +for vs_lookup, vs_component_rank, vs_componentid in ( + + ('vswhere', 240, 'Enterprise'), + ('vswhere', 230, 'Professional'), + ('vswhere', 220, 'Community'), + ('vswhere', 210, 'BuildTools'), + ('vswhere', 200, 'Express'), + + ('registry', 170, 'Enterprise'), + ('registry', 160, 'Professional'), + ('registry', 150, 'Community'), + ('registry', _REG_DEV, 'Develop'), + ('registry', _REG_VPY, 'Python'), + ('registry', 120, 'CmdLine'), + ('registry', 110, 'BuildTools'), + ('registry', 100, 'Express'), + +): + + vs_componentid_def = MSVS_COMPONENTID_INTERNAL[vs_componentid] + + vs_component_def = MSVS_COMPONENT_DEFINITION( + vs_componentid_def=vs_componentid_def, + vs_lookup=vs_lookup, + vs_component_rank=vs_component_rank, + ) + + MSVS_COMPONENT_DEFINITION_LIST.append(vs_component_def) + + if vs_lookup == 'vswhere': + for symbol in vs_componentid_def.vs_component_symbols: + MSVS_COMPONENT_INTERNAL[(vs_lookup, symbol)] = vs_component_def + VSWHERE_COMPONENT_INTERNAL[symbol] = vs_component_def + VSWHERE_COMPONENT_SYMBOLS.append(symbol) + elif vs_lookup == 'registry': + for symbol in vs_componentid_def.vs_component_symbols: + MSVS_COMPONENT_INTERNAL[(vs_lookup, symbol)] = vs_component_def + REGISTRY_COMPONENT_INTERNAL[symbol] = vs_component_def + REGISTRY_COMPONENT_SYMBOLS.append(symbol) + +VSWHERE_COMPONENT_ENTERPRISE = VSWHERE_COMPONENT_INTERNAL['Ent'] +VSWHERE_COMPONENT_PROFESSIONAL = VSWHERE_COMPONENT_INTERNAL['Pro'] +VSWHERE_COMPONENT_COMMUNITY = VSWHERE_COMPONENT_INTERNAL['Com'] +VSWHERE_COMPONENT_BUILDTOOLS = VSWHERE_COMPONENT_INTERNAL['BT'] +VSWHERE_COMPONENT_EXPRESS = VSWHERE_COMPONENT_INTERNAL['Exp'] + +REGISTRY_COMPONENT_ENTERPRISE = REGISTRY_COMPONENT_INTERNAL['Ent'] +REGISTRY_COMPONENT_PROFESSIONAL = REGISTRY_COMPONENT_INTERNAL['Pro'] +REGISTRY_COMPONENT_COMMUNITY = REGISTRY_COMPONENT_INTERNAL['Com'] +REGISTRY_COMPONENT_DEVELOP = REGISTRY_COMPONENT_INTERNAL['Dev'] +REGISTRY_COMPONENT_PYTHON = REGISTRY_COMPONENT_INTERNAL['Py'] +REGISTRY_COMPONENT_CMDLINE = REGISTRY_COMPONENT_INTERNAL['Cmd'] +REGISTRY_COMPONENT_BUILDTOOLS = REGISTRY_COMPONENT_INTERNAL['BT'] +REGISTRY_COMPONENT_EXPRESS = REGISTRY_COMPONENT_INTERNAL['Exp'] + # EXPERIMENTAL: msvc version/toolset search lists # # VS2017 example: @@ -289,32 +522,33 @@ MSVC_VERSION_TOOLSET_SEARCH_MAP = {} # Pass 1: Build defaults lists and setup express versions -for vs_def in VISUALSTUDIO_DEFINITION_LIST: - if not vs_def.vc_buildtools_def.vc_istoolset: +for vs_product_def in MSVS_PRODUCT_DEFINITION_LIST: + if not vs_product_def.vc_buildtools_def.vc_istoolset: continue - version_key = vs_def.vc_buildtools_def.vc_version + version_key = vs_product_def.vc_buildtools_def.vc_version MSVC_VERSION_TOOLSET_DEFAULTS_MAP[version_key] = [version_key] MSVC_VERSION_TOOLSET_SEARCH_MAP[version_key] = [] - if vs_def.vs_express: + if vs_product_def.vs_express: express_key = version_key + 'Exp' MSVC_VERSION_TOOLSET_DEFAULTS_MAP[version_key].append(express_key) MSVC_VERSION_TOOLSET_DEFAULTS_MAP[express_key] = [express_key] MSVC_VERSION_TOOLSET_SEARCH_MAP[express_key] = [express_key] # Pass 2: Extend search lists (decreasing version order) -for vs_def in VISUALSTUDIO_DEFINITION_LIST: - if not vs_def.vc_buildtools_def.vc_istoolset: +for vs_product_def in MSVS_PRODUCT_DEFINITION_LIST: + if not vs_product_def.vc_buildtools_def.vc_istoolset: continue - version_key = vs_def.vc_buildtools_def.vc_version - for vc_buildtools in vs_def.vc_buildtools_all: + version_key = vs_product_def.vc_buildtools_def.vc_version + for vc_buildtools in vs_product_def.vc_buildtools_all: toolset_buildtools_def = MSVC_BUILDTOOLS_INTERNAL[vc_buildtools] - toolset_vs_def = MSVC_VERSION_INTERNAL[toolset_buildtools_def.vc_version] + toolset_vs_product_def = MSVC_VERSION_INTERNAL[toolset_buildtools_def.vc_version] buildtools_key = toolset_buildtools_def.vc_version MSVC_VERSION_TOOLSET_SEARCH_MAP[buildtools_key].extend(MSVC_VERSION_TOOLSET_DEFAULTS_MAP[version_key]) # convert string version set to string version list ranked in descending order MSVC_SDK_VERSIONS = [str(f) for f in sorted([float(s) for s in MSVC_SDK_VERSIONS], reverse=True)] +# internal consistency check (should be last) def verify(): from . import Util diff --git a/SCons/Tool/MSCommon/MSVC/Exceptions.py b/SCons/Tool/MSCommon/MSVC/Exceptions.py index 7b24a2b4b4..4f24ed17e1 100644 --- a/SCons/Tool/MSCommon/MSVC/Exceptions.py +++ b/SCons/Tool/MSCommon/MSVC/Exceptions.py @@ -39,6 +39,9 @@ class MSVCUserError(VisualCException): class MSVCScriptExecutionError(VisualCException): pass +class MSVCVersionUnsupported(MSVCUserError): + pass + class MSVCVersionNotFound(MSVCUserError): pass diff --git a/SCons/Tool/MSCommon/MSVC/Options.py b/SCons/Tool/MSCommon/MSVC/Options.py new file mode 100644 index 0000000000..72f3fb5768 --- /dev/null +++ b/SCons/Tool/MSCommon/MSVC/Options.py @@ -0,0 +1,245 @@ +# 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. + +""" +Command-line options for Microsoft Visual C/C++. +""" + +import argparse +import os +import shlex + +from collections import namedtuple + +import SCons.Script + +from ..common import ( + debug, + debug_extra, +) + +from . import Util + +from .Exceptions import ( + MSVCInternalError, + MSVCArgumentError, +) + +from . import Dispatcher +Dispatcher.register_modulename(__name__) + + +MSVC_OPTIONS_EVAR = 'SCONS_MSVC_OPTIONS' + +class MSVCOptionsParser(argparse.ArgumentParser, Util.AutoInitialize): + + debug_extra = None + + def __init__(self, *, environ_var, environ_val): + super().__init__() + self.debug_extra = debug_extra(self.__class__) + self._environ_var = environ_var + self._environ_val = environ_val + self._environ_args = shlex.split(environ_val, posix=False) + self.prog = f'{self.__class__.__name__}' + debug('environ_args=%s', repr(self._environ_args), extra=self.debug_extra) + + def error(self, message) -> None: + debug( + 'MSVCArgumentError: message=%s', + repr(message), extra=self.debug_extra + ) + errmsg = f'{self._environ_var} {message}\n {self._environ_var}={self._environ_val}' + raise MSVCArgumentError(errmsg) + + def exit(self, status=0, message=None) -> None: + message = message.rstrip('\n') + debug( + 'MSVCInternalError: status=%d, message=%s', + status, repr(message), extra=self.debug_extra + ) + raise MSVCInternalError(message) + + def parse_args(self) -> None: + args = super().parse_args(self._environ_args) + debug('args=%s', repr(args), extra=self.debug_extra) + return args + +class _Options(Util.AutoInitialize): + + debug_extra = None + + options_environ = None + options_enabled = False + + _Option = namedtuple('Option', [ + 'option', + 'dest', + 'default', + 'action', + 'help', + 'metavar', + 'type', + 'type_scons', + ]) + + msvs_channel_opt = _Option( + option='--msvs-channel', + dest='msvs_channel', + default=None, + type=str, + type_scons='string', + action='store', + help='Set default msvs CHANNEL [Release, Preview, Any].', + metavar='CHANNEL', + ) + + msvs_channel_val = msvs_channel_opt.default + + vswhere_opt = _Option( + option='--vswhere', + dest='vswhere', + default=None, + type=str, + type_scons='string', + action='store', + help='Add vswhere executable located at EXEPATH.', + metavar='EXEPATH', + ) + + vswhere_val = vswhere_opt.default + + @classmethod + def _options_enabled(cls) -> bool: + + val = os.environ.get(MSVC_OPTIONS_EVAR) + if val: + val = val.strip() + + if val: + cls.options_environ = val + cls.options_enabled = True + + debug( + 'options_enabled=%s, options_environ=%s', + cls.options_enabled, repr(cls.options_environ), extra=cls.debug_extra + ) + + return cls.options_enabled + + @classmethod + def _options_parser(cls) -> None: + + parser = MSVCOptionsParser( + environ_var=MSVC_OPTIONS_EVAR, + environ_val=cls.options_environ, + ) + + parser.add_argument( + cls.msvs_channel_opt.option, + dest=cls.msvs_channel_opt.dest, + default=cls.msvs_channel_opt.default, + type=cls.msvs_channel_opt.type, + action=cls.msvs_channel_opt.action, + help=cls.msvs_channel_opt.help, + metavar=cls.msvs_channel_opt.metavar, + ) + + parser.add_argument( + cls.vswhere_opt.option, + dest=cls.vswhere_opt.dest, + default=cls.vswhere_opt.default, + type=cls.vswhere_opt.type, + action=cls.vswhere_opt.action, + help=cls.vswhere_opt.help, + metavar=cls.vswhere_opt.metavar, + ) + + args = parser.parse_args() + + msvs_channel_val = getattr(args, cls.msvs_channel_opt.dest) + if msvs_channel_val: + cls.msvs_channel_val = msvs_channel_val + debug('msvs_channel=%s', repr(cls.msvs_channel_val), extra=cls.debug_extra) + + vswhere_val = getattr(args, cls.vswhere_opt.dest) + if vswhere_val: + cls.vswhere_val = vswhere_val + debug('vswhere=%s', repr(cls.vswhere_val), extra=cls.debug_extra) + + @classmethod + def _options_cmdline(cls) -> None: + + SCons.Script.AddOption( + cls.msvs_channel_opt.option, + dest=cls.msvs_channel_opt.dest, + default=cls.msvs_channel_opt.default, + type=cls.msvs_channel_opt.type_scons, + action=cls.msvs_channel_opt.action, + help=cls.msvs_channel_opt.help, + metavar=cls.msvs_channel_opt.metavar, + nargs=1, + ) + + SCons.Script.AddOption( + cls.vswhere_opt.option, + dest=cls.vswhere_opt.dest, + default=cls.vswhere_opt.default, + type=cls.vswhere_opt.type_scons, + action=cls.vswhere_opt.action, + help=cls.vswhere_opt.help, + metavar=cls.vswhere_opt.metavar, + nargs=1, + ) + + msvs_channel_val = SCons.Script.GetOption(cls.msvs_channel_opt.dest) + if msvs_channel_val: + cls.msvs_channel_val = msvs_channel_val + debug('msvs_channel=%s', repr(cls.msvs_channel_val), extra=cls.debug_extra) + + vswhere_val = SCons.Script.GetOption(cls.vswhere_opt.dest) + if vswhere_val: + cls.vswhere_val = vswhere_val + debug('vswhere=%s', repr(cls.vswhere_val), extra=cls.debug_extra) + + @classmethod + def _options_finalize(cls) -> None: + debug('msvs_channel=%s', repr(cls.msvs_channel_val), extra=cls.debug_extra) + debug('vswhere=%s', repr(cls.vswhere_val), extra=cls.debug_extra) + + @classmethod + def _initialize(cls) -> None: + + cls.debug_extra = debug_extra(cls) + + if cls._options_enabled(): + cls._options_parser() + + cls._options_cmdline() + cls._options_finalize() + +def msvs_channel(): + return _Options.msvs_channel_val, _Options.msvs_channel_opt.option + +def vswhere(): + return _Options.vswhere_val, _Options.vswhere_opt.option diff --git a/SCons/Tool/MSCommon/MSVC/Registry.py b/SCons/Tool/MSCommon/MSVC/Registry.py index f9e544c6fc..b5b72c42fc 100644 --- a/SCons/Tool/MSCommon/MSVC/Registry.py +++ b/SCons/Tool/MSCommon/MSVC/Registry.py @@ -110,6 +110,9 @@ def windows_kit_query_paths(version): q = windows_kits(version) return microsoft_query_paths(q) +def vstudio_sxs_vs7(version): + return '\\'.join([r'VisualStudio\SxS\VS7', version]) + def vstudio_sxs_vc7(version): return '\\'.join([r'VisualStudio\SxS\VC7', version]) diff --git a/SCons/Tool/MSCommon/MSVC/RegistryTests.py b/SCons/Tool/MSCommon/MSVC/RegistryTests.py index 9c9ef89a0e..859bb69ae4 100644 --- a/SCons/Tool/MSCommon/MSVC/RegistryTests.py +++ b/SCons/Tool/MSCommon/MSVC/RegistryTests.py @@ -40,10 +40,10 @@ class RegistryTests(unittest.TestCase): def setUpClass(cls) -> None: cls._sdk_versions = [] sdk_seen = set() - for vs_def in Config.VISUALSTUDIO_DEFINITION_LIST: - if not vs_def.vc_sdk_versions: + for vs_product_def in Config.MSVS_PRODUCT_DEFINITION_LIST: + if not vs_product_def.vc_sdk_versions: continue - for sdk_version in vs_def.vc_sdk_versions: + for sdk_version in vs_product_def.vc_sdk_versions: if sdk_version in sdk_seen: continue sdk_seen.add(sdk_version) diff --git a/SCons/Tool/MSCommon/MSVC/ScriptArguments.py b/SCons/Tool/MSCommon/MSVC/ScriptArguments.py index 8848095cf9..ebef9d693b 100644 --- a/SCons/Tool/MSCommon/MSVC/ScriptArguments.py +++ b/SCons/Tool/MSCommon/MSVC/ScriptArguments.py @@ -82,6 +82,7 @@ def _verify_re_sdk_dispatch_map(): _msvc_sxs_bugfix_folder = {} _msvc_sxs_bugfix_version = {} +# TODO(JCB): verstr or version? for msvc_version, sxs_version, sxs_bugfix in [ # VS2019\Common7\Tools\vsdevcmd\ext\vcvars.bat AzDO Bug#1293526 # special handling of the 16.8 SxS toolset, use VC\Auxiliary\Build\14.28 directory and SxS files @@ -167,39 +168,27 @@ class SortOrder(enum.IntEnum): VS2017 = Config.MSVS_VERSION_INTERNAL['2017'] VS2015 = Config.MSVS_VERSION_INTERNAL['2015'] -MSVC_VERSION_ARGS_DEFINITION = namedtuple('MSVCVersionArgsDefinition', [ - 'version', # full version (e.g., '14.1Exp', '14.32.31326') - 'vs_def', +MSVC_TOOLSET_VERSION_ARGS_DEFINITION = namedtuple('MSVCToolsetVersionArgsDefinition', [ + 'version', # full version (e.g., '14.32.31326') + 'vs_product_def', ]) -def _msvc_version(version): - - verstr = Util.get_msvc_version_prefix(version) - vs_def = Config.MSVC_VERSION_INTERNAL[verstr] - - version_args = MSVC_VERSION_ARGS_DEFINITION( - version = version, - vs_def = vs_def, - ) - - return version_args - def _toolset_version(version): verstr = Util.get_msvc_version_prefix(version) - vs_def = Config.MSVC_VERSION_INTERNAL[verstr] + vs_product_def = Config.MSVC_VERSION_INTERNAL[verstr] - version_args = MSVC_VERSION_ARGS_DEFINITION( + version_args = MSVC_TOOLSET_VERSION_ARGS_DEFINITION( version = version, - vs_def = vs_def, + vs_product_def = vs_product_def, ) return version_args -def _msvc_script_argument_uwp(env, msvc, arglist): +def _msvc_script_argument_uwp(env, msvc_instance, arglist, target_arch): uwp_app = env['MSVC_UWP_APP'] - debug('MSVC_VERSION=%s, MSVC_UWP_APP=%s', repr(msvc.version), repr(uwp_app)) + debug('MSVC_VERSION=%s, MSVC_UWP_APP=%s', repr(msvc_instance.msvc_version), repr(uwp_app)) if not uwp_app: return None @@ -207,19 +196,36 @@ def _msvc_script_argument_uwp(env, msvc, arglist): if uwp_app not in _ARGUMENT_BOOLEAN_TRUE_LEGACY: return None - if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2015.vc_buildtools_def.vc_version_numeric: + if msvc_instance.vs_product_def.vc_buildtools_def.vc_version_numeric < VS2015.vc_buildtools_def.vc_version_numeric: debug( 'invalid: msvc version constraint: %s < %s VS2015', - repr(msvc.vs_def.vc_buildtools_def.vc_version_numeric), + repr(msvc_instance.vs_product_def.vc_buildtools_def.vc_version_numeric), repr(VS2015.vc_buildtools_def.vc_version_numeric) ) err_msg = "MSVC_UWP_APP ({}) constraint violation: MSVC_VERSION {} < {} VS2015".format( - repr(uwp_app), repr(msvc.version), repr(VS2015.vc_buildtools_def.vc_version) + repr(uwp_app), repr(msvc_instance.msvc_version), repr(VS2015.vc_buildtools_def.vc_version) ) raise MSVCArgumentError(err_msg) + is_supported, is_target = msvc_instance.is_uwp_target_supported(target_arch) + if not is_supported: + component_str = msvc_instance.vs_component_id + debug( + 'invalid: msvc_version constraint: %s %s %s', + repr(msvc_instance.msvc_version), repr(component_str), repr(target_arch) + ) + if is_target and target_arch: + err_msg = "MSVC_UWP_APP ({}) TARGET_ARCH ({}) is not supported for MSVC_VERSION {} ({})".format( + repr(uwp_app), repr(target_arch), repr(msvc_instance.msvc_version), repr(component_str) + ) + else: + err_msg = "MSVC_UWP_APP ({}) is not supported for MSVC_VERSION {} ({})".format( + repr(uwp_app), repr(msvc_instance.msvc_version), repr(component_str) + ) + raise MSVCArgumentError(err_msg) + # VS2017+ rewrites uwp => store for 14.0 toolset - uwp_arg = msvc.vs_def.vc_uwp + uwp_arg = msvc_instance.vs_product_def.vc_uwp # store/uwp may not be fully installed argpair = (SortOrder.UWP, uwp_arg) @@ -250,20 +256,28 @@ def _user_script_argument_uwp(env, uwp, user_argstr) -> bool: raise MSVCArgumentError(err_msg) -def _msvc_script_argument_sdk_constraints(msvc, sdk_version): +def _msvc_script_argument_sdk_constraints(msvc_instance, sdk_version): - if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2015.vc_buildtools_def.vc_version_numeric: + if msvc_instance.vs_product_def.vc_buildtools_def.vc_version_numeric < VS2015.vc_buildtools_def.vc_version_numeric: debug( 'invalid: msvc_version constraint: %s < %s VS2015', - repr(msvc.vs_def.vc_buildtools_def.vc_version_numeric), + repr(msvc_instance.vs_product_def.vc_buildtools_def.vc_version_numeric), repr(VS2015.vc_buildtools_def.vc_version_numeric) ) err_msg = "MSVC_SDK_VERSION ({}) constraint violation: MSVC_VERSION {} < {} VS2015".format( - repr(sdk_version), repr(msvc.version), repr(VS2015.vc_buildtools_def.vc_version) + repr(sdk_version), repr(msvc_instance.msvc_version), repr(VS2015.vc_buildtools_def.vc_version) + ) + return err_msg + + if not msvc_instance.is_sdkversion_supported: + component_str = msvc_instance.vs_component_id + debug('invalid: msvc_version constraint: %s %s', repr(msvc_instance.msvc_version), repr(component_str)) + err_msg = "MSVC_SDK_VERSION ({}) is not supported for MSVC_VERSION {} ({})".format( + repr(sdk_version), repr(msvc_instance.msvc_version), repr(component_str) ) return err_msg - for msvc_sdk_version in msvc.vs_def.vc_sdk_versions: + for msvc_sdk_version in msvc_instance.vs_product_def.vc_sdk_versions: re_sdk_version = re_sdk_dispatch_map[msvc_sdk_version] if re_sdk_version.match(sdk_version): debug('valid: sdk_version=%s', repr(sdk_version)) @@ -273,19 +287,19 @@ def _msvc_script_argument_sdk_constraints(msvc, sdk_version): err_msg = f"MSVC_SDK_VERSION ({sdk_version!r}) is not supported" return err_msg -def _msvc_script_argument_sdk_platform_constraints(msvc, toolset, sdk_version, platform_def): +def _msvc_script_argument_sdk_platform_constraints(msvc_instance, toolset, sdk_version, platform_def): if sdk_version == '8.1' and platform_def.is_uwp: - vs_def = toolset.vs_def if toolset else msvc.vs_def + vs_product_def = toolset.vs_product_def if toolset else msvc_instance.vs_product_def - if vs_def.vc_buildtools_def.vc_version_numeric > VS2015.vc_buildtools_def.vc_version_numeric: + if vs_product_def.vc_buildtools_def.vc_version_numeric > VS2015.vc_buildtools_def.vc_version_numeric: debug( 'invalid: uwp/store SDK 8.1 msvc_version constraint: %s > %s VS2015', - repr(vs_def.vc_buildtools_def.vc_version_numeric), + repr(vs_product_def.vc_buildtools_def.vc_version_numeric), repr(VS2015.vc_buildtools_def.vc_version_numeric) ) - if toolset and toolset.vs_def != msvc.vs_def: + if toolset and toolset.vs_product_def != msvc_instance.vs_product_def: err_msg = "MSVC_SDK_VERSION ({}) and platform type ({}) constraint violation: toolset version {} > {} VS2015".format( repr(sdk_version), repr(platform_def.vc_platform), repr(toolset.version), repr(VS2015.vc_buildtools_def.vc_version) @@ -293,28 +307,28 @@ def _msvc_script_argument_sdk_platform_constraints(msvc, toolset, sdk_version, p else: err_msg = "MSVC_SDK_VERSION ({}) and platform type ({}) constraint violation: MSVC_VERSION {} > {} VS2015".format( repr(sdk_version), repr(platform_def.vc_platform), - repr(msvc.version), repr(VS2015.vc_buildtools_def.vc_version) + repr(msvc_instance.msvc_version), repr(VS2015.vc_buildtools_def.vc_version) ) return err_msg return None -def _msvc_script_argument_sdk(env, msvc, toolset, platform_def, arglist): +def _msvc_script_argument_sdk(env, msvc_instance, toolset, platform_def, arglist): sdk_version = env['MSVC_SDK_VERSION'] debug( 'MSVC_VERSION=%s, MSVC_SDK_VERSION=%s, platform_type=%s', - repr(msvc.version), repr(sdk_version), repr(platform_def.vc_platform) + repr(msvc_instance.msvc_version), repr(sdk_version), repr(platform_def.vc_platform) ) if not sdk_version: return None - err_msg = _msvc_script_argument_sdk_constraints(msvc, sdk_version) + err_msg = _msvc_script_argument_sdk_constraints(msvc_instance, sdk_version) if err_msg: raise MSVCArgumentError(err_msg) - sdk_list = WinSDK.get_sdk_version_list(msvc.vs_def, platform_def) + sdk_list = WinSDK.get_sdk_version_list(msvc_instance.vs_product_def, platform_def) if sdk_version not in sdk_list: err_msg = "MSVC_SDK_VERSION {} not found for platform type {}".format( @@ -322,7 +336,7 @@ def _msvc_script_argument_sdk(env, msvc, toolset, platform_def, arglist): ) raise MSVCSDKVersionNotFound(err_msg) - err_msg = _msvc_script_argument_sdk_platform_constraints(msvc, toolset, sdk_version, platform_def) + err_msg = _msvc_script_argument_sdk_platform_constraints(msvc_instance, toolset, sdk_version, platform_def) if err_msg: raise MSVCArgumentError(err_msg) @@ -331,12 +345,15 @@ def _msvc_script_argument_sdk(env, msvc, toolset, platform_def, arglist): return sdk_version -def _msvc_script_default_sdk(env, msvc, platform_def, arglist, force_sdk: bool=False): +def _msvc_script_default_sdk(env, msvc_instance, platform_def, arglist, force_sdk: bool=False): + + if msvc_instance.vs_product_def.vc_buildtools_def.vc_version_numeric < VS2015.vc_buildtools_def.vc_version_numeric: + return None - if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2015.vc_buildtools_def.vc_version_numeric: + if not msvc_instance.is_sdkversion_supported: return None - sdk_list = WinSDK.get_sdk_version_list(msvc.vs_def, platform_def) + sdk_list = WinSDK.get_sdk_version_list(msvc_instance.vs_product_def, platform_def) if not len(sdk_list): return None @@ -344,7 +361,7 @@ def _msvc_script_default_sdk(env, msvc, platform_def, arglist, force_sdk: bool=F debug( 'MSVC_VERSION=%s, sdk_default=%s, platform_type=%s', - repr(msvc.version), repr(sdk_default), repr(platform_def.vc_platform) + repr(msvc_instance.msvc_version), repr(sdk_default), repr(platform_def.vc_platform) ) if force_sdk: @@ -395,27 +412,27 @@ def _reset_have140_cache() -> None: debug('reset: cache') _toolset_have140_cache = None -def _msvc_read_toolset_file(msvc, filename): +def _msvc_read_toolset_file(msvc_instance, filename): toolset_version = None try: with open(filename) as f: toolset_version = f.readlines()[0].strip() debug( 'msvc_version=%s, filename=%s, toolset_version=%s', - repr(msvc.version), repr(filename), repr(toolset_version) + repr(msvc_instance.msvc_version), repr(filename), repr(toolset_version) ) except OSError: - debug('OSError: msvc_version=%s, filename=%s', repr(msvc.version), repr(filename)) + debug('OSError: msvc_version=%s, filename=%s', repr(msvc_instance.msvc_version), repr(filename)) except IndexError: - debug('IndexError: msvc_version=%s, filename=%s', repr(msvc.version), repr(filename)) + debug('IndexError: msvc_version=%s, filename=%s', repr(msvc_instance.msvc_version), repr(filename)) return toolset_version -def _msvc_sxs_toolset_folder(msvc, sxs_folder): +def _msvc_sxs_toolset_folder(msvc_instance, sxs_folder): if Util.is_toolset_sxs(sxs_folder): return sxs_folder, sxs_folder - key = (msvc.vs_def.vc_buildtools_def.vc_version, sxs_folder) + key = (msvc_instance.vs_product_def.vc_buildtools_def.vc_version, sxs_folder) if key in _msvc_sxs_bugfix_folder: sxs_version = _msvc_sxs_bugfix_folder[key] return sxs_folder, sxs_version @@ -423,31 +440,31 @@ def _msvc_sxs_toolset_folder(msvc, sxs_folder): debug('sxs folder: ignore version=%s', repr(sxs_folder)) return None, None -def _msvc_read_toolset_folders(msvc, vc_dir): +def _msvc_read_toolset_folders(msvc_instance): toolsets_sxs = {} toolsets_full = [] - build_dir = os.path.join(vc_dir, "Auxiliary", "Build") + build_dir = os.path.join(msvc_instance.vc_dir, "Auxiliary", "Build") if os.path.exists(build_dir): for sxs_folder, sxs_path in Util.listdir_dirs(build_dir): - sxs_folder, sxs_version = _msvc_sxs_toolset_folder(msvc, sxs_folder) + sxs_folder, sxs_version = _msvc_sxs_toolset_folder(msvc_instance, sxs_folder) if not sxs_version: continue filename = f'Microsoft.VCToolsVersion.{sxs_folder}.txt' filepath = os.path.join(sxs_path, filename) debug('sxs toolset: check file=%s', repr(filepath)) if os.path.exists(filepath): - toolset_version = _msvc_read_toolset_file(msvc, filepath) + toolset_version = _msvc_read_toolset_file(msvc_instance, filepath) if not toolset_version: continue toolsets_sxs[sxs_version] = toolset_version debug( 'sxs toolset: msvc_version=%s, sxs_version=%s, toolset_version=%s', - repr(msvc.version), repr(sxs_version), repr(toolset_version) + repr(msvc_instance.msvc_version), repr(sxs_version), repr(toolset_version) ) - toolset_dir = os.path.join(vc_dir, "Tools", "MSVC") + toolset_dir = os.path.join(msvc_instance.vc_dir, "Tools", "MSVC") if os.path.exists(toolset_dir): for toolset_version, toolset_path in Util.listdir_dirs(toolset_dir): binpath = os.path.join(toolset_path, "bin") @@ -456,23 +473,23 @@ def _msvc_read_toolset_folders(msvc, vc_dir): toolsets_full.append(toolset_version) debug( 'toolset: msvc_version=%s, toolset_version=%s', - repr(msvc.version), repr(toolset_version) + repr(msvc_instance.msvc_version), repr(toolset_version) ) - vcvars140 = os.path.join(vc_dir, "..", "Common7", "Tools", "vsdevcmd", "ext", "vcvars", "vcvars140.bat") + vcvars140 = os.path.join(msvc_instance.vc_dir, "..", "Common7", "Tools", "vsdevcmd", "ext", "vcvars", "vcvars140.bat") if os.path.exists(vcvars140) and _msvc_have140_toolset(): toolset_version = '14.0' toolsets_full.append(toolset_version) debug( 'toolset: msvc_version=%s, toolset_version=%s', - repr(msvc.version), repr(toolset_version) + repr(msvc_instance.msvc_version), repr(toolset_version) ) toolsets_full.sort(reverse=True) # SxS bugfix fixup (if necessary) - if msvc.version in _msvc_sxs_bugfix_map: - for sxs_version, sxs_bugfix in _msvc_sxs_bugfix_map[msvc.version]: + if msvc_instance.msvc_version in _msvc_sxs_bugfix_map: + for sxs_version, sxs_bugfix in _msvc_sxs_bugfix_map[msvc_instance.msvc_version]: if sxs_version in toolsets_sxs: # have SxS version (folder/file mapping exists) continue @@ -487,21 +504,21 @@ def _msvc_read_toolset_folders(msvc, vc_dir): toolsets_sxs[sxs_version] = toolset_version break - debug('msvc_version=%s, toolsets=%s', repr(msvc.version), repr(toolsets_full)) + debug('msvc_version=%s, toolsets=%s', repr(msvc_instance.msvc_version), repr(toolsets_full)) return toolsets_sxs, toolsets_full -def _msvc_read_toolset_default(msvc, vc_dir): +def _msvc_read_toolset_default(msvc_instance): - build_dir = os.path.join(vc_dir, "Auxiliary", "Build") + build_dir = os.path.join(msvc_instance.vc_dir, "Auxiliary", "Build") # VS2019+ - filename = f"Microsoft.VCToolsVersion.{msvc.vs_def.vc_buildtools_def.vc_buildtools}.default.txt" + filename = f"Microsoft.VCToolsVersion.{msvc_instance.vs_product_def.vc_buildtools_def.vc_buildtools}.default.txt" filepath = os.path.join(build_dir, filename) debug('default toolset: check file=%s', repr(filepath)) if os.path.exists(filepath): - toolset_buildtools = _msvc_read_toolset_file(msvc, filepath) + toolset_buildtools = _msvc_read_toolset_file(msvc_instance, filepath) if toolset_buildtools: return toolset_buildtools @@ -511,7 +528,7 @@ def _msvc_read_toolset_default(msvc, vc_dir): debug('default toolset: check file=%s', repr(filepath)) if os.path.exists(filepath): - toolset_default = _msvc_read_toolset_file(msvc, filepath) + toolset_default = _msvc_read_toolset_file(msvc_instance, filepath) if toolset_default: return toolset_default @@ -527,29 +544,33 @@ def _reset_toolset_cache() -> None: _toolset_version_cache = {} _toolset_default_cache = {} -def _msvc_version_toolsets(msvc, vc_dir): +def _msvc_version_toolsets(msvc_instance): + + cache_key = msvc_instance.vs_edition_channel_component_seqnbr_key - if msvc.version in _toolset_version_cache: - toolsets_sxs, toolsets_full = _toolset_version_cache[msvc.version] + if cache_key in _toolset_version_cache: + toolsets_sxs, toolsets_full = _toolset_version_cache[cache_key] else: - toolsets_sxs, toolsets_full = _msvc_read_toolset_folders(msvc, vc_dir) - _toolset_version_cache[msvc.version] = toolsets_sxs, toolsets_full + toolsets_sxs, toolsets_full = _msvc_read_toolset_folders(msvc_instance) + _toolset_version_cache[cache_key] = toolsets_sxs, toolsets_full return toolsets_sxs, toolsets_full -def _msvc_default_toolset(msvc, vc_dir): +def _msvc_default_toolset(msvc_instance): - if msvc.version in _toolset_default_cache: - toolset_default = _toolset_default_cache[msvc.version] + cache_key = msvc_instance.vs_edition_channel_component_seqnbr_key + + if cache_key in _toolset_default_cache: + toolset_default = _toolset_default_cache[cache_key] else: - toolset_default = _msvc_read_toolset_default(msvc, vc_dir) - _toolset_default_cache[msvc.version] = toolset_default + toolset_default = _msvc_read_toolset_default(msvc_instance) + _toolset_default_cache[cache_key] = toolset_default return toolset_default -def _msvc_version_toolset_vcvars(msvc, vc_dir, toolset_version): +def _msvc_version_toolset_vcvars(msvc_instance, toolset_version): - toolsets_sxs, toolsets_full = _msvc_version_toolsets(msvc, vc_dir) + toolsets_sxs, toolsets_full = _msvc_version_toolsets(msvc_instance) if toolset_version in toolsets_full: # full toolset version provided @@ -572,16 +593,16 @@ def _msvc_version_toolset_vcvars(msvc, vc_dir, toolset_version): return None -def _msvc_script_argument_toolset_constraints(msvc, toolset_version): +def _msvc_script_argument_toolset_constraints(msvc_instance, toolset_version): - if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2017.vc_buildtools_def.vc_version_numeric: + if msvc_instance.vs_product_def.vc_buildtools_def.vc_version_numeric < VS2017.vc_buildtools_def.vc_version_numeric: debug( 'invalid: msvc version constraint: %s < %s VS2017', - repr(msvc.vs_def.vc_buildtools_def.vc_version_numeric), + repr(msvc_instance.vs_product_def.vc_buildtools_def.vc_version_numeric), repr(VS2017.vc_buildtools_def.vc_version_numeric) ) err_msg = "MSVC_TOOLSET_VERSION ({}) constraint violation: MSVC_VERSION {} < {} VS2017".format( - repr(toolset_version), repr(msvc.version), repr(VS2017.vc_buildtools_def.vc_version) + repr(toolset_version), repr(msvc_instance.msvc_version), repr(VS2017.vc_buildtools_def.vc_version) ) return err_msg @@ -606,13 +627,13 @@ def _msvc_script_argument_toolset_constraints(msvc, toolset_version): ) return err_msg - if toolset_vernum > msvc.vs_def.vc_buildtools_def.vc_version_numeric: + if toolset_vernum > msvc_instance.vs_product_def.vc_buildtools_def.vc_version_numeric: debug( 'invalid: toolset version constraint: toolset %s > %s msvc', - repr(toolset_vernum), repr(msvc.vs_def.vc_buildtools_def.vc_version_numeric) + repr(toolset_vernum), repr(msvc_instance.vs_product_def.vc_buildtools_def.vc_version_numeric) ) err_msg = "MSVC_TOOLSET_VERSION ({}) constraint violation: toolset version {} > {} MSVC_VERSION".format( - repr(toolset_version), repr(toolset_verstr), repr(msvc.version) + repr(toolset_version), repr(toolset_verstr), repr(msvc_instance.msvc_version) ) return err_msg @@ -642,9 +663,9 @@ def _msvc_script_argument_toolset_constraints(msvc, toolset_version): err_msg = f"MSVC_TOOLSET_VERSION ({toolset_version!r}) format is not supported" return err_msg -def _msvc_script_argument_toolset_vcvars(msvc, toolset_version, vc_dir): +def _msvc_script_argument_toolset_vcvars(msvc_instance, toolset_version): - err_msg = _msvc_script_argument_toolset_constraints(msvc, toolset_version) + err_msg = _msvc_script_argument_toolset_constraints(msvc_instance, toolset_version) if err_msg: raise MSVCArgumentError(err_msg) @@ -656,7 +677,7 @@ def _msvc_script_argument_toolset_vcvars(msvc, toolset_version, vc_dir): ) toolset_version = new_toolset_version - toolset_vcvars = _msvc_version_toolset_vcvars(msvc, vc_dir, toolset_version) + toolset_vcvars = _msvc_version_toolset_vcvars(msvc_instance, toolset_version) debug( 'toolset: toolset_version=%s, toolset_vcvars=%s', repr(toolset_version), repr(toolset_vcvars) @@ -664,21 +685,21 @@ def _msvc_script_argument_toolset_vcvars(msvc, toolset_version, vc_dir): if not toolset_vcvars: err_msg = "MSVC_TOOLSET_VERSION {} not found for MSVC_VERSION {}".format( - repr(toolset_version), repr(msvc.version) + repr(toolset_version), repr(msvc_instance.msvc_version) ) raise MSVCToolsetVersionNotFound(err_msg) return toolset_vcvars -def _msvc_script_argument_toolset(env, msvc, vc_dir, arglist): +def _msvc_script_argument_toolset(env, msvc_instance, arglist): toolset_version = env['MSVC_TOOLSET_VERSION'] - debug('MSVC_VERSION=%s, MSVC_TOOLSET_VERSION=%s', repr(msvc.version), repr(toolset_version)) + debug('MSVC_VERSION=%s, MSVC_TOOLSET_VERSION=%s', repr(msvc_instance.msvc_version), repr(toolset_version)) if not toolset_version: return None - toolset_vcvars = _msvc_script_argument_toolset_vcvars(msvc, toolset_version, vc_dir) + toolset_vcvars = _msvc_script_argument_toolset_vcvars(msvc_instance, toolset_version) # toolset may not be installed for host/target argpair = (SortOrder.TOOLSET, f'-vcvars_ver={toolset_vcvars}') @@ -686,16 +707,16 @@ def _msvc_script_argument_toolset(env, msvc, vc_dir, arglist): return toolset_vcvars -def _msvc_script_default_toolset(env, msvc, vc_dir, arglist, force_toolset: bool=False): +def _msvc_script_default_toolset(env, msvc_instance, arglist, force_toolset: bool=False): - if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2017.vc_buildtools_def.vc_version_numeric: + if msvc_instance.vs_product_def.vc_buildtools_def.vc_version_numeric < VS2017.vc_buildtools_def.vc_version_numeric: return None - toolset_default = _msvc_default_toolset(msvc, vc_dir) + toolset_default = _msvc_default_toolset(msvc_instance) if not toolset_default: return None - debug('MSVC_VERSION=%s, toolset_default=%s', repr(msvc.version), repr(toolset_default)) + debug('MSVC_VERSION=%s, toolset_default=%s', repr(msvc_instance.msvc_version), repr(toolset_default)) if force_toolset: argpair = (SortOrder.TOOLSET, f'-vcvars_ver={toolset_default}') @@ -727,24 +748,24 @@ def _user_script_argument_toolset(env, toolset_version, user_argstr): raise MSVCArgumentError(err_msg) -def _msvc_script_argument_spectre_constraints(msvc, toolset, spectre_libs, platform_def): +def _msvc_script_argument_spectre_constraints(msvc_instance, toolset, spectre_libs, platform_def): - if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2017.vc_buildtools_def.vc_version_numeric: + if msvc_instance.vs_product_def.vc_buildtools_def.vc_version_numeric < VS2017.vc_buildtools_def.vc_version_numeric: debug( 'invalid: msvc version constraint: %s < %s VS2017', - repr(msvc.vs_def.vc_buildtools_def.vc_version_numeric), + repr(msvc_instance.vs_product_def.vc_buildtools_def.vc_version_numeric), repr(VS2017.vc_buildtools_def.vc_version_numeric) ) err_msg = "MSVC_SPECTRE_LIBS ({}) constraint violation: MSVC_VERSION {} < {} VS2017".format( - repr(spectre_libs), repr(msvc.version), repr(VS2017.vc_buildtools_def.vc_version) + repr(spectre_libs), repr(msvc_instance.msvc_version), repr(VS2017.vc_buildtools_def.vc_version) ) return err_msg if toolset: - if toolset.vs_def.vc_buildtools_def.vc_version_numeric < VS2017.vc_buildtools_def.vc_version_numeric: + if toolset.vs_product_def.vc_buildtools_def.vc_version_numeric < VS2017.vc_buildtools_def.vc_version_numeric: debug( 'invalid: toolset version constraint: %s < %s VS2017', - repr(toolset.vs_def.vc_buildtools_def.vc_version_numeric), + repr(toolset.vs_product_def.vc_buildtools_def.vc_version_numeric), repr(VS2017.vc_buildtools_def.vc_version_numeric) ) err_msg = "MSVC_SPECTRE_LIBS ({}) constraint violation: toolset version {} < {} VS2017".format( @@ -752,7 +773,6 @@ def _msvc_script_argument_spectre_constraints(msvc, toolset, spectre_libs, platf ) return err_msg - if platform_def.is_uwp: debug( 'invalid: spectre_libs=%s and platform_type=%s', @@ -765,14 +785,14 @@ def _msvc_script_argument_spectre_constraints(msvc, toolset, spectre_libs, platf return None -def _msvc_toolset_version_spectre_path(vc_dir, toolset_version): - spectre_dir = os.path.join(vc_dir, "Tools", "MSVC", toolset_version, "lib", "spectre") +def _msvc_toolset_version_spectre_path(msvc_instance, toolset_version): + spectre_dir = os.path.join(msvc_instance.vc_dir, "Tools", "MSVC", toolset_version, "lib", "spectre") return spectre_dir -def _msvc_script_argument_spectre(env, msvc, vc_dir, toolset, platform_def, arglist): +def _msvc_script_argument_spectre(env, msvc_instance, toolset, platform_def, arglist): spectre_libs = env['MSVC_SPECTRE_LIBS'] - debug('MSVC_VERSION=%s, MSVC_SPECTRE_LIBS=%s', repr(msvc.version), repr(spectre_libs)) + debug('MSVC_VERSION=%s, MSVC_SPECTRE_LIBS=%s', repr(msvc_instance.msvc_version), repr(spectre_libs)) if not spectre_libs: return None @@ -780,19 +800,19 @@ def _msvc_script_argument_spectre(env, msvc, vc_dir, toolset, platform_def, argl if spectre_libs not in _ARGUMENT_BOOLEAN_TRUE: return None - err_msg = _msvc_script_argument_spectre_constraints(msvc, toolset, spectre_libs, platform_def) + err_msg = _msvc_script_argument_spectre_constraints(msvc_instance, toolset, spectre_libs, platform_def) if err_msg: raise MSVCArgumentError(err_msg) if toolset: - spectre_dir = _msvc_toolset_version_spectre_path(vc_dir, toolset.version) + spectre_dir = _msvc_toolset_version_spectre_path(msvc_instance, toolset.version) if not os.path.exists(spectre_dir): debug( 'spectre libs: msvc_version=%s, toolset_version=%s, spectre_dir=%s', - repr(msvc.version), repr(toolset.version), repr(spectre_dir) + repr(msvc_instance.msvc_version), repr(toolset.version), repr(spectre_dir) ) err_msg = "Spectre libraries not found for MSVC_VERSION {} toolset version {}".format( - repr(msvc.version), repr(toolset.version) + repr(msvc_instance.msvc_version), repr(toolset.version) ) raise MSVCSpectreLibsNotFound(err_msg) @@ -827,23 +847,23 @@ def _user_script_argument_spectre(env, spectre, user_argstr): raise MSVCArgumentError(err_msg) -def _msvc_script_argument_user(env, msvc, arglist): +def _msvc_script_argument_user(env, msvc_instance, arglist): # subst None -> empty string script_args = env.subst('$MSVC_SCRIPT_ARGS') - debug('MSVC_VERSION=%s, MSVC_SCRIPT_ARGS=%s', repr(msvc.version), repr(script_args)) + debug('MSVC_VERSION=%s, MSVC_SCRIPT_ARGS=%s', repr(msvc_instance.msvc_version), repr(script_args)) if not script_args: return None - if msvc.vs_def.vc_buildtools_def.vc_version_numeric < VS2015.vc_buildtools_def.vc_version_numeric: + if msvc_instance.vs_product_def.vc_buildtools_def.vc_version_numeric < VS2015.vc_buildtools_def.vc_version_numeric: debug( 'invalid: msvc version constraint: %s < %s VS2015', - repr(msvc.vs_def.vc_buildtools_def.vc_version_numeric), + repr(msvc_instance.vs_product_def.vc_buildtools_def.vc_version_numeric), repr(VS2015.vc_buildtools_def.vc_version_numeric) ) err_msg = "MSVC_SCRIPT_ARGS ({}) constraint violation: MSVC_VERSION {} < {} VS2015".format( - repr(script_args), repr(msvc.version), repr(VS2015.vc_buildtools_def.vc_version) + repr(script_args), repr(msvc_instance.msvc_version), repr(VS2015.vc_buildtools_def.vc_version) ) raise MSVCArgumentError(err_msg) @@ -873,26 +893,37 @@ def _msvc_process_construction_variables(env) -> bool: return False -def msvc_script_arguments(env, version, vc_dir, arg): +def msvc_script_arguments_has_uwp(env): + + if not _msvc_process_construction_variables(env): + return False + + uwp_app = env.get('MSVC_UWP_APP') + is_uwp = bool(uwp_app and uwp_app in _ARGUMENT_BOOLEAN_TRUE_LEGACY) + + debug('is_uwp=%s', is_uwp) + return is_uwp + +def msvc_script_arguments(env, msvc_instance, arg=None): arguments = [arg] if arg else [] arglist = [] arglist_reverse = False - msvc = _msvc_version(version) - if 'MSVC_SCRIPT_ARGS' in env: - user_argstr = _msvc_script_argument_user(env, msvc, arglist) + user_argstr = _msvc_script_argument_user(env, msvc_instance, arglist) else: user_argstr = None if _msvc_process_construction_variables(env): + target_arch = env.get('TARGET_ARCH') + # MSVC_UWP_APP if 'MSVC_UWP_APP' in env: - uwp = _msvc_script_argument_uwp(env, msvc, arglist) + uwp = _msvc_script_argument_uwp(env, msvc_instance, arglist, target_arch) else: uwp = None @@ -907,7 +938,7 @@ def msvc_script_arguments(env, version, vc_dir, arg): # MSVC_TOOLSET_VERSION if 'MSVC_TOOLSET_VERSION' in env: - toolset_version = _msvc_script_argument_toolset(env, msvc, vc_dir, arglist) + toolset_version = _msvc_script_argument_toolset(env, msvc_instance, arglist) else: toolset_version = None @@ -917,7 +948,7 @@ def msvc_script_arguments(env, version, vc_dir, arg): user_toolset = None if not toolset_version and not user_toolset: - default_toolset = _msvc_script_default_toolset(env, msvc, vc_dir, arglist, _MSVC_FORCE_DEFAULT_TOOLSET) + default_toolset = _msvc_script_default_toolset(env, msvc_instance, arglist, _MSVC_FORCE_DEFAULT_TOOLSET) if _MSVC_FORCE_DEFAULT_TOOLSET: toolset_version = default_toolset else: @@ -935,7 +966,7 @@ def msvc_script_arguments(env, version, vc_dir, arg): # MSVC_SDK_VERSION if 'MSVC_SDK_VERSION' in env: - sdk_version = _msvc_script_argument_sdk(env, msvc, toolset, platform_def, arglist) + sdk_version = _msvc_script_argument_sdk(env, msvc_instance, toolset, platform_def, arglist) else: sdk_version = None @@ -946,19 +977,19 @@ def msvc_script_arguments(env, version, vc_dir, arg): if _MSVC_FORCE_DEFAULT_SDK: if not sdk_version and not user_sdk: - sdk_version = _msvc_script_default_sdk(env, msvc, platform_def, arglist, _MSVC_FORCE_DEFAULT_SDK) + sdk_version = _msvc_script_default_sdk(env, msvc_instance, platform_def, arglist, _MSVC_FORCE_DEFAULT_SDK) # MSVC_SPECTRE_LIBS if 'MSVC_SPECTRE_LIBS' in env: - spectre = _msvc_script_argument_spectre(env, msvc, vc_dir, toolset, platform_def, arglist) + spectre = _msvc_script_argument_spectre(env, msvc_instance, toolset, platform_def, arglist) else: spectre = None if user_argstr: _user_script_argument_spectre(env, spectre, user_argstr) - if msvc.vs_def.vc_buildtools_def.vc_version == '14.0': + if msvc_instance.vs_product_def.vc_buildtools_def.vc_version == '14.0': if user_uwp and sdk_version and len(arglist) == 2: # VS2015 toolset argument order issue: SDK store => store SDK arglist_reverse = True @@ -974,24 +1005,20 @@ def msvc_script_arguments(env, version, vc_dir, arg): debug('arguments: %s', repr(argstr)) return argstr -def _msvc_toolset_internal(msvc_version, toolset_version, vc_dir): - - msvc = _msvc_version(msvc_version) +def _msvc_toolset_internal(msvc_instance, toolset_version): - toolset_vcvars = _msvc_script_argument_toolset_vcvars(msvc, toolset_version, vc_dir) + toolset_vcvars = _msvc_script_argument_toolset_vcvars(msvc_instance, toolset_version) return toolset_vcvars -def _msvc_toolset_versions_internal(msvc_version, vc_dir, full: bool=True, sxs: bool=False): +def _msvc_toolset_versions_internal(msvc_instance, full: bool=True, sxs: bool=False): - msvc = _msvc_version(msvc_version) - - if len(msvc.vs_def.vc_buildtools_all) <= 1: + if len(msvc_instance.vs_product_def.vc_buildtools_all) <= 1: return None toolset_versions = [] - toolsets_sxs, toolsets_full = _msvc_version_toolsets(msvc, vc_dir) + toolsets_sxs, toolsets_full = _msvc_version_toolsets(msvc_instance) if sxs: sxs_versions = list(toolsets_sxs.keys()) @@ -1003,19 +1030,17 @@ def _msvc_toolset_versions_internal(msvc_version, vc_dir, full: bool=True, sxs: return toolset_versions -def _msvc_toolset_versions_spectre_internal(msvc_version, vc_dir): - - msvc = _msvc_version(msvc_version) +def _msvc_toolset_versions_spectre_internal(msvc_instance): - if len(msvc.vs_def.vc_buildtools_all) <= 1: + if len(msvc_instance.vs_product_def.vc_buildtools_all) <= 1: return None - _, toolsets_full = _msvc_version_toolsets(msvc, vc_dir) + _, toolsets_full = _msvc_version_toolsets(msvc_instance) spectre_toolset_versions = [ toolset_version for toolset_version in toolsets_full - if os.path.exists(_msvc_toolset_version_spectre_path(vc_dir, toolset_version)) + if os.path.exists(_msvc_toolset_version_spectre_path(msvc_instance, toolset_version)) ] return spectre_toolset_versions diff --git a/SCons/Tool/MSCommon/MSVC/ScriptArgumentsTests.py b/SCons/Tool/MSCommon/MSVC/ScriptArgumentsTests.py index 670576b046..507156b36b 100644 --- a/SCons/Tool/MSCommon/MSVC/ScriptArgumentsTests.py +++ b/SCons/Tool/MSCommon/MSVC/ScriptArgumentsTests.py @@ -116,9 +116,6 @@ def _make_notfound_version(sdk_seen, sdk_def): class Data: - # all versions - ALL_VERSIONS_PAIRS = [] - # installed versions INSTALLED_VERSIONS_PAIRS = [] @@ -127,11 +124,11 @@ class Data: for vcver in Config.MSVC_VERSION_SUFFIX.keys(): version_def = Util.msvc_version_components(vcver) - vc_dir = vc.find_vc_pdir(None, vcver) - t = (version_def, vc_dir) - ALL_VERSIONS_PAIRS.append(t) - if vc_dir: - INSTALLED_VERSIONS_PAIRS.append(t) + msvc_instance = vc.find_msvc_instance(vcver) + if not msvc_instance: + continue + t = (version_def, msvc_instance) + INSTALLED_VERSIONS_PAIRS.append(t) HAVE_MSVC = True if len(INSTALLED_VERSIONS_PAIRS) else False @@ -218,18 +215,24 @@ def test_msvc_script_arguments_defaults(self) -> None: env = Environment() # disable forcing sdk and toolset versions as arguments force = ScriptArguments.msvc_force_default_arguments(force=False) - for version_def, vc_dir in Data.INSTALLED_VERSIONS_PAIRS: + for version_def, msvc_instance in Data.INSTALLED_VERSIONS_PAIRS: for arg in ('', 'arch'): - scriptargs = func(env, version_def.msvc_version, vc_dir, arg) + scriptargs = func(env, msvc_instance, arg) self.assertTrue(scriptargs == arg, "{}({},{}) != {} [force=False]".format( func.__name__, repr(version_def.msvc_version), repr(arg), repr(scriptargs) )) # enable forcing sdk and toolset versions as arguments ScriptArguments.msvc_force_default_arguments(force=True) - for version_def, vc_dir in Data.INSTALLED_VERSIONS_PAIRS: + for version_def, msvc_instance in Data.INSTALLED_VERSIONS_PAIRS: for arg in ('', 'arch'): - scriptargs = func(env, version_def.msvc_version, vc_dir, arg) - if version_def.msvc_vernum >= 14.0: + scriptargs = func(env, msvc_instance, arg) + sdk_supported = True + if version_def.msvc_verstr == '14.0': + if msvc_instance.is_express: + sdk_supported = False + elif msvc_instance.is_buildtools: + sdk_supported = False + if version_def.msvc_vernum >= 14.0 and sdk_supported: if arg and scriptargs.startswith(arg): testargs = scriptargs[len(arg):].lstrip() else: @@ -246,10 +249,11 @@ def test_msvc_script_arguments_defaults(self) -> None: def test_msvc_toolset_versions_internal(self) -> None: func = ScriptArguments._msvc_toolset_versions_internal - for version_def, vc_dir in Data.INSTALLED_VERSIONS_PAIRS: + for version_def, msvc_instance in Data.INSTALLED_VERSIONS_PAIRS: + vc_dir = msvc_instance.vc_dir for full in (True, False): for sxs in (True, False): - toolset_versions = func(version_def.msvc_version, vc_dir, full=full, sxs=sxs) + toolset_versions = func(msvc_instance, full=full, sxs=sxs) if version_def.msvc_vernum < 14.1: self.assertTrue(toolset_versions is None, "{}({},{},full={},sxs={}) is not None ({})".format( func.__name__, repr(version_def.msvc_version), repr(vc_dir), repr(full), repr(sxs), @@ -271,16 +275,17 @@ def test_msvc_toolset_internal(self) -> None: if not Data.HAVE_MSVC: return func = ScriptArguments._msvc_toolset_internal - for version_def, vc_dir in Data.INSTALLED_VERSIONS_PAIRS: - toolset_versions = ScriptArguments._msvc_toolset_versions_internal(version_def.msvc_version, vc_dir, full=True, sxs=True) + for version_def, msvc_instance in Data.INSTALLED_VERSIONS_PAIRS: + vc_dir = msvc_instance.vc_dir + toolset_versions = ScriptArguments._msvc_toolset_versions_internal(msvc_instance, full=True, sxs=True) if not toolset_versions: continue for toolset_version in toolset_versions: - _ = func(version_def.msvc_version, toolset_version, vc_dir) + _ = func(msvc_instance, toolset_version) def run_msvc_script_args_none(self) -> None: func = ScriptArguments.msvc_script_arguments - for version_def, vc_dir in Data.INSTALLED_VERSIONS_PAIRS: + for version_def, msvc_instance in Data.INSTALLED_VERSIONS_PAIRS: for kwargs in [ {'MSVC_SCRIPT_ARGS': None}, {'MSVC_SCRIPT_ARGS': None, 'MSVC_UWP_APP': None}, @@ -289,11 +294,12 @@ def run_msvc_script_args_none(self) -> None: {'MSVC_SCRIPT_ARGS': None, 'MSVC_SPECTRE_LIBS': None}, ]: env = Environment(**kwargs) - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, msvc_instance) def run_msvc_script_args(self) -> None: func = ScriptArguments.msvc_script_arguments - for version_def, vc_dir in Data.INSTALLED_VERSIONS_PAIRS: + for version_def, msvc_instance in Data.INSTALLED_VERSIONS_PAIRS: + vc_dir = msvc_instance.vc_dir if version_def.msvc_vernum >= 14.1: # VS2017 and later @@ -301,7 +307,7 @@ def run_msvc_script_args(self) -> None: Util.msvc_extended_version_components(toolset_version) for toolset_version in ScriptArguments._msvc_toolset_versions_internal( - version_def.msvc_version, vc_dir, full=True, sxs=False + msvc_instance, full=True, sxs=False ) ] @@ -312,7 +318,7 @@ def run_msvc_script_args(self) -> None: # should not raise exception (argument not validated) env = Environment(MSVC_SCRIPT_ARGS='undefinedsymbol') - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, msvc_instance) for kwargs in [ {'MSVC_UWP_APP': False, 'MSVC_SCRIPT_ARGS': None}, @@ -323,7 +329,7 @@ def run_msvc_script_args(self) -> None: {'MSVC_SPECTRE_LIBS': 'True', 'MSVC_SCRIPT_ARGS': '-vcvars_spectre_libs=spectre'}, # not boolean ignored ]: env = Environment(**kwargs) - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, msvc_instance) for msvc_uwp_app in (True, False): @@ -368,9 +374,9 @@ def run_msvc_script_args(self) -> None: env = Environment(**kwargs) if exc: with self.assertRaises(MSVCArgumentError): - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, msvc_instance) else: - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, msvc_instance) else: @@ -379,14 +385,14 @@ def run_msvc_script_args(self) -> None: {'MSVC_SDK_VERSION': sdk_def.sdk_version, 'MSVC_UWP_APP': msvc_uwp_app}, ]: env = Environment(**kwargs) - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, msvc_instance) for kwargs in [ {'MSVC_SCRIPT_ARGS': '-vcvars_ver={}'.format(version_def.msvc_verstr)}, {'MSVC_TOOLSET_VERSION': version_def.msvc_verstr}, ]: env = Environment(**kwargs) - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, msvc_instance) msvc_toolset_notfound_version = Data.msvc_toolset_notfound_version(version_def.msvc_version) @@ -398,7 +404,7 @@ def run_msvc_script_args(self) -> None: ]: env = Environment(**kwargs) with self.assertRaises(MSVCToolsetVersionNotFound): - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, msvc_instance) msvc_sdk_notfound_version = Data.msvc_sdk_notfound_version(version_def.msvc_version) @@ -407,15 +413,15 @@ def run_msvc_script_args(self) -> None: ]: env = Environment(**kwargs) with self.assertRaises(MSVCSDKVersionNotFound): - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, msvc_instance) have_spectre = toolset_def.msvc_toolset_version in Data.SPECTRE_TOOLSET_VERSIONS.get(version_def.msvc_version,[]) env = Environment(MSVC_SPECTRE_LIBS=True, MSVC_TOOLSET_VERSION=toolset_def.msvc_toolset_version) if not have_spectre: with self.assertRaises(MSVCSpectreLibsNotFound): - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, msvc_instance) else: - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, msvc_instance) msvc_sdk_version = Data.msvc_sdk_version(version_def.msvc_version) @@ -519,25 +525,90 @@ def run_msvc_script_args(self) -> None: ] + more_tests: env = Environment(**kwargs) with self.assertRaises(exc_t): - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, msvc_instance) elif version_def.msvc_verstr == '14.0': - # VS2015: MSVC_SDK_VERSION and MSVC_UWP_APP + + if msvc_instance.is_express: + sdk_supported = False + uwp_supported = True # based on target arch + elif msvc_instance.is_buildtools: + sdk_supported = False + uwp_supported = False + else: + sdk_supported = True + uwp_supported = True env = Environment(MSVC_SCRIPT_ARGS='undefinedsymbol') - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, msvc_instance) - for msvc_uwp_app in (True, False): + if sdk_supported: + # VS2015: MSVC_SDK_VERSION + + if uwp_supported: + # VS2015: MSVC_UWP_APP + + for msvc_uwp_app in (True, False): + + sdk_list = WinSDK.get_msvc_sdk_version_list(version_def.msvc_version, msvc_uwp_app=msvc_uwp_app) + for sdk_version in sdk_list: + + for kwargs in [ + {'MSVC_SCRIPT_ARGS': sdk_version, 'MSVC_UWP_APP': msvc_uwp_app}, + {'MSVC_SDK_VERSION': sdk_version, 'MSVC_UWP_APP': msvc_uwp_app}, + ]: + env = Environment(**kwargs) + _ = func(env, msvc_instance) + + else: + # VS2015: MSVC_UWP_APP error + + for msvc_uwp_app in (True,): + + sdk_list = WinSDK.get_msvc_sdk_version_list(version_def.msvc_version, msvc_uwp_app=msvc_uwp_app) + for sdk_version in sdk_list: + + for kwargs in [ + {'MSVC_SCRIPT_ARGS': sdk_version, 'MSVC_UWP_APP': msvc_uwp_app}, + {'MSVC_SDK_VERSION': sdk_version, 'MSVC_UWP_APP': msvc_uwp_app}, + ]: + env = Environment(**kwargs) + with self.assertRaises(MSVCArgumentError): + _ = func(env, msvc_instance) + + else: + # VS2015: MSVC_SDK_VERSION error sdk_list = WinSDK.get_msvc_sdk_version_list(version_def.msvc_version, msvc_uwp_app=msvc_uwp_app) for sdk_version in sdk_list: - for kwargs in [ - {'MSVC_SCRIPT_ARGS': sdk_version, 'MSVC_UWP_APP': msvc_uwp_app}, - {'MSVC_SDK_VERSION': sdk_version, 'MSVC_UWP_APP': msvc_uwp_app}, - ]: - env = Environment(**kwargs) - _ = func(env, version_def.msvc_version, vc_dir, '') + env = Environment(MSVC_SDK_VERSION=sdk_version) + with self.assertRaises(MSVCArgumentError): + _ = func(env, msvc_instance) + + # MSVC_SCRIPT_ARGS sdk_version not validated + env = Environment(MSVC_SCRIPT_ARGS=sdk_version) + _ = func(env, msvc_instance) + + if uwp_supported: + # VS2015: MSVC_UWP_APP + + for msvc_uwp_app in (True, False): + env = Environment(MSVC_UWP_APP=msvc_uwp_app) + _ = func(env, msvc_instance) + + else: + # VS2015: MSVC_UWP_APP error + + for msvc_uwp_app in (True,): + + env = Environment(MSVC_UWP_APP=msvc_uwp_app) + with self.assertRaises(MSVCArgumentError): + _ = func(env, msvc_instance) + + # MSVC_SCRIPT_ARGS store not validated + env = Environment(MSVC_SCRIPT_ARGS='store') + _ = func(env, msvc_instance) for kwargs in [ {'MSVC_SPECTRE_LIBS': True, 'MSVC_SCRIPT_ARGS': None}, @@ -545,14 +616,14 @@ def run_msvc_script_args(self) -> None: ]: env = Environment(**kwargs) with self.assertRaises(MSVCArgumentError): - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, msvc_instance) else: # VS2013 and earlier: no arguments env = Environment(MSVC_SCRIPT_ARGS='undefinedsymbol') with self.assertRaises(MSVCArgumentError): - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, msvc_instance) for kwargs in [ {'MSVC_UWP_APP': True, 'MSVC_SCRIPT_ARGS': None}, @@ -563,7 +634,7 @@ def run_msvc_script_args(self) -> None: ]: env = Environment(**kwargs) with self.assertRaises(MSVCArgumentError): - _ = func(env, version_def.msvc_version, vc_dir, '') + _ = func(env, msvc_instance) def test_msvc_script_args_none(self) -> None: force = ScriptArguments.msvc_force_default_arguments(force=False) diff --git a/SCons/Tool/MSCommon/MSVC/Util.py b/SCons/Tool/MSCommon/MSVC/Util.py index 6fd188bc35..0c2e051848 100644 --- a/SCons/Tool/MSCommon/MSVC/Util.py +++ b/SCons/Tool/MSCommon/MSVC/Util.py @@ -37,6 +37,44 @@ from . import Config +# class utilities + +class AutoInitialize: + # Automatically call _initialize classmethod upon class definition completion. + def __init_subclass__(cls, **kwargs): + super().__init_subclass__(**kwargs) + if hasattr(cls, '_initialize') and callable(getattr(cls, '_initialize', None)): + cls._initialize() + +# env utilities + +def env_query(env, key, default=None, subst=False): + """ + Query a mapping object for the value associated with the key. + + Args: + env: Mapping[str, object] + mapping for key retrieval + key: str + key for object retrieval + default: object + return value when key is not in mapping + subst: bool + call substr method if it exists when True + + Returns: + object: value retrieved from mapping or the default value + + """ + + if not env or key not in env: + rval = default + elif subst and hasattr(env, 'subst') and callable(getattr(env, 'subst', None)): + rval = env.subst('$' + key) + else: + rval = env[key] + return rval + # path utilities # windows drive specification (e.g., 'C:') @@ -234,6 +272,35 @@ def get_msvc_version_prefix(version): rval = m.group('version') return rval +def get_msvc_version_prefix_suffix(version): + """ + Get the msvc version number prefix and suffix from a string. + + Args: + version: str + version specification + + Returns: + (str, str): the msvc version prefix and suffix + + """ + prefix = suffix = '' + if version: + m = re_msvc_version.match(version) + if m: + prefix = m.group('msvc_version') + suffix = m.group('suffix') if m.group('suffix') else '' + return prefix, suffix + +# msvc version query utilities + +def is_version_valid(version) -> bool: + rval = False + if version: + if re_msvc_version.match(version): + rval = True + return rval + # toolset version query utilities def is_toolset_full(toolset_version) -> bool: @@ -297,8 +364,8 @@ def msvc_version_components(vcver): if not m: return None - vs_def = Config.MSVC_VERSION_SUFFIX.get(vcver) - if not vs_def: + vs_product_def = Config.MSVC_VERSION_SUFFIX.get(vcver) + if not vs_product_def: return None msvc_version = vcver @@ -363,8 +430,8 @@ def msvc_extended_version_components(version): msvc_suffix = m.group('suffix') if m.group('suffix') else '' msvc_version = msvc_verstr + msvc_suffix - vs_def = Config.MSVC_VERSION_SUFFIX.get(msvc_version) - if not vs_def: + vs_product_def = Config.MSVC_VERSION_SUFFIX.get(msvc_version) + if not vs_product_def: return None msvc_vernum = float(msvc_verstr) diff --git a/SCons/Tool/MSCommon/MSVC/VSDetect.py b/SCons/Tool/MSCommon/MSVC/VSDetect.py new file mode 100644 index 0000000000..8962b0c2f3 --- /dev/null +++ b/SCons/Tool/MSCommon/MSVC/VSDetect.py @@ -0,0 +1,2569 @@ +# 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. + +""" +Visual Studio detection for Microsoft Visual C/C++. +""" + +import json +import os +import re +import subprocess + +from collections import namedtuple +from functools import cmp_to_key + +import SCons.Util +import SCons.Warnings + +from .. import common + +from ..common import ( + debug, + debug_extra, + DEBUG_ENABLED, +) + +from . import Config +from . import Exceptions +from . import Options +from . import Util +from . import Registry +from . import Validate +from . import VSWhere +from . import Warnings + +from . import Dispatcher +Dispatcher.register_modulename(__name__) + +# TODO(JCB): temporary + +_msvc_instance_check_files_exist = None + +def register_msvc_instance_check_files_exist(func): + global _msvc_instance_check_files_exist + _msvc_instance_check_files_exist = func + +class _VSUtil(Util.AutoInitialize): + + debug_extra = None + + # cached values + _normalized_path = {} + _path_exists = {} + + @classmethod + def reset(cls) -> None: + cls._normalized_path = {} + cls._path_exists = {} + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + cls.reset() + + # normalized paths + + @classmethod + def normalize_path(cls, pval): + rval = cls._normalized_path.get(pval, Config.UNDEFINED) + if rval == Config.UNDEFINED: + rval = Util.normalize_path(pval) + cls._normalized_path[pval] = rval + debug('norm=%s, pval=%s', repr(rval), repr(pval), extra=cls.debug_extra) + return rval + + # path existence + + @classmethod + def path_exists(cls, pval): + rval = cls._path_exists.get(pval, Config.UNDEFINED) + if rval == Config.UNDEFINED: + rval = os.path.exists(pval) + cls._path_exists[pval] = rval + debug('exists=%s, pval=%s', repr(rval), repr(pval), extra=cls.debug_extra) + return rval + + +class _VSConfig: + + # MSVS IDE binaries + + BITFIELD_DEVELOP = 0b_1000 + BITFIELD_EXPRESS = 0b_0100 + BITFIELD_EXPRESS_WIN = 0b_0010 + BITFIELD_EXPRESS_WEB = 0b_0001 + + EXECUTABLE_MASK = BITFIELD_DEVELOP | BITFIELD_EXPRESS + + VSProgram = namedtuple("VSProgram", [ + 'program', + 'bitfield' + ]) + + DEVENV_COM = VSProgram(program='devenv.com', bitfield=BITFIELD_DEVELOP) + MSDEV_COM = VSProgram(program='msdev.com', bitfield=BITFIELD_DEVELOP) + + WDEXPRESS_EXE = VSProgram(program='WDExpress.exe', bitfield=BITFIELD_EXPRESS) + VCEXPRESS_EXE = VSProgram(program='VCExpress.exe', bitfield=BITFIELD_EXPRESS) + + VSWINEXPRESS_EXE = VSProgram(program='VSWinExpress.exe', bitfield=BITFIELD_EXPRESS_WIN) + VWDEXPRESS_EXE = VSProgram(program='VWDExpress.exe', bitfield=BITFIELD_EXPRESS_WEB) + + # MSVS IDE binary + + VSBinaryConfig = namedtuple("VSBinaryConfig", [ + 'pathcomp', # vs root -> ide dir + 'programs', # ide binaries + ]) + + # MSVS batch file + + VSBatchConfig = namedtuple("VSBatchConfig", [ + 'pathcomp', # vs root -> batch dir + 'script', # vs script + ]) + + # detect configuration + + DetectConfig = namedtuple("DetectConfig", [ + 'vs_cfg', # vs configuration + 'vc_cfg', # vc configuration + ]) + + VSDetectConfig = namedtuple("VSDetectConfig", [ + 'root', # relative path vc dir -> vs root + 'vs_binary_cfg', # vs executable + 'vs_batch_cfg', # vs batch file + 'vc_folder', # vc folder name + ]) + + VCDetectConfigRegistry = namedtuple("VCDetectConfigRegistry", [ + 'regkeys', + ]) + + _VCRegistryKey = namedtuple("_VCRegistryKey", [ + 'hkroot', + 'key', + 'is_vsroot', + 'is_vcforpython', + ]) + + class VCRegKey(_VCRegistryKey): + + @classmethod + def factory( + cls, *, + key, + hkroot=SCons.Util.HKEY_LOCAL_MACHINE, + is_vsroot=False, + is_vcforpython=False, + ): + + regkey = cls( + hkroot=hkroot, + key=key, + is_vsroot=is_vsroot, + is_vcforpython=is_vcforpython, + ) + + return regkey + + _regkey = VCRegKey.factory + + # vs detect configuration: vswhere + + DETECT_VSWHERE = { + + '2022': DetectConfig( # 14.3 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + script='VsDevCmd.bat', + ), + vc_folder='VC', + ), + vc_cfg=None, + ), + + '2019': DetectConfig( # 14.2 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + script='VsDevCmd.bat', + ), + vc_folder='VC', + ), + vc_cfg=None, + ), + + '2017': DetectConfig( # 14.1 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM, WDEXPRESS_EXE], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + script='VsDevCmd.bat', + ), + vc_folder='VC', + ), + vc_cfg=None, + ), + + } + + # vs detect configuration: registry + + DETECT_REGISTRY = { + + '2015': DetectConfig( # 14.0 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM, WDEXPRESS_EXE, VSWINEXPRESS_EXE, VWDEXPRESS_EXE], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + # VsDevCmd.bat and vsvars32.bat appear to be different + script='vsvars32.bat', + ), + vc_folder='VC', + ), + vc_cfg=VCDetectConfigRegistry( + regkeys=[ + _regkey(key=r'Microsoft\VisualStudio\SxS\VS7\14.0', is_vsroot=True), + _regkey(key=r'Microsoft\VisualStudio\14.0\Setup\VS\ProductDir', is_vsroot=True), + _regkey(key=r'Microsoft\WDExpress\14.0\Setup\VS\ProductDir', is_vsroot=True), + _regkey(key=r'Microsoft\VisualStudio\SxS\VC7\14.0'), + _regkey(key=r'Microsoft\VisualStudio\14.0\Setup\VC\ProductDir'), + _regkey(key=r'Microsoft\VCExpress\14.0\Setup\VC\ProductDir'), # key not defined + ], + ), + ), + + '2013': DetectConfig( # 12.0 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM, WDEXPRESS_EXE, VSWINEXPRESS_EXE, VWDEXPRESS_EXE], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + # VsDevCmd.bat and vsvars32.bat appear to be different + script='vsvars32.bat', + ), + vc_folder='VC', + ), + vc_cfg=VCDetectConfigRegistry( + regkeys=[ + _regkey(key=r'Microsoft\VisualStudio\SxS\VS7\12.0', is_vsroot=True), + _regkey(key=r'Microsoft\VisualStudio\12.0\Setup\VS\ProductDir', is_vsroot=True), + _regkey(key=r'Microsoft\WDExpress\12.0\Setup\VS\ProductDir', is_vsroot=True), + _regkey(key=r'Microsoft\VisualStudio\SxS\VC7\12.0'), + _regkey(key=r'Microsoft\VisualStudio\12.0\Setup\VC\ProductDir'), + _regkey(key=r'Microsoft\VCExpress\12.0\Setup\VC\ProductDir'), + ] + ), + ), + + '2012': DetectConfig( # 11.0 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM, WDEXPRESS_EXE, VSWINEXPRESS_EXE, VWDEXPRESS_EXE], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + # VsDevCmd.bat and vsvars32.bat appear to be identical + script='vsvars32.bat', + ), + vc_folder='VC', + ), + vc_cfg=VCDetectConfigRegistry( + regkeys=[ + _regkey(key=r'Microsoft\VisualStudio\SxS\VS7\11.0', is_vsroot=True), + _regkey(key=r'Microsoft\VisualStudio\11.0\Setup\VS\ProductDir', is_vsroot=True), + _regkey(key=r'Microsoft\WDExpress\11.0\Setup\VS\ProductDir', is_vsroot=True), + _regkey(key=r'Microsoft\VisualStudio\SxS\VC7\11.0'), + _regkey(key=r'Microsoft\VisualStudio\11.0\Setup\VC\ProductDir'), + _regkey(key=r'Microsoft\VCExpress\11.0\Setup\VC\ProductDir'), + ] + ), + ), + + '2010': DetectConfig( # 10.0 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM, VCEXPRESS_EXE], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + script='vsvars32.bat', + ), + vc_folder='VC', + ), + vc_cfg=VCDetectConfigRegistry( + regkeys=[ + _regkey(key=r'Microsoft\VisualStudio\SxS\VS7\10.0', is_vsroot=True), + _regkey(key=r'Microsoft\VisualStudio\10.0\Setup\VS\ProductDir', is_vsroot=True), + _regkey(key=r'Microsoft\VCExpress\10.0\Setup\VS\ProductDir', is_vsroot=True), + _regkey(key=r'Microsoft\VisualStudio\SxS\VC7\10.0'), + _regkey(key=r'Microsoft\VisualStudio\10.0\Setup\VC\ProductDir'), + _regkey(key=r'Microsoft\VCExpress\10.0\Setup\VC\ProductDir'), + ] + ), + ), + + '2008': DetectConfig( # 9.0 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM, VCEXPRESS_EXE], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + script='vsvars32.bat', + ), + vc_folder='VC', + ), + vc_cfg=VCDetectConfigRegistry( + regkeys=[ + _regkey( + hkroot=SCons.Util.HKEY_CURRENT_USER, + key=r'Microsoft\DevDiv\VCForPython\9.0\InstallDir', is_vsroot=True, + is_vcforpython=True, + ), + _regkey( + key=r'Microsoft\DevDiv\VCForPython\9.0\InstallDir', is_vsroot=True, + is_vcforpython=True + ), + _regkey(key=r'Microsoft\VisualStudio\SxS\VS7\9.0', is_vsroot=True), + _regkey(key=r'Microsoft\VisualStudio\9.0\Setup\VS\ProductDir', is_vsroot=True), + _regkey(key=r'Microsoft\VCExpress\9.0\Setup\VS\ProductDir', is_vsroot=True), + _regkey(key=r'Microsoft\VisualStudio\SxS\VC7\9.0'), + _regkey(key=r'Microsoft\VisualStudio\9.0\Setup\VC\ProductDir'), + _regkey(key=r'Microsoft\VCExpress\9.0\Setup\VC\ProductDir'), + ] + ), + ), + + '2005': DetectConfig( # 8.0 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM, VCEXPRESS_EXE], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + script='vsvars32.bat', + ), + vc_folder='VC', + ), + vc_cfg=VCDetectConfigRegistry( + regkeys=[ + _regkey(key=r'Microsoft\VisualStudio\SxS\VS7\8.0', is_vsroot=True), + _regkey(key=r'Microsoft\VisualStudio\8.0\Setup\VS\ProductDir', is_vsroot=True), + _regkey(key=r'Microsoft\VCExpress\8.0\Setup\VS\ProductDir', is_vsroot=True), + _regkey(key=r'Microsoft\VisualStudio\SxS\VC7\8.0'), + _regkey(key=r'Microsoft\VisualStudio\8.0\Setup\VC\ProductDir'), + _regkey(key=r'Microsoft\VCExpress\8.0\Setup\VC\ProductDir'), + ] + ), + ), + + '2003': DetectConfig( # 7.1 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + script='vsvars32.bat', + ), + vc_folder='VC7', + ), + vc_cfg=VCDetectConfigRegistry( + regkeys=[ + _regkey(key=r'Microsoft\VisualStudio\SxS\VS7\7.1', is_vsroot=True), + _regkey(key=r'Microsoft\VisualStudio\7.1\Setup\VS\ProductDir', is_vsroot=True), + _regkey(key=r'Microsoft\VisualStudio\SxS\VC7\7.1'), + _regkey(key=r'Microsoft\VisualStudio\7.1\Setup\VC\ProductDir'), + ] + ), + ), + + '2002': DetectConfig( # 7.0 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common7\IDE', + programs=[DEVENV_COM], + ), + vs_batch_cfg=VSBatchConfig( + pathcomp=r'Common7\Tools', + script='vsvars32.bat', + ), + vc_folder='VC7', + ), + vc_cfg=VCDetectConfigRegistry( + regkeys=[ + _regkey(key=r'Microsoft\VisualStudio\7.0\Setup\VS\ProductDir', is_vsroot=True), + _regkey(key=r'Microsoft\VisualStudio\7.0\Setup\VC\ProductDir'), + ] + ), + ), + + '1998': DetectConfig( # 6.0 + vs_cfg=VSDetectConfig( + root=os.pardir, + vs_binary_cfg=VSBinaryConfig( + pathcomp=r'Common\MSDev98\Bin', + programs=[MSDEV_COM], + ), + vs_batch_cfg=VSBatchConfig( + # There is no vsvars32.bat batch file + pathcomp=r'VC98\bin', + script='vcvars32.bat', + ), + vc_folder='VC98', + ), + vc_cfg=VCDetectConfigRegistry( + regkeys=[ + _regkey(key=r'Microsoft\VisualStudio\6.0\Setup\Microsoft Visual C++\ProductDir'), + ] + ), + ), + + } + + +class _VSChannel(Util.AutoInitialize): + + # TODO(JCB): review reset + + # priority: cmdline > environ > user > initial + + debug_extra = None + + vs_channel_initial = None + vs_channel_cmdline = None + + # reset + + vs_channel_user = None + vs_channel_def = None + vs_channel_retrieved = False + + @classmethod + def _initial(cls) -> None: + cls.vs_channel_initial = Config.MSVS_CHANNEL_RELEASE + cls.vs_channel_def = cls.vs_channel_initial + debug('vs_channel_initial=%s', + cls.vs_channel_initial.vs_channel_id, + extra=cls.debug_extra + ) + + @classmethod + def _cmdline(cls) -> None: + channel, option = Options.msvs_channel() + if channel: + vs_channel_def = Validate.validate_msvs_channel( + channel, option + ) + if vs_channel_def: + cls.vs_channel_cmdline = vs_channel_def + cls.vs_channel_def = cls.vs_channel_cmdline + debug('vs_channel_cmdline=%s', + cls.vs_channel_cmdline.vs_channel_id, + extra=cls.debug_extra + ) + + @classmethod + def _setup(cls) -> None: + cls._initial() + cls._cmdline() + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + cls._setup() + + @classmethod + def reset(cls) -> None: + cls.vs_channel_user = None + cls.vs_channel_def = None + cls.vs_channel_retrieved = False + cls._setup() + + @classmethod + def get_default_channel(cls): + if not cls.vs_channel_retrieved: + cls.vs_channel_retrieved = True + if cls.vs_channel_cmdline: + cls.vs_channel_def = cls.vs_channel_cmdline + elif cls.vs_channel_user: + cls.vs_channel_def = cls.vs_channel_user + else: + cls.vs_channel_def = cls.vs_channel_initial + debug( + 'vs_channel=%s', + cls.vs_channel_def.vs_channel_id, + extra=cls.debug_extra + ) + return cls.vs_channel_def + + @classmethod + def set_default_channel(cls, msvs_channel, source) -> bool: + + rval = False + + if cls.vs_channel_retrieved: + + warn_msg = f'msvs channel {msvs_channel!r} ({source}) ignored: must be set before first use.' + debug(warn_msg, extra=cls.debug_extra) + SCons.Warnings.warn(Warnings.MSVSChannelWarning, warn_msg) + + else: + + vs_channel_def = Validate.validate_msvs_channel(msvs_channel, source) + if vs_channel_def: + + cls.vs_channel_user = vs_channel_def + debug( + 'vs_channel_user=%s', + cls.vs_channel_user.vs_channel_id, + extra=cls.debug_extra + ) + + if not cls.vs_channel_cmdline: + cls.vs_channel_def = cls.vs_channel_user + debug( + 'vs_channel=%s', + cls.vs_channel_def.vs_channel_id, + extra=cls.debug_extra + ) + rval = True + + debug( + 'vs_channel=%s', + cls.vs_channel_def.vs_channel_id, + extra=cls.debug_extra + ) + + return rval + +def msvs_set_channel_default(msvs_channel) -> bool: + """Set the default msvs channel. + + Args: + msvs_channel: str + string representing the msvs channel + + Case-insensitive values are: + Release, Rel, Preview, Pre, Any + + Returns: + bool: True if the default msvs channel was accepted. + False if the default msvs channel was not accepted. + + """ + rval = _VSChannel.set_default_channel(msvs_channel, 'msvs_set_channel_default') + return rval + +def msvs_get_channel_default(): + """Get the default msvs channel. + + Returns: + str: default msvs channel + + """ + return _VSChannel.vs_channel_def.vs_channel_id + + +class _VSKeys(Util.AutoInitialize): + + debug_extra = None + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + + # edition key: (product, channel, component/None, seqnbr/None) + + _MSVSEditionKey = namedtuple('_MSVSEditionKey', [ + 'vs_product_def', + 'vs_channel_def', + 'vs_componentid_def', + 'vs_sequence_nbr', + ]) + + class MSVSEditionKey(_MSVSEditionKey): + + def serialize(self): + values = [ + self.vs_product_def.vs_product, + self.vs_channel_def.vs_channel_suffix, + ] + if self.vs_componentid_def: + values.append(self.vs_componentid_def.vs_component_suffix) + if self.vs_sequence_nbr: + values.append(str(self.vs_sequence_nbr)) + rval = '-'.join(values) + return rval + + @classmethod + def factory( + cls, *, + vs_product_def, + vs_channel_def, + vs_componentid_def, + vs_sequence_nbr, + ): + + vs_edition_key = cls( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + vs_sequence_nbr=vs_sequence_nbr + ) + + return vs_edition_key + + @classmethod + def msvs_edition_key( + cls, *, + vs_product_def=None, + vs_channel_def=None, + vs_componentid_def=None, + vs_sequence_nbr=None, + ): + + if not vs_product_def: + errmsg = 'vs_product_def is undefined' + debug('MSVCInternalError: %s', errmsg, extra=cls.debug_extra) + raise Exceptions.MSVCInternalError(errmsg) + + if not vs_channel_def: + errmsg = 'vs_channel_def is undefined' + debug('MSVCInternalError: %s', errmsg, extra=cls.debug_extra) + raise Exceptions.MSVCInternalError(errmsg) + + vs_edition_key = cls.MSVSEditionKey.factory( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + vs_sequence_nbr=vs_sequence_nbr + ) + + return vs_edition_key + + # channel key: (channel, component/None) + + _MSVSChannelKey = namedtuple('_MSVSChannelKey', [ + 'vs_channel_def', + 'vs_componentid_def', + ]) + + class MSVSChannelKey(_MSVSChannelKey): + + def serialize(self): + values = [ + self.vs_channel_def.vs_channel_suffix, + ] + if self.vs_componentid_def: + values.append(self.vs_componentid_def.vs_component_suffix) + rval = '-'.join(values) + return rval + + @classmethod + def factory( + cls, *, + vs_channel_def, + vs_componentid_def, + ): + + vs_channel_key = cls( + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + ) + + return vs_channel_key + + @classmethod + def msvs_channel_key( + cls, *, + vs_channel_def=None, + vs_componentid_def=None, + ): + + if not vs_channel_def: + errmsg = 'vs_channel_def is undefined' + debug('MSVCInternalError: %s', errmsg, extra=cls.debug_extra) + raise Exceptions.MSVCInternalError(errmsg) + + vs_channel_key = cls.MSVSChannelKey.factory( + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + ) + + return vs_channel_key + + +_MSVSBase = namedtuple('_MSVSBase', [ + 'id_str', + 'id_comps', + 'vs_product_def', + 'vs_channel_def', + 'vs_component_def', + 'vs_sequence_nbr', + 'vs_dir', + 'vs_dir_norm', + 'vs_version', + 'is_express', + 'is_buildtools', + 'is_vcforpython', + 'vs_edition_channel_component_seqnbr_key', + 'vs_edition_channel_component_key', + 'vs_edition_channel_key', + 'vs_channel_component_key', + 'vs_channel_key', + 'instance_map', +]) + +class MSVSBase(_MSVSBase): + + def _is_express(vs_component_def) -> bool: + vs_componentid_def = vs_component_def.vs_componentid_def + is_express = bool(vs_componentid_def == Config.MSVS_COMPONENTID_EXPRESS) + return is_express + + @staticmethod + def _is_buildtools(vs_component_def) -> bool: + vs_componentid_def = vs_component_def.vs_componentid_def + is_buildtools = bool(vs_componentid_def == Config.MSVS_COMPONENTID_BUILDTOOLS) + return is_buildtools + + @staticmethod + def _is_vcforpython(vs_component_def) -> bool: + vs_componentid_def = vs_component_def.vs_componentid_def + is_vcforpython = bool(vs_componentid_def == Config.MSVS_COMPONENTID_PYTHON) + return is_vcforpython + + @classmethod + def factory( + cls, *, + vs_product_def, + vs_channel_def, + vs_component_def, + vs_sequence_nbr, + vs_dir, + vs_version, + ): + + vs_componentid_def = vs_component_def.vs_componentid_def + + vs_edition_channel_component_seqnbr_key = _VSKeys.msvs_edition_key( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + vs_sequence_nbr=vs_sequence_nbr, + ) + + vs_edition_channel_component_key = _VSKeys.msvs_edition_key( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + ) + + vs_edition_channel_key = _VSKeys.msvs_edition_key( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + ) + + vs_channel_component_key = _VSKeys.msvs_channel_key( + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + ) + + vs_channel_key = _VSKeys.msvs_channel_key( + vs_channel_def=vs_channel_def, + ) + + instance_map = { + 'msvs_instance': None, + 'msvc_instance': None, + } + + id_comps = ( + vs_product_def.vs_product, + vs_channel_def.vs_channel_suffix, + vs_component_def.vs_componentid_def.vs_component_suffix, + str(vs_sequence_nbr), + ) + + id_str = '{}({})'.format(cls.__name__, ', '.join(id_comps)) + + msvs_base = cls( + id_str=id_str, + id_comps=id_comps, + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_component_def=vs_component_def, + vs_sequence_nbr=vs_sequence_nbr, + vs_dir=vs_dir, + vs_dir_norm=_VSUtil.normalize_path(vs_dir), + vs_version=vs_version, + is_express=cls._is_express(vs_component_def), + is_buildtools=cls._is_buildtools(vs_component_def), + is_vcforpython=cls._is_vcforpython(vs_component_def), + vs_edition_channel_component_seqnbr_key=vs_edition_channel_component_seqnbr_key, + vs_edition_channel_component_key=vs_edition_channel_component_key, + vs_edition_channel_key=vs_edition_channel_key, + vs_channel_component_key=vs_channel_component_key, + vs_channel_key=vs_channel_key, + instance_map=instance_map, + ) + + return msvs_base + + @staticmethod + def default_order(a, b): + # vs product numeric: descending order + if a.vs_product_def.vs_product_numeric != b.vs_product_def.vs_product_numeric: + return 1 if a.vs_product_def.vs_product_numeric < b.vs_product_def.vs_product_numeric else -1 + # vs channel: ascending order (release, preview) + if a.vs_channel_def.vs_channel_rank != b.vs_channel_def.vs_channel_rank: + return 1 if a.vs_channel_def.vs_channel_rank > b.vs_channel_def.vs_channel_rank else -1 + # component rank: descending order + if a.vs_component_def.vs_component_rank != b.vs_component_def.vs_component_rank: + return 1 if a.vs_component_def.vs_component_rank < b.vs_component_def.vs_component_rank else -1 + # sequence number: ascending order + if a.vs_sequence_nbr != b.vs_sequence_nbr: + return 1 if a.vs_sequence_nbr > b.vs_sequence_nbr else -1 + return 0 + + def register_msvs_instance(self, msvs_instance) -> None: + self.instance_map['msvs_instance'] = msvs_instance + + def register_msvc_instance(self, msvc_instance) -> None: + self.instance_map['msvc_instance'] = msvc_instance + + @property + def msvs_instance(self): + return self.instance_map.get('msvs_instance') + + @property + def msvc_instance(self): + return self.instance_map.get('msvc_instance') + + @property + def vs_component_suffix(self): + return self.vs_component_def.vs_componentid_def.vs_component_suffix + + +_MSVSInstance = namedtuple('_MSVSInstance', [ + 'id_str', + 'msvs_base', + 'vs_executable', + 'vs_executable_norm', + 'vs_script', + 'vs_script_norm', + 'vc_version_def', +]) + +class MSVSInstance(_MSVSInstance): + + @classmethod + def factory( + cls, *, + msvs_base, + vs_executable, + vs_script, + vc_version_def, + ): + + id_str = '{}({})'.format(cls.__name__, ', '.join(msvs_base.id_comps)) + + msvs_instance = cls( + id_str=id_str, + msvs_base=msvs_base, + vs_executable=vs_executable, + vs_executable_norm=_VSUtil.normalize_path(vs_executable), + vs_script=vs_script, + vs_script_norm=_VSUtil.normalize_path(vs_script), + vc_version_def=vc_version_def, + ) + + return msvs_instance + + @staticmethod + def default_order(a, b): + return MSVSBase.default_order(a.msvs_base, b.msvs_base) + + @property + def id_comps(self): + return self.msvs_base.id_comps + + @property + def msvc_instance(self): + return self.msvs_base.msvc_instance + + @property + def vs_dir(self): + return self.msvs_base.vs_dir + + @property + def vs_version(self): + return self.msvs_base.vs_version + + @property + def vs_edition_channel_component_seqnbr_key(self): + return self.msvs_base.vs_edition_channel_component_seqnbr_key + + @property + def vs_edition_channel_component_key(self): + return self.msvs_base.vs_edition_channel_component_key + + @property + def vs_edition_channel_key(self): + return self.msvs_base.vs_edition_channel_key + + @property + def vs_channel_component_key(self): + return self.msvs_base.vs_channel_component_key + + @property + def vs_channel_key(self): + return self.msvs_base.vs_channel_key + + @property + def msvc_version(self): + return self.vc_version_def.msvc_version + + @property + def msvc_verstr(self): + return self.vc_version_def.msvc_verstr + + @property + def vs_product_def(self): + return self.msvs_base.vs_product_def + + @property + def vs_channel_def(self): + return self.msvs_base.vs_channel_def + + @property + def vs_componentid_def(self): + return self.msvs_base.vs_component_def.vs_componentid_def + + @property + def vs_product(self): + return self.msvs_base.vs_product_def.vs_product + + @property + def vs_channel_id(self): + return self.msvs_base.vs_channel_def.vs_channel_id + + @property + def vs_component_id(self): + return self.msvs_base.vs_component_def.vs_componentid_def.vs_component_id + + @property + def vs_sequence_nbr(self): + return self.msvs_base.vs_sequence_nbr + + +_MSVSInstalled = namedtuple('_MSVSInstalled', [ + 'msvs_instances', + 'msvs_edition_instances_map', + 'msvs_channel_instances_map', + 'msvs_channel_map', # TODO(JCB): remove? +]) + +class MSVSInstalled(_MSVSInstalled, Util.AutoInitialize): + + debug_extra = None + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + + @classmethod + def factory( + cls, *, + msvs_instances, + ): + + msvs_instances = sorted( + msvs_instances, key=cmp_to_key(MSVSInstance.default_order) + ) + + msvs_channel_map = { + vs_channel_def: {} + for vs_channel_def in Config.MSVS_CHANNEL_DEFINITION_LIST + } + + vs_edition_instances = {} + vs_channel_instances = {} + + # channel key: (ANY, None) + vs_anychannel_key = _VSKeys.msvs_channel_key( + vs_channel_def=Config.MSVS_CHANNEL_ANY, + ) + + for msvs_instance in msvs_instances: + + debug( + 'msvs instance: id_str=%s, msvc_version=%s, vs_dir=%s, vs_exec=%s', + repr(msvs_instance.id_str), + repr(msvs_instance.msvc_version), + repr(msvs_instance.vs_dir), + repr(msvs_instance.vs_executable_norm), + extra=cls.debug_extra, + ) + + # edition key: (product, ANY, None, None) + vs_edition_any_key = _VSKeys.msvs_edition_key( + vs_product_def=msvs_instance.vs_product_def, + vs_channel_def=Config.MSVS_CHANNEL_ANY, + ) + + # edition key: (product, ANY, component, None) + vs_edition_anychannel_component_key = _VSKeys.msvs_edition_key( + vs_product_def=msvs_instance.vs_product_def, + vs_channel_def=Config.MSVS_CHANNEL_ANY, + vs_componentid_def=msvs_instance.vs_componentid_def, + ) + + # all editions keys + vs_edition_keys = ( + vs_edition_any_key, + msvs_instance.vs_edition_channel_key, + vs_edition_anychannel_component_key, + msvs_instance.vs_edition_channel_component_key, + msvs_instance.vs_edition_channel_component_seqnbr_key, + ) + + # channel key: (ANY, component) + vs_anychannel_component_key = _VSKeys.msvs_channel_key( + vs_channel_def=Config.MSVS_CHANNEL_ANY, + vs_componentid_def=msvs_instance.vs_componentid_def, + ) + + # all channel keys + vs_channel_keys = ( + vs_anychannel_key, + msvs_instance.vs_channel_key, + vs_anychannel_component_key, + msvs_instance.vs_channel_component_key, + ) + + for vs_edition_key in vs_edition_keys: + vs_edition_instances.setdefault(vs_edition_key, []).append(msvs_instance) + + for vs_channel_key in vs_channel_keys: + vs_channel_instances.setdefault(vs_channel_key, []).append(msvs_instance) + + # msvc_version support + + if msvs_instance.msvc_version != msvs_instance.msvc_verstr: + versions = [msvs_instance.msvc_verstr, msvs_instance.msvc_version] + else: + versions = [msvs_instance.msvc_verstr] + + vs_channel_defs = Config.MSVS_CHANNEL_MEMBERLISTS[msvs_instance.vs_channel_def] + + for vcver in versions: + for vs_channel_def in vs_channel_defs: + + msvs_version_map = msvs_channel_map[vs_channel_def] + + msvs_instance_list = msvs_version_map.setdefault(vcver, []) + msvs_instance_list.append(msvs_instance) + + msvs_installed = cls( + msvs_instances=msvs_instances, + msvs_edition_instances_map=vs_edition_instances, + msvs_channel_instances_map=vs_channel_instances, + msvs_channel_map=msvs_channel_map, + ) + + if DEBUG_ENABLED: + msvs_installed._debug_dump() + + return msvs_installed + + def _debug_dump(self) -> None: + + for vs_channel_key, msvc_instance_list in self.msvs_channel_instances_map.items(): + msvc_instances = [msvc_instance.id_str for msvc_instance in msvc_instance_list] + debug( + 'msvs_channel_instances_map[%s]=%s', + repr(vs_channel_key.serialize()), + repr(msvc_instances), + extra=self.debug_extra, + ) + + for vs_edition_key, msvc_instance_list in self.msvs_edition_instances_map.items(): + msvc_instances = [msvc_instance.id_str for msvc_instance in msvc_instance_list] + debug( + 'msvs_edition_instances_map[%s]=%s', + repr(vs_edition_key.serialize()), + repr(msvc_instances), + extra=self.debug_extra, + ) + + for vs_channel_def, msvs_version_map in self.msvs_channel_map.items(): + for vcver, msvs_instance_list in msvs_version_map.items(): + msvs_instances = [msvs_instance.id_str for msvs_instance in msvs_instance_list] + debug( + 'msvs_version_map[%s][%s]=%s', + repr(vs_channel_def.vs_channel_suffix), + repr(vcver), + repr(msvs_instances), + extra=self.debug_extra, + ) + + def _query_instances_internal( + self, *, + vs_product_def=None, + vs_channel_def=None, + vs_componentid_def=None, + vs_sequence_nbr=None, + ): + + if not vs_channel_def: + vs_channel_def = _VSChannel.get_default_channel() + + if vs_product_def: + + query_key = _VSKeys.msvs_edition_key( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + vs_sequence_nbr=vs_sequence_nbr + ) + + msvs_instances = self.msvs_edition_instances_map.get(query_key, []) + + else: + + query_key = _VSKeys.msvs_channel_key( + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + ) + + msvs_instances = self.msvs_channel_instances_map.get(query_key, []) + + debug( + 'query_key=%s, n_msvs_instances=%s', + repr(query_key.serialize()), + repr(len(msvs_instances)), + extra=self.debug_extra, + ) + + return msvs_instances, query_key + + +_MSVCInstance = namedtuple('_MSVCInstance', [ + 'id_str', + 'msvs_base', + 'vc_version_def', + 'vc_feature_map', + 'vc_dir', + 'vc_dir_norm', + 'is_sdkversion_supported', +]) + +class MSVCInstance(_MSVCInstance, Util.AutoInitialize): + + debug_extra = None + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + + @classmethod + def factory( + cls, *, + msvs_base, + vc_version_def, + vc_feature_map, + vc_dir, + ): + + id_str = '{}({})'.format(cls.__name__, ', '.join(msvs_base.id_comps)) + + if vc_feature_map is None: + vc_feature_map = {} + + msvc_instance = cls( + id_str=id_str, + msvs_base=msvs_base, + vc_version_def=vc_version_def, + vc_feature_map=vc_feature_map, + vc_dir=vc_dir, + vc_dir_norm=_VSUtil.normalize_path(vc_dir), + is_sdkversion_supported=cls._is_sdkversion_supported(msvs_base), + ) + + return msvc_instance + + @staticmethod + def default_order(a, b): + return MSVSBase.default_order(a.msvs_base, b.msvs_base) + + @property + def id_comps(self): + return self.msvs_base.id_comps + + @property + def msvs_instance(self): + return self.msvs_base.msvs_instance + + @staticmethod + def _is_sdkversion_supported(msvs_base) -> bool: + + vs_componentid_def = msvs_base.vs_component_def.vs_componentid_def + vs_product_numeric = msvs_base.vs_product_def.vs_product_numeric + + if vs_product_numeric >= 2017: + # VS2017 and later + is_supported = True + elif vs_product_numeric == 2015: + # VS2015: + # False: Express, BuildTools + # True: Develop, CmdLine + if vs_componentid_def == Config.MSVS_COMPONENTID_EXPRESS: + is_supported = False + elif vs_componentid_def == Config.MSVS_COMPONENTID_BUILDTOOLS: + is_supported = False + else: + is_supported = True + else: + # VS2013 and earlier + is_supported = False + + return is_supported + + def skip_uwp_target(self, env) -> bool: + skip = False + if self.vc_feature_map: + target_arch = env.get('TARGET_ARCH') + uwp_target_is_supported = self.vc_feature_map.get('uwp_target_is_supported', {}) + is_supported = uwp_target_is_supported.get(target_arch, True) + skip = bool(not is_supported) + debug( + 'skip=%s, msvc_version=%s', + repr(skip), repr(self.vc_version_def.msvc_version), extra=self.debug_extra + ) + return skip + + def is_uwp_target_supported(self, target_arch=None) -> bool: + + vs_componentid_def = self.msvs_base.vs_component_def.vs_componentid_def + vs_product_numeric = self.msvs_base.vs_product_def.vs_product_numeric + + is_target = False + if vs_product_numeric >= 2017: + # VS2017 and later + is_supported = True + elif vs_product_numeric == 2015: + # VS2015: + # Maybe: Express + # False: BuildTools + # True: Develop, CmdLine + if vs_componentid_def == Config.MSVS_COMPONENTID_EXPRESS: + uwp_target_is_supported = self.vc_feature_map.get('uwp_target_is_supported', {}) + is_supported = uwp_target_is_supported.get(target_arch, True) + is_target = True + elif vs_componentid_def == Config.MSVS_COMPONENTID_BUILDTOOLS: + is_supported = False + else: + is_supported = True + else: + # VS2013 and earlier + is_supported = False + + debug( + 'is_supported=%s, is_target=%s, msvc_version=%s, msvs_component=%s, target_arch=%s', + is_supported, is_target, + repr(self.vc_version_def.msvc_version), + repr(vs_componentid_def.vs_component_id), + repr(target_arch), + extra=self.debug_extra, + ) + + return is_supported, is_target + + # convenience properties: reduce member lookup chain for readability + + @property + def is_express(self) -> bool: + return self.msvs_base.is_express + + @property + def is_buildtools(self) -> bool: + return self.msvs_base.is_buildtools + + @property + def is_vcforpython(self) -> bool: + return self.msvs_base.is_vcforpython + + @property + def vs_edition_channel_component_seqnbr_key(self): + return self.msvs_base.vs_edition_channel_component_seqnbr_key + + @property + def vs_edition_channel_component_key(self): + return self.msvs_base.vs_edition_channel_component_key + + @property + def vs_edition_channel_key(self): + return self.msvs_base.vs_edition_channel_key + + @property + def vs_channel_component_key(self): + return self.msvs_base.vs_channel_component_key + + @property + def vs_channel_key(self): + return self.msvs_base.vs_channel_key + + @property + def msvc_version(self): + return self.vc_version_def.msvc_version + + @property + def msvc_vernum(self): + return self.vc_version_def.msvc_vernum + + @property + def msvc_verstr(self): + return self.vc_version_def.msvc_verstr + + @property + def vs_product_def(self): + return self.msvs_base.vs_product_def + + @property + def vs_channel_def(self): + return self.msvs_base.vs_channel_def + + @property + def vs_componentid_def(self): + return self.msvs_base.vs_component_def.vs_componentid_def + + @property + def vs_product(self): + return self.msvs_base.vs_product_def.vs_product + + @property + def vs_product_numeric(self): + return self.msvs_base.vs_product_def.vs_product_numeric + + @property + def vs_channel_id(self): + return self.msvs_base.vs_channel_def.vs_channel_id + + @property + def vs_component_id(self): + return self.msvs_base.vs_component_def.vs_componentid_def.vs_component_id + + @property + def vs_sequence_nbr(self): + return self.msvs_base.vs_sequence_nbr + + +_MSVCInstalled = namedtuple('_MSVCInstalled', [ + 'msvc_instances', + 'msvs_edition_instances_map', + 'msvs_channel_instances_map', + 'msvs_channel_map', # TODO(JCB): remove? +]) + +class MSVCInstalled(_MSVCInstalled, Util.AutoInitialize): + + debug_extra = None + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + + @classmethod + def factory( + cls, *, + msvc_instances, + ): + + msvc_instances = sorted( + msvc_instances, key=cmp_to_key(MSVCInstance.default_order) + ) + + msvs_channel_map = { + vs_channel_def: {} + for vs_channel_def in Config.MSVS_CHANNEL_DEFINITION_LIST + } + + vs_edition_instances = {} + vs_channel_instances = {} + + # channel key: (ANY, None) + vs_anychannel_key = _VSKeys.msvs_channel_key( + vs_channel_def=Config.MSVS_CHANNEL_ANY, + ) + + for msvc_instance in msvc_instances: + + debug( + 'msvc_instance: id_str=%s, msvc_version=%s, vc_dir=%s', + repr(msvc_instance.id_str), + repr(msvc_instance.msvc_version), + repr(msvc_instance.vc_dir), + extra=cls.debug_extra, + ) + + # edition key: (product, ANY, None, None) + vs_edition_any_key = _VSKeys.msvs_edition_key( + vs_product_def=msvc_instance.vs_product_def, + vs_channel_def=Config.MSVS_CHANNEL_ANY, + ) + + # edition key: (product, ANY, component, None) + vs_edition_anychannel_component_key = _VSKeys.msvs_edition_key( + vs_product_def=msvc_instance.vs_product_def, + vs_channel_def=Config.MSVS_CHANNEL_ANY, + vs_componentid_def=msvc_instance.vs_componentid_def, + ) + + # all editions keys + vs_edition_keys = ( + vs_edition_any_key, + msvc_instance.vs_edition_channel_key, + vs_edition_anychannel_component_key, + msvc_instance.vs_edition_channel_component_key, + msvc_instance.vs_edition_channel_component_seqnbr_key, + ) + + # channel key: (ANY, component) + vs_anychannel_component_key = _VSKeys.msvs_channel_key( + vs_channel_def=Config.MSVS_CHANNEL_ANY, + vs_componentid_def=msvc_instance.vs_componentid_def, + ) + + # all channel keys + vs_channel_keys = ( + vs_anychannel_key, + msvc_instance.vs_channel_key, + vs_anychannel_component_key, + msvc_instance.vs_channel_component_key, + ) + + for vs_edition_key in vs_edition_keys: + vs_edition_instances.setdefault(vs_edition_key, []).append(msvc_instance) + + for vs_channel_key in vs_channel_keys: + vs_channel_instances.setdefault(vs_channel_key, []).append(msvc_instance) + + # msvc_version support + + if msvc_instance.msvc_version != msvc_instance.msvc_verstr: + versions = [msvc_instance.msvc_verstr, msvc_instance.msvc_version] + else: + versions = [msvc_instance.msvc_verstr] + + vs_channel_defs = Config.MSVS_CHANNEL_MEMBERLISTS[msvc_instance.vs_channel_def] + + for vcver in versions: + + for vs_channel_def in vs_channel_defs: + + msvc_version_map = msvs_channel_map[vs_channel_def] + + msvc_instance_list = msvc_version_map.setdefault(vcver, []) + msvc_instance_list.append(msvc_instance) + + msvc_installed = cls( + msvc_instances=msvc_instances, + msvs_edition_instances_map=vs_edition_instances, + msvs_channel_instances_map=vs_channel_instances, + msvs_channel_map=msvs_channel_map, + ) + + if DEBUG_ENABLED: + msvc_installed._debug_dump() + + return msvc_installed + + def _debug_dump(self) -> None: + + for vs_channel_key, msvc_instance_list in self.msvs_channel_instances_map.items(): + msvc_instances = [msvc_instance.id_str for msvc_instance in msvc_instance_list] + debug( + 'msvs_channel_instances_map[%s]=%s', + repr(vs_channel_key.serialize()), + repr(msvc_instances), + extra=self.debug_extra, + ) + + for vs_edition_key, msvc_instance_list in self.msvs_edition_instances_map.items(): + msvc_instances = [msvc_instance.id_str for msvc_instance in msvc_instance_list] + debug( + 'msvs_edition_instances_map[%s]=%s', + repr(vs_edition_key.serialize()), + repr(msvc_instances), + extra=self.debug_extra, + ) + + for vs_channel_def, msvc_version_map in self.msvs_channel_map.items(): + for vcver, msvc_instance_list in msvc_version_map.items(): + msvc_instances = [msvc_instance.id_str for msvc_instance in msvc_instance_list] + debug( + 'msvc_version_map[%s][%s]=%s', + repr(vs_channel_def.vs_channel_suffix), + repr(vcver), + repr(msvc_instances), + extra=self.debug_extra, + ) + + def _query_instances_internal( + self, *, + vs_product_def=None, + vs_channel_def=None, + vs_componentid_def=None, + vs_sequence_nbr=None, + ): + + if not vs_channel_def: + vs_channel_def = _VSChannel.get_default_channel() + + if vs_product_def: + + query_key = _VSKeys.msvs_edition_key( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + vs_sequence_nbr=vs_sequence_nbr + ) + + msvc_instances = self.msvs_edition_instances_map.get(query_key, []) + + else: + + query_key = _VSKeys.msvs_channel_key( + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + ) + + msvc_instances = self.msvs_channel_instances_map.get(query_key, []) + + debug( + 'query_key=%s, n_msvc_instances=%s', + repr(query_key.serialize()), + repr(len(msvc_instances)), + extra=self.debug_extra, + ) + + return msvc_instances, query_key + + +_MSVSManager = namedtuple('_MSVSManager', [ + 'msvc_installed', + 'msvs_installed', +]) + +class MSVSManager(_MSVSManager, Util.AutoInitialize): + + debug_extra = None + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + + @classmethod + def factory(cls, msvc_installed, msvs_installed): + + msvs_manager = cls( + msvc_installed=msvc_installed, + msvs_installed=msvs_installed, + ) + + debug( + 'n_msvc_instances=%d, n_msvs_instances=%d', + len(msvc_installed.msvc_instances), + len(msvs_installed.msvs_instances), + extra=cls.debug_extra, + ) + + return msvs_manager + + def default_msvs_channel(self): + vs_channel_def = _VSChannel.get_default_channel() + return vs_channel_def + + def get_installed_msvc_instances(self): + # TODO(JCB): channel? + vs_channel_def = _VSChannel.get_default_channel() + msvs_channel_map = self.msvc_installed.msvs_channel_map + msvc_version_map = msvs_channel_map.get(vs_channel_def) + installed_msvc_instances = [ + msvc_version_map[vc_version][0] + for vc_version in msvc_version_map.keys() + ] + return installed_msvc_instances + + def get_installed_msvc_versions(self): + # TODO(JCB): channel? + vs_channel_def = _VSChannel.get_default_channel() + msvs_channel_map = self.msvc_installed.msvs_channel_map + msvc_version_map = msvs_channel_map.get(vs_channel_def) + installed_msvc_versions = [ + vc_version + for vc_version in msvc_version_map.keys() + ] + return installed_msvc_versions + + def query_msvs_instances( + self, *, + msvc_version, + ): + + # TODO(JCB): temporary placeholder + + prefix, suffix = Util.get_msvc_version_prefix_suffix(msvc_version) + + vs_product_def = Config.MSVC_VERSION_INTERNAL[prefix] + + vs_channel_def = None + + if suffix == 'Exp': + vs_componentid_def = Config.MSVS_COMPONENTID_EXPRESS + else: + vs_componentid_def = None + + msvs_instances, query_key = self.msvs_installed._query_instances_internal( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + ) + + return msvs_instances, query_key + + def query_msvc_instances( + self, *, + msvc_version, + ): + + # TODO(JCB): temporary placeholder + + prefix, suffix = Util.get_msvc_version_prefix_suffix(msvc_version) + + vs_product_def = Config.MSVC_VERSION_INTERNAL[prefix] + + vs_channel_def = None + + if suffix == 'Exp': + vs_componentid_def = Config.MSVS_COMPONENTID_EXPRESS + else: + vs_componentid_def = None + + msvc_instances, query_key = self.msvc_installed._query_instances_internal( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_componentid_def=vs_componentid_def, + ) + + return msvc_instances, query_key + + +class _VSDetectCommon(Util.AutoInitialize): + + debug_extra = None + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + + VSDetectedBinaries = namedtuple("VSDetectedBinaries", [ + 'bitfields', # detect values + 'have_dev', # develop ide binary + 'have_exp', # express ide binary + 'have_exp_win', # express windows ide binary + 'have_exp_web', # express web ide binary + 'vs_exec', # vs binary + ]) + + @classmethod + def msvs_detect(cls, vs_dir, vs_cfg): + + vs_exec = None + vs_script = None + + vs_binary_cfg = vs_cfg.vs_binary_cfg + vs_batch_cfg = vs_cfg.vs_batch_cfg + + ide_path = os.path.join(vs_dir, vs_binary_cfg.pathcomp) + + bitfields = 0b_0000 + for ide_program in vs_binary_cfg.programs: + prog = os.path.join(ide_path, ide_program.program) + if not os.path.exists(prog): + continue + bitfields |= ide_program.bitfield + if vs_exec: + continue + if not ide_program.bitfield & _VSConfig.EXECUTABLE_MASK: + continue + vs_exec = prog + + have_dev = bool(bitfields & _VSConfig.BITFIELD_DEVELOP) + have_exp = bool(bitfields & _VSConfig.BITFIELD_EXPRESS) + have_exp_win = bool(bitfields & _VSConfig.BITFIELD_EXPRESS_WIN) + have_exp_web = bool(bitfields & _VSConfig.BITFIELD_EXPRESS_WEB) + + binaries_t = cls.VSDetectedBinaries( + bitfields=bitfields, + have_dev=have_dev, + have_exp=have_exp, + have_exp_win=have_exp_win, + have_exp_web=have_exp_web, + vs_exec=vs_exec, + ) + + script_file = os.path.join(vs_dir, vs_batch_cfg.pathcomp, vs_batch_cfg.script) + if os.path.exists(script_file): + vs_script = script_file + + debug( + 'vs_dir=%s, dev=%s, exp=%s, exp_win=%s, exp_web=%s, vs_exec=%s, vs_script=%s', + repr(vs_dir), + binaries_t.have_dev, binaries_t.have_exp, + binaries_t.have_exp_win, binaries_t.have_exp_web, + repr(binaries_t.vs_exec), + repr(vs_script), + extra=cls.debug_extra, + ) + + return binaries_t, vs_script + + +class _VSDetectVSWhere(Util.AutoInitialize): + + DETECT_CONFIG = _VSConfig.DETECT_VSWHERE + + # initialization + + debug_extra = None + reset_funcs = [] + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + cls.reset() + + @classmethod + def reset(cls) -> None: + + cls.vswhere_executable_seen = set() + cls.vswhere_executables = [] + + cls.vs_dir_seen = set() + + cls.msvs_sequence_nbr = {} + + cls.msvs_instances = [] + cls.msvc_instances = [] + + @classmethod + def register_reset_func(cls, func) -> None: + if func: + cls.reset_funcs.append(func) + + @classmethod + def call_reset_funcs(cls) -> None: + for func in cls.reset_funcs: + func() + + @classmethod + def _filter_vswhere_paths(cls, vswhere_env): + vswhere_executables = VSWhere.vswhere_get_executables(vswhere_env) + vswhere_paths = [ + vswhere_exec.norm + for vswhere_exec in vswhere_executables + if vswhere_exec.norm not in cls.vswhere_executable_seen + ] + debug('vswhere_paths=%s', vswhere_paths, extra=cls.debug_extra) + return vswhere_paths + + @classmethod + def _vswhere_query_json_output(cls, vswhere_exe, vswhere_args): + + vswhere_json = None + + once = True + while once: + once = False + # using break for single exit (unless exception) + + vswhere_cmd = [vswhere_exe] + vswhere_args + ['-format', 'json', '-utf8'] + debug("running: %s", vswhere_cmd, extra=cls.debug_extra) + + try: + cp = subprocess.run( + vswhere_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False + ) + except OSError as e: + errmsg = str(e) + debug("%s: %s", type(e).__name__, errmsg, extra=cls.debug_extra) + break + except Exception as e: + errmsg = str(e) + debug("%s: %s", type(e).__name__, errmsg, extra=cls.debug_extra) + raise + + if not cp.stdout: + debug("no vswhere information returned", extra=cls.debug_extra) + break + + vswhere_output = cp.stdout.decode('utf8', errors='replace') + if not vswhere_output: + debug("no vswhere information output", extra=cls.debug_extra) + break + + try: + vswhere_output_json = json.loads(vswhere_output) + except json.decoder.JSONDecodeError: + debug("json decode exception loading vswhere output", extra=cls.debug_extra) + break + + vswhere_json = vswhere_output_json + break + + debug( + 'vswhere_json=%s, vswhere_exe=%s', + bool(vswhere_json), repr(vswhere_exe), extra=cls.debug_extra + ) + + return vswhere_json + + @classmethod + def _msvc_resolve(cls, vs_dir, vs_cfg): + + binaries_t, vs_script = _VSDetectCommon.msvs_detect(vs_dir, vs_cfg) + + return binaries_t.vs_exec, vs_script + + @classmethod + def _msvc_instance_toolsets(cls, msvc_instance): + # TODO(JCB): load toolsets/tools + rval = _msvc_instance_check_files_exist(msvc_instance) + return rval + + @classmethod + def detect(cls, vswhere_env): + + num_instances = len(cls.msvc_instances) + num_new_instances = 0 + + vswhere_paths = cls._filter_vswhere_paths(vswhere_env) + if vswhere_paths: + + num_beg_instances = num_instances + + for vswhere_exe in vswhere_paths: + + if vswhere_exe in cls.vswhere_executable_seen: + continue + + cls.vswhere_executable_seen.add(vswhere_exe) + cls.vswhere_executables.append(vswhere_exe) + + debug('vswhere_exe=%s', repr(vswhere_exe), extra=cls.debug_extra) + + vswhere_json = cls._vswhere_query_json_output( + vswhere_exe, + ['-all', '-products', '*', '-prerelease'] + ) + + if not vswhere_json: + continue + + for instance in vswhere_json: + + #print(json.dumps(instance, indent=4, sort_keys=True)) + + vs_dir = instance.get('installationPath') + if not vs_dir or not _VSUtil.path_exists(vs_dir): + continue + + vs_norm = _VSUtil.normalize_path(vs_dir) + if vs_norm in cls.vs_dir_seen: + continue + + vs_version = instance.get('installationVersion') + if not vs_version: + continue + + product_id = instance.get('productId') + if not product_id: + continue + + # consider msvs root evaluated at this point + cls.vs_dir_seen.add(vs_norm) + + vs_major = vs_version.split('.')[0] + if vs_major not in Config.MSVS_VERSION_MAJOR_MAP: + debug('ignore vs_major: %s', vs_major, extra=cls.debug_extra) + continue + + vs_product_def = Config.MSVS_VERSION_MAJOR_MAP[vs_major] + + detect_cfg = cls.DETECT_CONFIG[vs_product_def.vs_product] + + vc_dir = os.path.join(vs_dir, detect_cfg.vs_cfg.vc_folder) + if not _VSUtil.path_exists(vc_dir): + continue + + component_id = product_id.split('.')[-1] + vs_component_def = Config.VSWHERE_COMPONENT_INTERNAL.get(component_id) + if not vs_component_def: + debug('ignore component_id: %s', component_id, extra=cls.debug_extra) + continue + + is_prerelease = True if instance.get('isPrerelease', False) else False + if is_prerelease: + vs_channel_def = Config.MSVS_CHANNEL_PREVIEW + else: + vs_channel_def = Config.MSVS_CHANNEL_RELEASE + + vs_exec, vs_script = cls._msvc_resolve(vs_dir, detect_cfg.vs_cfg) + + vs_sequence_key = (vs_product_def, vs_channel_def, vs_component_def) + + cls.msvs_sequence_nbr.setdefault(vs_sequence_key, 0) + cls.msvs_sequence_nbr[vs_sequence_key] += 1 + + vs_sequence_nbr = cls.msvs_sequence_nbr[vs_sequence_key] + + msvs_base = MSVSBase.factory( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_component_def=vs_component_def, + vs_sequence_nbr=vs_sequence_nbr, + vs_dir=vs_dir, + vs_version=vs_version, + ) + + vc_version = vs_product_def.vc_buildtools_def.vc_version + + if msvs_base.is_express: + vc_version += msvs_base.vs_component_suffix + + vc_version_def = Util.msvc_version_components(vc_version) + + msvc_instance = MSVCInstance.factory( + msvs_base=msvs_base, + vc_version_def=vc_version_def, + vc_feature_map=None, + vc_dir=vc_dir, + ) + + if not cls._msvc_instance_toolsets(msvc_instance): + # no compilers detected don't register objects + continue + + if vs_exec: + + # TODO(JCB): register iff compilers and executable? + msvs_instance = MSVSInstance.factory( + msvs_base=msvs_base, + vs_executable=vs_exec, + vs_script=vs_script, + vc_version_def=vc_version_def, + ) + + msvs_base.register_msvs_instance(msvs_instance) + cls.msvs_instances.append(msvs_instance) + + debug( + 'msvs_instance=%s', + repr(msvs_instance.id_str), + extra=cls.debug_extra, + ) + + msvs_base.register_msvc_instance(msvc_instance) + cls.msvc_instances.append(msvc_instance) + + debug( + 'msvc_instance=%s', + repr(msvc_instance.id_str), + extra=cls.debug_extra, + ) + + num_instances = len(cls.msvc_instances) + num_new_instances = num_instances - num_beg_instances + + if num_new_instances > 0: + cls.call_reset_funcs() + debug( + 'num_new_instances=%s, num_instances=%s', + num_new_instances, num_instances, extra=cls.debug_extra + ) + + return num_new_instances + + +class _VSDetectRegistry(Util.AutoInitialize): + + DETECT_CONFIG = _VSConfig.DETECT_REGISTRY + + # initialization + + debug_extra = None + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + cls.reset() + + @classmethod + def reset(cls) -> None: + + cls.registry_once = False + + cls.vc_dir_seen = set() + + cls.msvs_sequence_nbr = {} + + cls.msvs_instances = [] + cls.msvc_instances = [] + + # VS2015 buildtools batch file call detection + # vs2015 buildtools do not support sdk_version or UWP arguments + + _VS2015BT_PATH = r'..\Microsoft Visual C++ Build Tools\vcbuildtools.bat' + + _VS2015BT_REGEX_STR = ''.join([ + r'^\s*if\s+exist\s+', + re.escape(fr'"%~dp0..\{_VS2015BT_PATH}"'), + r'\s+goto\s+setup_buildsku\s*$', + ]) + + _VS2015BT_VCVARS_BUILDTOOLS = re.compile(_VS2015BT_REGEX_STR, re.IGNORECASE) + _VS2015BT_VCVARS_STOP = re.compile(r'^\s*[:]Setup_VS\s*$', re.IGNORECASE) + + @classmethod + def _vs_buildtools_2015_vcvars(cls, vcvars_file) -> bool: + have_buildtools_vcvars = False + with open(vcvars_file) as fh: + for line in fh: + if cls._VS2015BT_VCVARS_BUILDTOOLS.match(line): + have_buildtools_vcvars = True + break + if cls._VS2015BT_VCVARS_STOP.match(line): + break + return have_buildtools_vcvars + + @classmethod + def _vs_buildtools_2015(cls, vs_dir, vc_dir) -> bool: + + is_buildtools = False + + do_once = True + while do_once: + do_once = False + + buildtools_file = os.path.join(vs_dir, cls._VS2015BT_PATH) + have_buildtools = os.path.exists(buildtools_file) + debug('have_buildtools=%s', have_buildtools, extra=cls.debug_extra) + if not have_buildtools: + break + + vcvars_file = os.path.join(vc_dir, 'vcvarsall.bat') + have_vcvars = os.path.exists(vcvars_file) + debug('have_vcvars=%s', have_vcvars, extra=cls.debug_extra) + if not have_vcvars: + break + + have_buildtools_vcvars = cls._vs_buildtools_2015_vcvars(vcvars_file) + debug('have_buildtools_vcvars=%s', have_buildtools_vcvars, extra=cls.debug_extra) + if not have_buildtools_vcvars: + break + + is_buildtools = True + + debug('is_buildtools=%s', is_buildtools, extra=cls.debug_extra) + return is_buildtools + + _VS2015EXP_VCVARS_LIBPATH = re.compile( + ''.join([ + r'^\s*\@if\s+exist\s+\"\%VCINSTALLDIR\%LIB\\store\\(amd64|arm)"\s+', + r'set (LIB|LIBPATH)=\%VCINSTALLDIR\%LIB\\store\\(amd64|arm);.*\%(LIB|LIBPATH)\%\s*$' + ]), + re.IGNORECASE + ) + + _VS2015EXP_VCVARS_STOP = re.compile(r'^\s*[:]GetVSCommonToolsDir\s*$', re.IGNORECASE) + + @classmethod + def _vs_express_2015_vcvars(cls, vcvars_file) -> bool: + n_libpath = 0 + with open(vcvars_file) as fh: + for line in fh: + if cls._VS2015EXP_VCVARS_LIBPATH.match(line): + n_libpath += 1 + elif cls._VS2015EXP_VCVARS_STOP.match(line): + break + have_uwp_fix = bool(n_libpath >= 2) + return have_uwp_fix + + @classmethod + def _vs_express_2015(cls, vc_dir): + + have_uwp_amd64 = False + have_uwp_arm = False + + vcvars_file = os.path.join(vc_dir, r'vcvarsall.bat') + if os.path.exists(vcvars_file): + + vcvars_file = os.path.join(vc_dir, r'bin\x86_amd64\vcvarsx86_amd64.bat') + if os.path.exists(vcvars_file): + have_uwp_fix = cls._vs_express_2015_vcvars(vcvars_file) + if have_uwp_fix: + have_uwp_amd64 = True + + vcvars_file = os.path.join(vc_dir, r'bin\x86_arm\vcvarsx86_arm.bat') + if os.path.exists(vcvars_file): + have_uwp_fix = cls._vs_express_2015_vcvars(vcvars_file) + if have_uwp_fix: + have_uwp_arm = True + + debug('have_uwp_amd64=%s, have_uwp_arm=%s', have_uwp_amd64, have_uwp_arm, extra=cls.debug_extra) + return have_uwp_amd64, have_uwp_arm + + # winsdk installed 2010 [7.1], 2008 [7.0, 6.1] folders + + _REGISTRY_WINSDK_VERSIONS = {'10.0', '9.0'} + + @classmethod + def _msvc_dir_is_winsdk_only(cls, vc_dir, msvc_version) -> bool: + + # detect winsdk-only installations + # + # registry keys: + # [prefix]\VisualStudio\SxS\VS7\10.0 + # [prefix]\VisualStudio\SxS\VC7\10.0 product directory + # [prefix]\VisualStudio\SxS\VS7\9.0 + # [prefix]\VisualStudio\SxS\VC7\9.0 product directory + # + # winsdk notes: + # - winsdk installs do not define the common tools env var + # - the product dir is detected but the vcvars batch files will fail + # - regular installations populate the VS7 registry keys + + if msvc_version not in cls._REGISTRY_WINSDK_VERSIONS: + + is_sdk = False + + debug('is_sdk=%s, msvc_version=%s', is_sdk, repr(msvc_version), extra=cls.debug_extra) + + else: + + vc_suffix = Registry.vstudio_sxs_vc7(msvc_version) + vc_qresults = [record[0] for record in Registry.microsoft_query_paths(vc_suffix)] + vc_regdir = vc_qresults[0] if vc_qresults else None + + if vc_regdir != vc_dir: + # registry vc path is not the current vc dir + + is_sdk = False + + debug( + 'is_sdk=%s, msvc_version=%s, vc_dir=%s, vc_regdir=%s', + is_sdk, repr(msvc_version), repr(vc_dir), repr(vc_regdir), extra=cls.debug_extra + ) + + else: + # registry vc dir is the current vc root + + vs_suffix = Registry.vstudio_sxs_vs7(msvc_version) + vs_qresults = [record[0] for record in Registry.microsoft_query_paths(vs_suffix)] + vs_dir = vs_qresults[0] if vs_qresults else None + + is_sdk = bool(not vs_dir and vc_dir) + + debug( + 'is_sdk=%s, msvc_version=%s, vc_dir=%s, vs_dir=%s', + is_sdk, repr(msvc_version), repr(vc_dir), repr(vs_dir), extra=cls.debug_extra + ) + + return is_sdk + + @classmethod + def _msvc_resolve(cls, vc_dir, vs_product_def, is_vcforpython, vs_version): + + detect_cfg = cls.DETECT_CONFIG[vs_product_def.vs_product] + + vc_feature_map = {} + + vs_cfg = detect_cfg.vs_cfg + vs_dir = os.path.normpath(os.path.join(vc_dir, vs_cfg.root)) + + binaries_t, vs_script = _VSDetectCommon.msvs_detect(vs_dir, vs_cfg) + + vs_product_numeric = vs_product_def.vs_product_numeric + vc_version = vs_product_def.vc_buildtools_def.vc_version + + if binaries_t.have_dev: + vs_component_def = Config.REGISTRY_COMPONENT_DEVELOP + elif binaries_t.have_exp: + vs_component_def = Config.REGISTRY_COMPONENT_EXPRESS + elif vs_product_numeric == 2008 and is_vcforpython: + vs_component_def = Config.REGISTRY_COMPONENT_PYTHON + elif binaries_t.have_exp_win: + vs_component_def = None + elif binaries_t.have_exp_web: + vs_component_def = None + elif cls._msvc_dir_is_winsdk_only(vc_dir, vc_version): + vs_component_def = None + else: + vs_component_def = Config.REGISTRY_COMPONENT_CMDLINE + + if vs_component_def and vs_product_numeric == 2015: + + # VS2015: + # remap DEVELOP => ENTERPRISE, PROFESSIONAL, COMMUNITY + # remap CMDLINE => BUILDTOOLS [conditional] + # process EXPRESS + + if vs_component_def == Config.REGISTRY_COMPONENT_DEVELOP: + + for reg_component, reg_component_def in [ + ('community', Config.REGISTRY_COMPONENT_COMMUNITY), + ('professional', Config.REGISTRY_COMPONENT_PROFESSIONAL), + ('enterprise', Config.REGISTRY_COMPONENT_ENTERPRISE), + ]: + suffix = Registry.devdiv_vs_servicing_component(vs_version, reg_component) + qresults = Registry.microsoft_query_keys(suffix, usrval=reg_component_def) + if not qresults: + continue + vs_component_def = qresults[0][-1] + break + + elif vs_component_def == Config.REGISTRY_COMPONENT_CMDLINE: + + if cls._vs_buildtools_2015(vs_dir, vc_dir): + vs_component_def = Config.REGISTRY_COMPONENT_BUILDTOOLS + + elif vs_component_def == Config.REGISTRY_COMPONENT_EXPRESS: + + have_uwp_amd64, have_uwp_arm = cls._vs_express_2015(vc_dir) + + uwp_target_is_supported = { + 'x86': True, + 'amd64': have_uwp_amd64, + 'arm': have_uwp_arm, + } + + vc_feature_map['uwp_target_is_supported'] = uwp_target_is_supported + + debug( + 'vs_product=%s, vs_component=%s, vc_dir=%s, vc_feature_map=%s', + repr(vs_product_numeric), + repr(vs_component_def.vs_componentid_def.vs_component_id) if vs_component_def else None, + repr(vc_dir), + repr(vc_feature_map), + extra=cls.debug_extra + ) + + return vs_component_def, vs_dir, binaries_t.vs_exec, vs_script, vc_feature_map + + @classmethod + def _msvc_instance_toolsets(cls, msvc_instance): + # TODO(JCB): load toolsets/tools + rval = _msvc_instance_check_files_exist(msvc_instance) + return rval + + @classmethod + def detect(cls): + + num_instances = len(cls.msvc_instances) + num_new_instances = 0 + + if not cls.registry_once: + cls.registry_once = True + + num_beg_instances = num_instances + + is_win64 = common.is_win64() + + for vs_product, config in cls.DETECT_CONFIG.items(): + + key_prefix = 'Software\\' + for regkey in config.vc_cfg.regkeys: + + if not regkey.hkroot or not regkey.key: + continue + + if is_win64: + mskeys = [key_prefix + 'Wow6432Node\\' + regkey.key, key_prefix + regkey.key] + else: + mskeys = [key_prefix + regkey.key] + + vc_dir = None + for mskey in mskeys: + debug('trying VC registry key %s', repr(mskey), extra=cls.debug_extra) + try: + vc_dir = common.read_reg(mskey, regkey.hkroot) + except OSError: + continue + if vc_dir: + break + + if not vc_dir: + debug('no VC registry key %s', repr(regkey.key), extra=cls.debug_extra) + continue + + if vc_dir in cls.vc_dir_seen: + continue + cls.vc_dir_seen.add(vc_dir) + + if not os.path.exists(vc_dir): + debug( + 'reg says vc dir is %s, but it does not exist. (ignoring)', + repr(vc_dir), extra=cls.debug_extra + ) + continue + + if regkey.is_vsroot: + + vc_dir = os.path.join(vc_dir, config.vs_cfg.vc_folder) + debug('convert vs dir to vc dir: %s', repr(vc_dir), extra=cls.debug_extra) + + if vc_dir in cls.vc_dir_seen: + continue + cls.vc_dir_seen.add(vc_dir) + + if not os.path.exists(vc_dir): + debug('vc dir does not exist: %s (ignoring)', repr(vc_dir), extra=cls.debug_extra) + continue + + vc_norm = _VSUtil.normalize_path(vc_dir) + if vc_norm in cls.vc_dir_seen: + continue + cls.vc_dir_seen.add(vc_norm) + + vs_product_def = Config.MSVS_VERSION_INTERNAL[vs_product] + + vs_component_def, vs_dir, vs_exec, vs_script, vc_feature_map = cls._msvc_resolve( + vc_norm, + vs_product_def, + regkey.is_vcforpython, + vs_product_def.vs_version + ) + + if not vs_component_def: + continue + + vs_channel_def = Config.MSVS_CHANNEL_RELEASE + + vs_sequence_key = (vs_product_def, vs_channel_def, vs_component_def) + + cls.msvs_sequence_nbr.setdefault(vs_sequence_key, 0) + cls.msvs_sequence_nbr[vs_sequence_key] += 1 + + vs_sequence_nbr = cls.msvs_sequence_nbr[vs_sequence_key] + + msvs_base = MSVSBase.factory( + vs_product_def=vs_product_def, + vs_channel_def=vs_channel_def, + vs_component_def=vs_component_def, + vs_sequence_nbr=vs_sequence_nbr, + vs_dir=vs_dir, + vs_version=vs_product_def.vs_version, + ) + + vc_version = vs_product_def.vc_buildtools_def.vc_version + + if msvs_base.is_express: + vc_version += msvs_base.vs_component_suffix + + vc_version_def = Util.msvc_version_components(vc_version) + + msvc_instance = MSVCInstance.factory( + msvs_base=msvs_base, + vc_version_def=vc_version_def, + vc_feature_map=vc_feature_map, + vc_dir=vc_dir, + ) + + if not cls._msvc_instance_toolsets(msvc_instance): + # no compilers detected don't register objects + continue + + if vs_exec: + + # TODO(JCB): register iff compilers and executable? + msvs_instance = MSVSInstance.factory( + msvs_base=msvs_base, + vs_executable=vs_exec, + vs_script=vs_script, + vc_version_def=vc_version_def, + ) + + msvs_base.register_msvs_instance(msvs_instance) + cls.msvs_instances.append(msvs_instance) + + debug( + 'msvs_instance=%s', + repr(msvs_instance.id_str), + extra=cls.debug_extra, + ) + + msvs_base.register_msvc_instance(msvc_instance) + cls.msvc_instances.append(msvc_instance) + + debug( + 'msvc_instance=%s', + repr(msvc_instance.id_str), + extra=cls.debug_extra, + ) + + num_instances = len(cls.msvc_instances) + num_new_instances = num_instances - num_beg_instances + + return num_new_instances + + +class _VSDetect(Util.AutoInitialize): + + debug_extra = None + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + cls._reset() + + @classmethod + def _reset(cls) -> None: + # volatile: reconstructed when new instances detected + cls.msvs_manager = None + + @classmethod + def reset(cls) -> None: + cls._reset() + _VSDetectVSWhere.reset() + _VSDetectRegistry.reset() + + @classmethod + def detect(cls, vswhere_env): + + num_new_instances = 0 + num_new_instances += _VSDetectVSWhere.detect(vswhere_env) + num_new_instances += _VSDetectRegistry.detect() + + if num_new_instances > 0 or cls.msvs_manager is None: + + msvc_instances = [] + msvc_instances.extend(_VSDetectVSWhere.msvc_instances) + msvc_instances.extend(_VSDetectRegistry.msvc_instances) + + msvs_instances = [] + msvs_instances.extend(_VSDetectVSWhere.msvs_instances) + msvs_instances.extend(_VSDetectRegistry.msvs_instances) + + msvc_installed = MSVCInstalled.factory( + msvc_instances=msvc_instances, + ) + + msvs_installed = MSVSInstalled.factory( + msvs_instances=msvs_instances, + ) + + cls.msvs_manager = MSVSManager.factory( + msvc_installed=msvc_installed, + msvs_installed=msvs_installed, + ) + + return cls.msvs_manager + + +def register_reset_func(func) -> None: + _VSDetectVSWhere.register_reset_func(func) + + +def msvs_detect(vswhere_env=None): + vs_manager = _VSDetect.detect(vswhere_env) + return vs_manager + +def msvs_detect_env(env=None): + vswhere_env = Util.env_query(env, 'VSWHERE', subst=True) + vs_manager = _VSDetect.detect(vswhere_env) + return vs_manager + + +def reset() -> None: + _VSUtil.reset() + _VSChannel.reset() + _VSDetect.reset() + +def verify() -> None: + + def _compare_product_sets(set_config, set_local, label) -> None: + diff = set_config - set_local + if diff: + keys = ', '.join([repr(s) for s in sorted(diff, reverse=True)]) + errmsg = f'{label} missing keys: {keys}' + debug('MSVCInternalError: %s', errmsg) + raise MSVCInternalError(errmsg) + + vswhere_config = Config.VSWHERE_SUPPORTED_PRODUCTS + vswhere_local = set(_VSConfig.DETECT_VSWHERE.keys()) + _compare_product_sets(vswhere_config, vswhere_local, '_VSConfig.DETECT_VSWHERE') + + registry_config = Config.REGISTRY_SUPPORTED_PRODUCTS + registry_local = set(_VSConfig.DETECT_REGISTRY.keys()) + _compare_product_sets(registry_config, registry_local, '_VSConfig.DETECT_REGISTRY') + diff --git a/SCons/Tool/MSCommon/MSVC/VSWhere.py b/SCons/Tool/MSCommon/MSVC/VSWhere.py new file mode 100644 index 0000000000..3d90314209 --- /dev/null +++ b/SCons/Tool/MSCommon/MSVC/VSWhere.py @@ -0,0 +1,217 @@ +# 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. + +""" +VSWhere executable locations for Microsoft Visual C/C++. +""" + +import os +from collections import namedtuple + +import SCons.Util + +from ..common import ( + debug, + debug_extra, +) + +from . import Config +from . import Util +from . import Options +from . import Warnings + +from . import Dispatcher +Dispatcher.register_modulename(__name__) + +# priority: env > cmdline > environ > (user, initial, user) + +VSWHERE_EXE = 'vswhere.exe' + +VSWHERE_PATHS = [ + os.path.join(p, VSWHERE_EXE) + for p in [ + # For bug 3333: support default location of vswhere for both + # 64 and 32 bit windows installs. + # For bug 3542: also accommodate not being on C: drive. + os.path.expandvars(r"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer"), + os.path.expandvars(r"%ProgramFiles%\Microsoft Visual Studio\Installer"), + os.path.expandvars(r"%ChocolateyInstall%\bin"), + os.path.expandvars(r"%LOCALAPPDATA%\Microsoft\WinGet\Links"), + os.path.expanduser(r"~\scoop\shims"), + os.path.expandvars(r"%SCOOP%\shims"), + ] + if not p.startswith('%') +] + +class _VSWhere(Util.AutoInitialize): + + # TODO(JCB): review reset + + VSWhereExecutable = namedtuple('VSWhereExecutable', [ + 'path', + 'norm', + ]) + + debug_extra = None + + vswhere_cmdline = None + vswhere_executables = [] + + _cache_user_vswhere_paths = {} + + @classmethod + def _cmdline(cls) -> None: + vswhere, option = Options.vswhere() + if vswhere: + vswhere_exec = cls.user_path(vswhere, option) + if vswhere_exec: + cls.vswhere_cmdline = vswhere_exec + debug('vswhere_cmdline=%s', cls.vswhere_cmdline, extra=cls.debug_extra) + + @classmethod + def _setup(cls) -> None: + for pval in VSWHERE_PATHS: + if not os.path.exists(pval): + continue + vswhere_exec = cls.VSWhereExecutable(path=pval, norm=Util.normalize_path(pval)) + cls.vswhere_executables.append(vswhere_exec) + debug('vswhere_executables=%s', cls.vswhere_executables, extra=cls.debug_extra) + cls._cmdline() + + @classmethod + def _initialize(cls) -> None: + cls.debug_extra = debug_extra(cls) + cls.reset() + cls._setup() + + @classmethod + def reset(cls) -> None: + cls._cache_user_vswhere_paths = {} + + # validate user-specified vswhere executable + + @classmethod + def user_path(cls, pval, source): + + rval = cls._cache_user_vswhere_paths.get(pval, Config.UNDEFINED) + if rval != Config.UNDEFINED: + debug('vswhere_exec=%s', repr(rval), extra=cls.debug_extra) + return rval + + vswhere_exec = None + if pval: + + if not os.path.exists(pval): + + warn_msg = f'vswhere executable path not found: {pval!r} ({source})' + debug(warn_msg, extra=cls.debug_extra) + SCons.Warnings.warn(Warnings.VSWherePathWarning, warn_msg) + + else: + + norm = Util.normalize_path(pval) + tail = os.path.split(norm)[-1] + if tail != VSWHERE_EXE: + + warn_msg = f'unsupported vswhere executable (expected {VSWHERE_EXE!r}, found {tail!r}): {pval!r} ({source})' + debug(warn_msg, extra=cls.debug_extra) + SCons.Warnings.warn(Warnings.VSWherePathWarning, warn_msg) + + else: + + vswhere_exec = cls.VSWhereExecutable(path=pval, norm=norm) + debug('vswhere_exec=%s', repr(vswhere_exec), extra=cls.debug_extra) + + cls._cache_user_vswhere_paths[pval] = vswhere_exec + + return vswhere_exec + + # user-specified vswhere executables + + @classmethod + def user_pathlist(cls, path_list, front, source) -> None: + + user_executables = [] + for pval in path_list: + vswhere_exec = cls.user_path(pval, source) + if vswhere_exec: + user_executables.append(vswhere_exec) + + if user_executables: + + if front: + all_executables = user_executables + cls.vswhere_executables + else: + all_executables = cls.vswhere_executables + user_executables + + seen = set() + unique_executables = [] + for vswhere_exec in all_executables: + if vswhere_exec.norm in seen: + continue + seen.add(vswhere_exec.norm) + unique_executables.append(vswhere_exec) + + cls.vswhere_executables = unique_executables + debug('vswhere_executables=%s', cls.vswhere_executables) + +# user vswhere executable location(s) + +def vswhere_push_location(string_or_list, front=False) -> None: + # TODO(JCB): need docstring + path_list = SCons.Util.flatten(string_or_list) + if path_list: + _VSWhere.user_pathlist(path_list, front, 'vswhere_push_location') + +# all vswhere executables + +def vswhere_get_executables(vswhere_env=None): + + vswhere_executables = [] + + # env['VSWHERE']=path + if vswhere_env: + vswhere_exec = _VSWhere.user_path(vswhere_env, "env['VSWHERE']") + if vswhere_exec: + vswhere_executables.append(vswhere_exec) + + # --vswhere=EXEPATH + if _VSWhere.vswhere_cmdline: + vswhere_executables.append(_VSWhere.vswhere_cmdline) + + # default paths and user paths (vswhere_push_location) + if _VSWhere.vswhere_executables: + vswhere_executables.extend(_VSWhere.vswhere_executables) + + return vswhere_executables + +def vswhere_get_executables_env(env=None): + vswhere_env = Util.env_query(env, 'VSWHERE', subst=True) + vswhere_executables = vswhere_get_executables(vswhere_env) + return vswhere_executables + +# reset state + +def reset() -> None: + _VSWhere.reset() + diff --git a/SCons/Tool/MSCommon/MSVC/Validate.py b/SCons/Tool/MSCommon/MSVC/Validate.py new file mode 100644 index 0000000000..ad058b673e --- /dev/null +++ b/SCons/Tool/MSCommon/MSVC/Validate.py @@ -0,0 +1,113 @@ +# 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. + +""" +Version validation for Microsoft Visual C/C++. +""" + +from ..common import debug + +from .Exceptions import ( + MSVCVersionUnsupported, + MSVCArgumentError, +) + +from . import Config +from . import Util + + +from . import Dispatcher +Dispatcher.register_modulename(__name__) + + +def validate_msvc_version(vc_version, source): + + vc_version_def = None + + if not vc_version: + return vc_version_def + + vc_version_def = Util.msvc_version_components(vc_version) + if not vc_version_def: + + if not Util.is_version_valid(vc_version): + err_msg = f'Unsupported version format: {vc_version!r} ({source})' + debug(err_msg) + raise MSVCArgumentError(err_msg) + + if vc_version not in Config.MSVC_VERSIONS: + symbols = Config.MSVC_VERSIONS + err_msg = f'Unrecognized msvc version {vc_version!r} ({source})\n Valid msvc versions are: {symbols}' + debug(err_msg) + raise MSVCVersionUnsupported(err_msg) + + return vc_version_def + +def validate_msvs_product(vs_product, source): + + vs_product_def = None + + if not vs_product: + return vs_product_def + + vs_product_def = Config.MSVS_VERSION_EXTERNAL.get(vs_product) + if not vs_product_def: + symbols = ', '.join(Config.MSVS_VERSION_SYMBOLS) + err_msg = f'Unrecognized msvs product {vs_product!r} ({source})\n Valid msvs products are: {symbols}' + debug(err_msg) + raise MSVCArgumentError(err_msg) + + return vs_product_def + +def validate_msvs_component(vs_component, source): + + vs_componentid_def = None + + if not vs_component: + return vs_componentid_def + + vs_componentid_def = Config.MSVS_COMPONENTID_EXTERNAL.get(vs_componentid_def) + if not vs_componentid_def: + symbols = ', '.join(Config.MSVS_COMPONENTID_SYMBOLS) + err_msg = f'Unrecognized msvs component {vs_component!r} ({source})\n Valid msvs components are: {symbols}' + debug(err_msg) + raise MSVCArgumentError(err_msg) + + return vs_componentid_def + +def validate_msvs_channel(vs_channel, source): + + vs_channel_def = None + + if not vs_channel: + return vs_channel_def + + vs_channel_def = Config.MSVS_CHANNEL_EXTERNAL.get(vs_channel) + if not vs_channel_def: + symbols = ', '.join(Config.MSVS_CHANNEL_SYMBOLS) + err_msg = f'Unrecognized msvs channel {vs_channel!r} ({source})\n Valid msvs channels are: {symbols}' + debug(err_msg) + raise MSVCArgumentError(err_msg) + + return vs_channel_def + diff --git a/SCons/Tool/MSCommon/MSVC/Warnings.py b/SCons/Tool/MSCommon/MSVC/Warnings.py index cab5145a99..ac7447c826 100644 --- a/SCons/Tool/MSCommon/MSVC/Warnings.py +++ b/SCons/Tool/MSCommon/MSVC/Warnings.py @@ -30,6 +30,12 @@ class VisualCWarning(SCons.Warnings.WarningOnByDefault): pass +class VSWherePathWarning(VisualCWarning): + pass + +class MSVSChannelWarning(VisualCWarning): + pass + class MSVCScriptExecutionWarning(VisualCWarning): pass diff --git a/SCons/Tool/MSCommon/MSVC/WinSDK.py b/SCons/Tool/MSCommon/MSVC/WinSDK.py index 7115d505ee..965d9aa98b 100644 --- a/SCons/Tool/MSCommon/MSVC/WinSDK.py +++ b/SCons/Tool/MSCommon/MSVC/WinSDK.py @@ -224,8 +224,8 @@ def get_msvc_platform(is_uwp: bool=False): platform_def = _UWP if is_uwp else _DESKTOP return platform_def -def get_sdk_version_list(vs_def, platform_def): - version_list = vs_def.vc_sdk_versions if vs_def.vc_sdk_versions is not None else [] +def get_sdk_version_list(vs_product_def, platform_def): + version_list = vs_product_def.vc_sdk_versions if vs_product_def.vc_sdk_versions is not None else [] sdk_map = _sdk_map(version_list) sdk_list = sdk_map.get(platform_def.vc_platform, []) return sdk_list @@ -240,14 +240,14 @@ def get_msvc_sdk_version_list(msvc_version, msvc_uwp_app: bool=False): debug('msvc_version is not defined') return sdk_versions - vs_def = Config.MSVC_VERSION_EXTERNAL.get(verstr, None) - if not vs_def: - debug('vs_def is not defined') + vs_product_def = Config.MSVC_VERSION_EXTERNAL.get(verstr, None) + if not vs_product_def: + debug('vs_product_def is not defined') return sdk_versions is_uwp = True if msvc_uwp_app in Config.BOOLEAN_SYMBOLS[True] else False platform_def = get_msvc_platform(is_uwp) - sdk_list = get_sdk_version_list(vs_def, platform_def) + sdk_list = get_sdk_version_list(vs_product_def, platform_def) sdk_versions.extend(sdk_list) debug('sdk_versions=%s', repr(sdk_versions)) diff --git a/SCons/Tool/MSCommon/MSVC/WinSDKTests.py b/SCons/Tool/MSCommon/MSVC/WinSDKTests.py index 10e68f3d6e..d3e54072cb 100644 --- a/SCons/Tool/MSCommon/MSVC/WinSDKTests.py +++ b/SCons/Tool/MSCommon/MSVC/WinSDKTests.py @@ -101,10 +101,10 @@ def _run_get_msvc_sdk_version_list(self) -> None: def _run_version_list_sdk_map(self) -> None: for vcver in Config.MSVC_VERSION_SUFFIX.keys(): - vs_def = Config.MSVC_VERSION_SUFFIX.get(vcver) - if not vs_def.vc_sdk_versions: + vs_product_def = Config.MSVC_VERSION_SUFFIX.get(vcver) + if not vs_product_def.vc_sdk_versions: continue - _ = WinSDK._version_list_sdk_map(vs_def.vc_sdk_versions) + _ = WinSDK._version_list_sdk_map(vs_product_def.vc_sdk_versions) def test_version_list_sdk_map(self) -> None: self._run_version_list_sdk_map() diff --git a/SCons/Tool/MSCommon/MSVC/__init__.py b/SCons/Tool/MSCommon/MSVC/__init__.py index 766894d9bb..262c23fdc7 100644 --- a/SCons/Tool/MSCommon/MSVC/__init__.py +++ b/SCons/Tool/MSCommon/MSVC/__init__.py @@ -35,15 +35,19 @@ * _verify is invoked from the last line in the vc module. """ -from . import Exceptions # noqa: F401 - from . import Config # noqa: F401 -from . import Util # noqa: F401 +from . import Exceptions # noqa: F401 +from . import Options # noqa: F401 +from . import Policy # noqa: F401 from . import Registry # noqa: F401 from . import SetupEnvDefault # noqa: F401 -from . import Policy # noqa: F401 -from . import WinSDK # noqa: F401 from . import ScriptArguments # noqa: F401 +from . import Util # noqa: F401 +from . import Validate # noqa: F401 +from . import VSDetect # noqa: F401 +from . import VSWhere # noqa: F401 +from . import Warnings # noqa: F401 +from . import WinSDK # noqa: F401 from . import Dispatcher as _Dispatcher diff --git a/SCons/Tool/MSCommon/README.rst b/SCons/Tool/MSCommon/README.rst index 5ab07ad631..bc57ca43b0 100644 --- a/SCons/Tool/MSCommon/README.rst +++ b/SCons/Tool/MSCommon/README.rst @@ -27,16 +27,166 @@ Design Notes ``MSCommon/vc.py`` and are available via the ``SCons.Tool.MSCommon`` namespace. +MSVC Detection Priority +======================= + +For msvc version specifications without an 'Exp' suffix, an express +installation is used only when no other installation is detected. + +======= ======= ======================================================== +Product VCVer Priority +======= ======= ======================================================== +VS2022 14.3 Enterprise, Professional, Community, BuildTools +------- ------- -------------------------------------------------------- +VS2019 14.2 Enterprise, Professional, Community, BuildTools +------- ------- -------------------------------------------------------- +VS2017 14.1 Enterprise, Professional, Community, BuildTools, Express +------- ------- -------------------------------------------------------- +VS2017 14.1Exp Express +------- ------- -------------------------------------------------------- +VS2015 14.0 [Develop, BuildTools, CmdLine], Express +------- ------- -------------------------------------------------------- +VS2015 14.0Exp Express +------- ------- -------------------------------------------------------- +VS2013 12.0 Develop, Express +------- ------- -------------------------------------------------------- +VS2013 12.0Exp Express +------- ------- -------------------------------------------------------- +VS2012 11.0 Develop, Express +------- ------- -------------------------------------------------------- +VS2012 11.0Exp Express +------- ------- -------------------------------------------------------- +VS2010 10.0 Develop, Express +------- ------- -------------------------------------------------------- +VS2010 10.0Exp Express +------- ------- -------------------------------------------------------- +VS2008 9.0 VCForPython, Develop, Express +------- ------- -------------------------------------------------------- +VS2008 9.0Exp Express +------- ------- -------------------------------------------------------- +VS2005 8.0 Develop, Express +------- ------- -------------------------------------------------------- +VS2005 8.0Exp Express +------- ------- -------------------------------------------------------- +VS2003 7.1 Develop +------- ------- -------------------------------------------------------- +VS2002 7.0 Develop +------- ------- -------------------------------------------------------- +VS6.0 6.0 Develop +======= ======= ======================================================== + +Legend: + + Develop + devenv.com (or msdev.com) is detected. + + Express + WDExpress.exe (or VCExpress.exe) is detected. + + BuildTools [VS2015] + The vcvarsall batch file dispatches to the buildtools batch file. + + CmdLine [VS2015] + Neither Develop, Express, or BuildTools. + +VS2015 Edition Limitations +========================== + +VS2015 BuildTools +----------------- + +The VS2015 BuildTools stand-alone batch file does not support the ``sdk version`` argument. + +The VS2015 BuildTools stand-alone batch file does not support the ``store`` argument. + +These arguments appear to be silently ignored and likely would result in compiler +and/or linker build failures. + +The VS2015 BuildTools ``vcvarsall.bat`` batch file dispatches to the stand-alone buildtools +batch file under certain circumstances. A fragment from the vcvarsall batch file is: +:: + if exist "%~dp0..\common7\IDE\devenv.exe" goto setup_VS + if exist "%~dp0..\common7\IDE\wdexpress.exe" goto setup_VS + if exist "%~dp0..\..\Microsoft Visual C++ Build Tools\vcbuildtools.bat" goto setup_buildsku + + :setup_VS + + ... + + :setup_buildsku + if not exist "%~dp0..\..\Microsoft Visual C++ Build Tools\vcbuildtools.bat" goto usage + set CurrentDir=%CD% + call "%~dp0..\..\Microsoft Visual C++ Build Tools\vcbuildtools.bat" %1 %2 + cd /d %CurrentDir% + goto :eof + +VS2015 Express +-------------- + +The VS2015 Express batch file does not support the ``sdk version`` argument. + +The VS2015 Express batch file does not support the ``store`` argument for the ``amd64`` and +``arm`` target architectures + +amd64 Target Architecture +^^^^^^^^^^^^^^^^^^^^^^^^^ + +As installed, VS2015 Express does not support the ``store`` argument for the ``amd64`` target +architecture. The generated ``store`` library paths include directories that do not exist. + +The store library paths appear in two places in the ``vcvarsx86_amd64`` batch file: +:: + :setstorelib + @if exist "%VCINSTALLDIR%LIB\amd64\store" set LIB=%VCINSTALLDIR%LIB\amd64\store;%LIB% + ... + :setstorelibpath + @if exist "%VCINSTALLDIR%LIB\amd64\store" set LIBPATH=%VCINSTALLDIR%LIB\amd64\store;%LIBPATH% + +The correct store library paths would be: +:: + :setstorelib + @if exist "%VCINSTALLDIR%LIB\store\amd64" set LIB=%VCINSTALLDIR%LIB\store\amd64;%LIB% + ... + :setstorelibpath + @if exist "%VCINSTALLDIR%LIB\store\amd64" set LIBPATH=%VCINSTALLDIR%LIB\store\amd64;%LIBPATH% + +arm Target Architecture +^^^^^^^^^^^^^^^^^^^^^^^ + +As installed, VS2015 Express does not support the ``store`` argument for the ``arm`` target +architecture. The generated ``store`` library paths include directories that do not exist. + +The store library paths appear in two places in the ``vcvarsx86_arm`` batch file: +:: + :setstorelib + @if exist "%VCINSTALLDIR%LIB\ARM\store" set LIB=%VCINSTALLDIR%LIB\ARM\store;%LIB% + ... + :setstorelibpath + @if exist "%VCINSTALLDIR%LIB\ARM\store" set LIBPATH=%VCINSTALLDIR%LIB\ARM\store;%LIBPATH% + +The correct store library paths would be file: +:: + :setstorelib + @if exist "%VCINSTALLDIR%LIB\store\ARM" set LIB=%VCINSTALLDIR%LIB\store\ARM;%LIB% + ... + :setstorelibpath + @if exist "%VCINSTALLDIR%LIB\store\ARM" set LIBPATH=%VCINSTALLDIR%LIB\store\ARM;%LIBPATH% + + Known Issues ============ The following issues are known to exist: * Using ``MSVC_USE_SCRIPT`` and ``MSVC_USE_SCRIPT_ARGS`` to call older Microsoft SDK - ``SetEnv.cmd`` batch files may result in build failures. Some of these batch files - require delayed expansion to be enabled which is not usually the Windows default. - One solution would be to launch the MSVC batch file command in a new command interpreter - instance with delayed expansion enabled via command-line options. + ``SetEnv.cmd`` batch files may result in build failures. + + Typically, the reasons for build failures with SDK batch files are one, or both, of: + + * The batch files require delayed expansion to be enabled which is not usually the Windows default. + + * The batch files inspect environment variables that are not defined in the minimal subprocess + environment in which the batch files are invoked. * The code to suppress the "No versions of the MSVC compiler were found" warning for the default environment was moved from ``MSCommon/vc.py`` to ``MSCommon/MSVC/SetupEnvDefault.py``. @@ -188,17 +338,21 @@ Batch File Arguments Supported MSVC batch file arguments by product: -======= === === ======= ======= -Product UWP SDK Toolset Spectre -======= === === ======= ======= -VS2022 X X X X -------- --- --- ------- ------- -VS2019 X X X X -------- --- --- ------- ------- -VS2017 X X X X -------- --- --- ------- ------- -VS2015 X X -======= === === ======= ======= +======= ======= ====== ======= ======= +Product UWP SDK Toolset Spectre +======= ======= ====== ======= ======= +VS2022 X X X X +------- ------- ------ ------- ------- +VS2019 X X X X +------- ------- ------ ------- ------- +VS2017 X X X X +------- ------- ------ ------- ------- +VS2015 X [1]_ X [2]_ +======= ======= ====== ======= ======= + +.. [1] The BuildTools edition does not support the ``store`` argument. The Express edition + supports the ``store`` argument for the ``x86`` target only. +.. [2] The ``sdk version`` argument is not supported in the BuildTools and Express editions. Supported MSVC batch file arguments in SCons: @@ -315,13 +469,17 @@ Visual Studio Version Notes SDK Versions ------------ -==== ============ +==== ================= SDK Format -==== ============ -10.0 10.0.XXXXX.Y ----- ------------ +==== ================= +10.0 10.0.XXXXX.Y [*]_ +---- ----------------- 8.1 8.1 -==== ============ +==== ================= + +.. [*] The Windows 10 SDK version number is 10.0.20348.0 and earlier. + + The Windows 11 SDK version number is 10.0.22000.194 and later. BuildTools Versions ------------------- diff --git a/SCons/Tool/MSCommon/__init__.py b/SCons/Tool/MSCommon/__init__.py index c3078ac630..e624e29d7e 100644 --- a/SCons/Tool/MSCommon/__init__.py +++ b/SCons/Tool/MSCommon/__init__.py @@ -55,6 +55,15 @@ query_versions, ) +from .MSVC.VSWhere import ( # noqa: F401 + vswhere_push_location, +) + +from .MSVC.VSDetect import ( # noqa: F401 + msvs_set_channel_default, + msvs_get_channel_default, +) + from .MSVC.Policy import ( # noqa: F401 msvc_set_notfound_policy, msvc_get_notfound_policy, diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py index 56d9c381ea..d0ee10154f 100644 --- a/SCons/Tool/MSCommon/vc.py +++ b/SCons/Tool/MSCommon/vc.py @@ -25,9 +25,6 @@ MS Compilers: Visual C/C++ detection and configuration. # TODO: -# * gather all the information from a single vswhere call instead -# of calling repeatedly (use json format?) -# * support passing/setting location for vswhere in env. # * supported arch for versions: for old versions of batch file without # argument, giving bogus argument cannot be detected, so we have to hardcode # this here @@ -38,15 +35,11 @@ # * Assembly """ -import SCons.compat - -import subprocess import os import platform import sysconfig from pathlib import Path from string import digits as string_digits -from subprocess import PIPE import re from collections import ( namedtuple, @@ -55,16 +48,18 @@ import SCons.Util import SCons.Warnings -from SCons.Tool import find_program_path from . import common -from .common import CONFIG_CACHE, debug -from .sdk import get_installed_sdks +from .common import ( + CONFIG_CACHE, + debug, +) from . import MSVC from .MSVC.Exceptions import ( VisualCException, + MSVCInternalError, MSVCUserError, MSVCArgumentError, MSVCToolsetVersionNotFound, @@ -86,25 +81,17 @@ class MSVCUseSettingsError(MSVCUserError): # internal exceptions -class UnsupportedVersion(VisualCException): - pass - class BatchFileExecutionError(VisualCException): pass # undefined object for dict.get() in case key exists and value is None -UNDEFINED = object() +UNDEFINED = MSVC.Config.UNDEFINED # powershell error sending telemetry for arm32 process on arm64 host (VS2019+): # True: force VSCMD_SKIP_SENDTELEMETRY=1 (if necessary) # False: do nothing _ARM32_ON_ARM64_SKIP_SENDTELEMETRY = True -# MSVC 9.0 preferred query order: -# True: VCForPython, VisualStudio -# FAlse: VisualStudio, VCForPython -_VC90_Prefer_VCForPython = True - # Dict to 'canonalize' the arch _ARCH_TO_CANONICAL = { "amd64" : "amd64", @@ -295,22 +282,22 @@ def _make_target_host_map(all_hosts, host_all_targets_map): # 14.3 (VS2022) and later -_GE2022_HOST_TARGET_BATCHFILE_CLPATHCOMPS = { +_GE2022_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS = { - ('amd64', 'amd64') : ('vcvars64.bat', ('bin', 'Hostx64', 'x64')), - ('amd64', 'x86') : ('vcvarsamd64_x86.bat', ('bin', 'Hostx64', 'x86')), - ('amd64', 'arm') : ('vcvarsamd64_arm.bat', ('bin', 'Hostx64', 'arm')), - ('amd64', 'arm64') : ('vcvarsamd64_arm64.bat', ('bin', 'Hostx64', 'arm64')), + ('amd64', 'amd64') : ('amd64', 'vcvars64.bat', ('bin', 'Hostx64', 'x64')), + ('amd64', 'x86') : ('amd64_x86', 'vcvarsamd64_x86.bat', ('bin', 'Hostx64', 'x86')), + ('amd64', 'arm') : ('amd64_arm', 'vcvarsamd64_arm.bat', ('bin', 'Hostx64', 'arm')), + ('amd64', 'arm64') : ('amd64_arm64', 'vcvarsamd64_arm64.bat', ('bin', 'Hostx64', 'arm64')), - ('x86', 'amd64') : ('vcvarsx86_amd64.bat', ('bin', 'Hostx86', 'x64')), - ('x86', 'x86') : ('vcvars32.bat', ('bin', 'Hostx86', 'x86')), - ('x86', 'arm') : ('vcvarsx86_arm.bat', ('bin', 'Hostx86', 'arm')), - ('x86', 'arm64') : ('vcvarsx86_arm64.bat', ('bin', 'Hostx86', 'arm64')), + ('x86', 'amd64') : ('x86_amd64', 'vcvarsx86_amd64.bat', ('bin', 'Hostx86', 'x64')), + ('x86', 'x86') : ('x86', 'vcvars32.bat', ('bin', 'Hostx86', 'x86')), + ('x86', 'arm') : ('x86_arm', 'vcvarsx86_arm.bat', ('bin', 'Hostx86', 'arm')), + ('x86', 'arm64') : ('x86_arm64', 'vcvarsx86_arm64.bat', ('bin', 'Hostx86', 'arm64')), - ('arm64', 'amd64') : ('vcvarsarm64_amd64.bat', ('bin', 'Hostarm64', 'arm64_amd64')), - ('arm64', 'x86') : ('vcvarsarm64_x86.bat', ('bin', 'Hostarm64', 'arm64_x86')), - ('arm64', 'arm') : ('vcvarsarm64_arm.bat', ('bin', 'Hostarm64', 'arm64_arm')), - ('arm64', 'arm64') : ('vcvarsarm64.bat', ('bin', 'Hostarm64', 'arm64')), + ('arm64', 'amd64') : ('arm64_amd64', 'vcvarsarm64_amd64.bat', ('bin', 'Hostarm64', 'arm64_amd64')), + ('arm64', 'x86') : ('arm64_x86', 'vcvarsarm64_x86.bat', ('bin', 'Hostarm64', 'arm64_x86')), + ('arm64', 'arm') : ('arm64_arm', 'vcvarsarm64_arm.bat', ('bin', 'Hostarm64', 'arm64_arm')), + ('arm64', 'arm64') : ('arm64', 'vcvarsarm64.bat', ('bin', 'Hostarm64', 'arm64')), } @@ -345,22 +332,22 @@ def _make_target_host_map(all_hosts, host_all_targets_map): # 14.2 (VS2019) to 14.1 (VS2017) -_LE2019_HOST_TARGET_BATCHFILE_CLPATHCOMPS = { +_LE2019_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS = { - ('amd64', 'amd64') : ('vcvars64.bat', ('bin', 'Hostx64', 'x64')), - ('amd64', 'x86') : ('vcvarsamd64_x86.bat', ('bin', 'Hostx64', 'x86')), - ('amd64', 'arm') : ('vcvarsamd64_arm.bat', ('bin', 'Hostx64', 'arm')), - ('amd64', 'arm64') : ('vcvarsamd64_arm64.bat', ('bin', 'Hostx64', 'arm64')), + ('amd64', 'amd64') : ('amd64', 'vcvars64.bat', ('bin', 'Hostx64', 'x64')), + ('amd64', 'x86') : ('amd64_x86', 'vcvarsamd64_x86.bat', ('bin', 'Hostx64', 'x86')), + ('amd64', 'arm') : ('amd64_arm', 'vcvarsamd64_arm.bat', ('bin', 'Hostx64', 'arm')), + ('amd64', 'arm64') : ('amd64_arm64', 'vcvarsamd64_arm64.bat', ('bin', 'Hostx64', 'arm64')), - ('x86', 'amd64') : ('vcvarsx86_amd64.bat', ('bin', 'Hostx86', 'x64')), - ('x86', 'x86') : ('vcvars32.bat', ('bin', 'Hostx86', 'x86')), - ('x86', 'arm') : ('vcvarsx86_arm.bat', ('bin', 'Hostx86', 'arm')), - ('x86', 'arm64') : ('vcvarsx86_arm64.bat', ('bin', 'Hostx86', 'arm64')), + ('x86', 'amd64') : ('x86_amd64', 'vcvarsx86_amd64.bat', ('bin', 'Hostx86', 'x64')), + ('x86', 'x86') : ('x86', 'vcvars32.bat', ('bin', 'Hostx86', 'x86')), + ('x86', 'arm') : ('x86_arm', 'vcvarsx86_arm.bat', ('bin', 'Hostx86', 'arm')), + ('x86', 'arm64') : ('x86_arm64', 'vcvarsx86_arm64.bat', ('bin', 'Hostx86', 'arm64')), - ('arm64', 'amd64') : ('vcvars64.bat', ('bin', 'Hostx64', 'x64')), - ('arm64', 'x86') : ('vcvarsamd64_x86.bat', ('bin', 'Hostx64', 'x86')), - ('arm64', 'arm') : ('vcvarsamd64_arm.bat', ('bin', 'Hostx64', 'arm')), - ('arm64', 'arm64') : ('vcvarsamd64_arm64.bat', ('bin', 'Hostx64', 'arm64')), + ('arm64', 'amd64') : ('amd64', 'vcvars64.bat', ('bin', 'Hostx64', 'x64')), + ('arm64', 'x86') : ('amd64_x86', 'vcvarsamd64_x86.bat', ('bin', 'Hostx64', 'x86')), + ('arm64', 'arm') : ('amd64_arm', 'vcvarsamd64_arm.bat', ('bin', 'Hostx64', 'arm')), + ('arm64', 'arm64') : ('amd64_arm64', 'vcvarsamd64_arm64.bat', ('bin', 'Hostx64', 'arm64')), } @@ -393,7 +380,7 @@ def _make_target_host_map(all_hosts, host_all_targets_map): # debug("_LE2019_HOST_TARGET_CFG: %s", _LE2019_HOST_TARGET_CFG) -# 14.0 (VS2015) to 8.0 (VS2005) +# 14.0 (VS2015) to 10.0 (VS2010) # Given a (host, target) tuple, return a tuple containing the argument for # the batch file and a tuple of the path components to find cl.exe. @@ -402,23 +389,23 @@ def _make_target_host_map(all_hosts, host_all_targets_map): # bin directory (i.e., /VC/bin). Any other tools are in subdirectory # named for the the host/target pair or a single name if the host==target. -_LE2015_HOST_TARGET_BATCHARG_CLPATHCOMPS = { +_LE2015_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS = { - ('amd64', 'amd64') : ('amd64', ('bin', 'amd64')), - ('amd64', 'x86') : ('amd64_x86', ('bin', 'amd64_x86')), - ('amd64', 'arm') : ('amd64_arm', ('bin', 'amd64_arm')), + ('amd64', 'amd64') : ('amd64', 'vcvars64.bat', ('bin', 'amd64')), + ('amd64', 'x86') : ('amd64_x86', 'vcvarsamd64_x86.bat', ('bin', 'amd64_x86')), + ('amd64', 'arm') : ('amd64_arm', 'vcvarsamd64_arm.bat', ('bin', 'amd64_arm')), - ('x86', 'amd64') : ('x86_amd64', ('bin', 'x86_amd64')), - ('x86', 'x86') : ('x86', ('bin', )), - ('x86', 'arm') : ('x86_arm', ('bin', 'x86_arm')), - ('x86', 'ia64') : ('x86_ia64', ('bin', 'x86_ia64')), + ('x86', 'amd64') : ('x86_amd64', 'vcvarsx86_amd64.bat', ('bin', 'x86_amd64')), + ('x86', 'x86') : ('x86', 'vcvars32.bat', ('bin', )), + ('x86', 'arm') : ('x86_arm', 'vcvarsx86_arm.bat', ('bin', 'x86_arm')), + ('x86', 'ia64') : ('x86_ia64', 'vcvarsx86_ia64.bat', ('bin', 'x86_ia64')), - ('arm64', 'amd64') : ('amd64', ('bin', 'amd64')), - ('arm64', 'x86') : ('amd64_x86', ('bin', 'amd64_x86')), - ('arm64', 'arm') : ('amd64_arm', ('bin', 'amd64_arm')), + ('arm64', 'amd64') : ('amd64', 'vcvars64.bat', ('bin', 'amd64')), + ('arm64', 'x86') : ('amd64_x86', 'vcvarsamd64_x86.bat', ('bin', 'amd64_x86')), + ('arm64', 'arm') : ('amd64_arm', 'vcvarsamd64_arm.bat', ('bin', 'amd64_arm')), - ('arm', 'arm') : ('arm', ('bin', 'arm')), - ('ia64', 'ia64') : ('ia64', ('bin', 'ia64')), + ('arm', 'arm') : ('arm', 'vcvarsarm.bat', ('bin', 'arm')), + ('ia64', 'ia64') : ('ia64', 'vcvars64.bat', ('bin', 'ia64')), } @@ -454,10 +441,62 @@ def _make_target_host_map(all_hosts, host_all_targets_map): # debug("_LE2015_HOST_TARGET_CFG: %s", _LE2015_HOST_TARGET_CFG) +# 9.0 (VS2008) to 8.0 (VS2005) + +_LE2008_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS = { + + ('amd64', 'amd64') : ('amd64', 'vcvarsamd64.bat', ('bin', 'amd64')), + ('amd64', 'x86') : ('x86', 'vcvars32.bat', ('bin', )), + + ('x86', 'amd64') : ('x86_amd64', 'vcvarsx86_amd64.bat', ('bin', 'x86_amd64')), + ('x86', 'x86') : ('x86', 'vcvars32.bat', ('bin', )), + ('x86', 'ia64') : ('x86_ia64', 'vcvarsx86_ia64.bat', ('bin', 'x86_ia64')), + + ('arm64', 'amd64') : ('amd64', 'vcvarsamd64.bat', ('bin', 'amd64')), + ('arm64', 'x86') : ('x86', 'vcvars32.bat', ('bin', )), + + ('ia64', 'ia64') : ('ia64', 'vcvarsia64.bat', ('bin', 'ia64')), + +} + +_LE2008_HOST_TARGET_CFG = _host_target_config_factory( + + label = 'LE2008', + + host_all_hosts = OrderedDict([ + ('amd64', ['amd64', 'x86']), + ('x86', ['x86']), + ('arm64', ['amd64', 'x86']), + ('ia64', ['ia64']), + ]), + + host_all_targets = { + 'amd64': ['amd64', 'x86'], + 'x86': ['x86', 'amd64', 'ia64'], + 'arm64': ['amd64', 'x86'], + 'ia64': ['ia64'], + }, + + host_def_targets = { + 'amd64': ['amd64', 'x86'], + 'x86': ['x86'], + 'arm64': ['amd64', 'x86'], + 'ia64': ['ia64'], + }, + +) + +# debug("_LE2008_HOST_TARGET_CFG: %s", _LE2008_HOST_TARGET_CFG) + # 7.1 (VS2003) and earlier -# For 7.1 (VS2003) and earlier, there are only x86 targets and the batch files -# take no arguments. +_LE2003_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS = { + + ('amd64', 'x86') : ('x86', 'vcvars32.bat', ('bin', )), + ('x86', 'x86') : ('x86', 'vcvars32.bat', ('bin', )), + ('arm64', 'x86') : ('x86', 'vcvars32.bat', ('bin', )), + +} _LE2003_HOST_TARGET_CFG = _host_target_config_factory( @@ -566,9 +605,12 @@ def get_host_target(env, msvc_version, all_host_targets: bool=False): elif 143 > vernum_int >= 141: # 14.2 (VS2019) to 14.1 (VS2017) host_target_cfg = _LE2019_HOST_TARGET_CFG - elif 141 > vernum_int >= 80: - # 14.0 (VS2015) to 8.0 (VS2005) + elif 141 > vernum_int >= 100: + # 14.0 (VS2015) to 10.0 (VS2010) host_target_cfg = _LE2015_HOST_TARGET_CFG + elif 100 > vernum_int >= 80: + # 9.0 (VS2008) to 8.0 (VS2005) + host_target_cfg = _LE2008_HOST_TARGET_CFG else: # 80 > vernum_int # 7.1 (VS2003) and earlier host_target_cfg = _LE2003_HOST_TARGET_CFG @@ -604,8 +646,8 @@ def get_host_target(env, msvc_version, all_host_targets: bool=False): warn_msg = "unsupported host, target combination ({}, {}) for MSVC version {}".format( repr(host_platform), repr(target_platform), msvc_version ) - SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg) debug(warn_msg) + SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg) else: @@ -687,84 +729,6 @@ def _skip_sendtelemetry(env): "7.0", "6.0"] -# if using vswhere, configure command line arguments to probe for installed VC editions -_VCVER_TO_VSWHERE_VER = { - '14.3': [ - ["-version", "[17.0, 18.0)"], # default: Enterprise, Professional, Community (order unpredictable?) - ["-version", "[17.0, 18.0)", "-products", "Microsoft.VisualStudio.Product.BuildTools"], # BuildTools - ], - '14.2': [ - ["-version", "[16.0, 17.0)"], # default: Enterprise, Professional, Community (order unpredictable?) - ["-version", "[16.0, 17.0)", "-products", "Microsoft.VisualStudio.Product.BuildTools"], # BuildTools - ], - '14.1': [ - ["-version", "[15.0, 16.0)"], # default: Enterprise, Professional, Community (order unpredictable?) - ["-version", "[15.0, 16.0)", "-products", "Microsoft.VisualStudio.Product.BuildTools"], # BuildTools - ], - '14.1Exp': [ - ["-version", "[15.0, 16.0)", "-products", "Microsoft.VisualStudio.Product.WDExpress"], # Express - ], -} - -_VCVER_TO_PRODUCT_DIR = { - '14.3': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'')], # not set by this version - '14.2': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'')], # not set by this version - '14.1': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'')], # not set by this version - '14.1Exp': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'')], # not set by this version - '14.0': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\14.0\Setup\VC\ProductDir')], - '14.0Exp': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\14.0\Setup\VC\ProductDir')], - '12.0': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\12.0\Setup\VC\ProductDir'), - ], - '12.0Exp': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\12.0\Setup\VC\ProductDir'), - ], - '11.0': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\11.0\Setup\VC\ProductDir'), - ], - '11.0Exp': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\11.0\Setup\VC\ProductDir'), - ], - '10.0': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\10.0\Setup\VC\ProductDir'), - ], - '10.0Exp': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\10.0\Setup\VC\ProductDir'), - ], - '9.0': [ - (SCons.Util.HKEY_CURRENT_USER, r'Microsoft\DevDiv\VCForPython\9.0\installdir',), - (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\9.0\Setup\VC\ProductDir',), - ] if _VC90_Prefer_VCForPython else [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\9.0\Setup\VC\ProductDir',), - (SCons.Util.HKEY_CURRENT_USER, r'Microsoft\DevDiv\VCForPython\9.0\installdir',), - ], - '9.0Exp': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\9.0\Setup\VC\ProductDir'), - ], - '8.0': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\8.0\Setup\VC\ProductDir'), - ], - '8.0Exp': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\8.0\Setup\VC\ProductDir'), - ], - '7.1': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\7.1\Setup\VC\ProductDir'), - ], - '7.0': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\7.0\Setup\VC\ProductDir'), - ], - '6.0': [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\6.0\Setup\Microsoft Visual C++\ProductDir'), - ] -} - - def msvc_version_to_maj_min(msvc_version): msvc_version_numeric = get_msvc_version_numeric(msvc_version) @@ -778,144 +742,58 @@ def msvc_version_to_maj_min(msvc_version): except ValueError: raise ValueError("Unrecognized version %s (%s)" % (msvc_version,msvc_version_numeric)) from None - -VSWHERE_PATHS = [os.path.join(p,'vswhere.exe') for p in [ - os.path.expandvars(r"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer"), - os.path.expandvars(r"%ProgramFiles%\Microsoft Visual Studio\Installer"), - os.path.expandvars(r"%ChocolateyInstall%\bin"), -]] - -def msvc_find_vswhere(): +def msvc_find_vswhere(env=None): """ Find the location of vswhere """ - # For bug 3333: support default location of vswhere for both - # 64 and 32 bit windows installs. - # For bug 3542: also accommodate not being on C: drive. # NB: this gets called from testsuite on non-Windows platforms. # Whether that makes sense or not, don't break it for those. - vswhere_path = None - for pf in VSWHERE_PATHS: - if os.path.exists(pf): - vswhere_path = pf - break - - return vswhere_path - -def find_vc_pdir_vswhere(msvc_version, env=None): - """ Find the MSVC product directory using the vswhere program. - - Args: - msvc_version: MSVC version to search for - env: optional to look up VSWHERE variable - - Returns: - MSVC install dir or None - - Raises: - UnsupportedVersion: if the version is not known by this file - - """ - try: - vswhere_version = _VCVER_TO_VSWHERE_VER[msvc_version] - except KeyError: - debug("Unknown version of MSVC: %s", msvc_version) - raise UnsupportedVersion("Unknown version %s" % msvc_version) from None - - if env is None or not env.get('VSWHERE'): - vswhere_path = msvc_find_vswhere() + vswhere_executables = MSVC.VSWhere.vswhere_get_executables_env(env) + if vswhere_executables: + vswhere_path = vswhere_executables[0].path else: - vswhere_path = env.subst('$VSWHERE') - - if vswhere_path is None: - return None + vswhere_path = None + debug('vswhere_path=%s', vswhere_path) + return vswhere_path - debug('VSWHERE: %s', vswhere_path) - for vswhere_version_args in vswhere_version: +def _find_msvc_instance(msvc_version, env=None): - vswhere_cmd = [vswhere_path] + vswhere_version_args + ["-property", "installationPath"] + msvc_instance = None + query_key = None - debug("running: %s", vswhere_cmd) + if msvc_version not in _VCVER: + # TODO(JCB): issue warning (add policy?) + debug("Unknown version of MSVC: %s", repr(msvc_version)) + return msvc_instance, query_key - # TODO: Python 3.7 - # cp = subprocess.run(vswhere_cmd, capture_output=True, check=True) # 3.7+ only - cp = subprocess.run(vswhere_cmd, stdout=PIPE, stderr=PIPE, check=True) + msvs_manager = MSVC.VSDetect.msvs_detect_env(env) - if cp.stdout: - # vswhere could return multiple lines, e.g. if Build Tools - # and {Community,Professional,Enterprise} are both installed. - # We could define a way to pick the one we prefer, but since - # this data is currently only used to make a check for existence, - # returning the first hit should be good enough. - lines = cp.stdout.decode("mbcs").splitlines() - return os.path.join(lines[0], 'VC') - else: - # We found vswhere, but no install info available for this version - pass + msvc_instances, query_key = msvs_manager.query_msvc_instances(msvc_version=msvc_version) - return None + msvc_instance = msvc_instances[0] if msvc_instances else None + debug( + 'msvc_version=%s, vc_dir=%s', + repr(msvc_version), + repr(msvc_instance.vc_dir if msvc_instance else None) + ) -def find_vc_pdir(env, msvc_version): - """Find the MSVC product directory for the given version. + return msvc_instance, query_key - Tries to look up the path using a registry key from the table - _VCVER_TO_PRODUCT_DIR; if there is no key, calls find_vc_pdir_wshere - for help instead. +def find_msvc_instance(msvc_version, env=None): + """Select an MSVC installation record. Args: msvc_version: str msvc version (major.minor, e.g. 10.0) + env: optional + env to look up construction variables Returns: - str: Path found in registry, or None - - Raises: - UnsupportedVersion: if the version is not known by this file. - - UnsupportedVersion inherits from VisualCException. - + str: MSVC installation record or None """ - root = 'Software\\' - try: - hkeys = _VCVER_TO_PRODUCT_DIR[msvc_version] - except KeyError: - debug("Unknown version of MSVC: %s", msvc_version) - raise UnsupportedVersion("Unknown version %s" % msvc_version) from None + msvc_instance, _ = _find_msvc_instance(msvc_version, env) + return msvc_instance - for hkroot, key in hkeys: - try: - comps = None - if not key: - comps = find_vc_pdir_vswhere(msvc_version, env) - if not comps: - debug('no VC found for version %s', repr(msvc_version)) - raise OSError - debug('VC found: %s', repr(msvc_version)) - return comps - else: - if common.is_win64(): - try: - # ordinarily at win64, try Wow6432Node first. - comps = common.read_reg(root + 'Wow6432Node\\' + key, hkroot) - except OSError: - # at Microsoft Visual Studio for Python 2.7, value is not in Wow6432Node - pass - if not comps: - # not Win64, or Microsoft Visual Studio for Python 2.7 - comps = common.read_reg(root + key, hkroot) - except OSError: - debug('no VC registry key %s', repr(key)) - else: - if msvc_version == '9.0' and key.lower().endswith('\\vcforpython\\9.0\\installdir'): - # Visual C++ for Python registry key is installdir (root) not productdir (vc) - comps = os.path.join(comps, 'VC') - debug('found VC in registry: %s', comps) - if os.path.exists(comps): - return comps - else: - debug('reg says dir is %s, but it does not exist. (ignoring)', comps) - return None - -def find_batch_file(msvc_version, host_arch, target_arch, pdir): +def find_batch_file(msvc_instance, host_arch, target_arch): """ Find the location of the batch script which should set up the compiler for any TARGET_ARCH whose compilers were installed by Visual Studio/VCExpress @@ -925,59 +803,70 @@ def find_batch_file(msvc_version, host_arch, target_arch, pdir): so use that and return an empty argument. """ - # filter out e.g. "Exp" from the version name - vernum = float(get_msvc_version_numeric(msvc_version)) - vernum_int = int(vernum * 10) - - sdk_pdir = pdir - arg = '' - vcdir = None + batfilename = None clexe = None + depbat = None - if vernum_int >= 143: + pdir = msvc_instance.vc_dir + + if msvc_instance.vs_product_numeric >= 2022: # 14.3 (VS2022) and later batfiledir = os.path.join(pdir, "Auxiliary", "Build") - batfile, _ = _GE2022_HOST_TARGET_BATCHFILE_CLPATHCOMPS[(host_arch, target_arch)] + _, batfile, _ = _GE2022_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS[(host_arch, target_arch)] batfilename = os.path.join(batfiledir, batfile) - vcdir = pdir - elif 143 > vernum_int >= 141: + elif 2022 > msvc_instance.vs_product_numeric >= 2017: # 14.2 (VS2019) to 14.1 (VS2017) batfiledir = os.path.join(pdir, "Auxiliary", "Build") - batfile, _ = _LE2019_HOST_TARGET_BATCHFILE_CLPATHCOMPS[(host_arch, target_arch)] + _, batfile, _ = _LE2019_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS[(host_arch, target_arch)] batfilename = os.path.join(batfiledir, batfile) - vcdir = pdir - elif 141 > vernum_int >= 80: - # 14.0 (VS2015) to 8.0 (VS2005) - arg, cl_path_comps = _LE2015_HOST_TARGET_BATCHARG_CLPATHCOMPS[(host_arch, target_arch)] + elif 2017 > msvc_instance.vs_product_numeric >= 2010: + # 14.0 (VS2015) to 10.0 (VS2010) + arg, batfile, cl_path_comps = _LE2015_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS[(host_arch, target_arch)] batfilename = os.path.join(pdir, "vcvarsall.bat") - if msvc_version == '9.0' and not os.path.exists(batfilename): - # Visual C++ for Python batch file is in installdir (root) not productdir (vc) - batfilename = os.path.normpath(os.path.join(pdir, os.pardir, "vcvarsall.bat")) - # Visual C++ for Python sdk batch files do not point to the VCForPython installation - sdk_pdir = None + depbat = os.path.join(pdir, *cl_path_comps, batfile) clexe = os.path.join(pdir, *cl_path_comps, _CL_EXE_NAME) - else: # 80 > vernum_int + elif 2010 > msvc_instance.vs_product_numeric >= 2005: + # 9.0 (VS2008) to 8.0 (VS2005) + arg, batfile, cl_path_comps = _LE2008_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS[(host_arch, target_arch)] + if msvc_instance.is_vcforpython: + # 9.0 (VS2008) Visual C++ for Python: + # sdk batch files do not point to the VCForPython installation + # vcvarsall batch file is in installdir not productdir (e.g., vc\..\vcvarsall.bat) + # dependency batch files are not called from vcvarsall.bat + batfilename = os.path.join(pdir, os.pardir, "vcvarsall.bat") + depbat = None + else: + batfilename = os.path.join(pdir, "vcvarsall.bat") + depbat = os.path.join(pdir, *cl_path_comps, batfile) + clexe = os.path.join(pdir, *cl_path_comps, _CL_EXE_NAME) + else: # 2005 > vc_product_numeric >= 6.0 # 7.1 (VS2003) and earlier - pdir = os.path.join(pdir, "Bin") - batfilename = os.path.join(pdir, "vcvars32.bat") - clexe = os.path.join(pdir, _CL_EXE_NAME) + _, batfile, cl_path_comps = _LE2003_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS[(host_arch, target_arch)] + batfilename = os.path.join(pdir, *cl_path_comps, batfile) + clexe = os.path.join(pdir, *cl_path_comps, _CL_EXE_NAME) if not os.path.exists(batfilename): debug("batch file not found: %s", batfilename) batfilename = None if clexe and not os.path.exists(clexe): - debug("cl.exe not found: %s", clexe) + debug("%s not found: %s", _CL_EXE_NAME, clexe) batfilename = None - return batfilename, arg, vcdir, sdk_pdir + if depbat and not os.path.exists(depbat): + debug("dependency batch file not found: %s", depbat) + batfilename = None + + return batfilename, arg +# TODO(JCB): test and remove (orphaned) def find_batch_file_sdk(host_arch, target_arch, sdk_pdir): """ Find the location of the sdk batch script which should set up the compiler for any TARGET_ARCH whose compilers were installed by Visual Studio/VCExpress """ + from .sdk import get_installed_sdks installed_sdks = get_installed_sdks() for _sdk in installed_sdks: @@ -992,27 +881,31 @@ def find_batch_file_sdk(host_arch, target_arch, sdk_pdir): return None -__INSTALLED_VCS_RUN = None +_cache_installed_msvc_versions = None +_cache_installed_msvc_instances = None + +def _reset_installed_vcs() -> None: + global _cache_installed_msvc_versions + global _cache_installed_msvc_instances + _cache_installed_msvc_versions = None + _cache_installed_msvc_instances = None + debug('reset') + +# register vcs cache reset function with vs detection +MSVC.VSDetect.register_reset_func(_reset_installed_vcs) + _VC_TOOLS_VERSION_FILE_PATH = ['Auxiliary', 'Build', 'Microsoft.VCToolsVersion.default.txt'] _VC_TOOLS_VERSION_FILE = os.sep.join(_VC_TOOLS_VERSION_FILE_PATH) -def _check_cl_exists_in_vc_dir(env, vc_dir, msvc_version) -> bool: - """Return status of finding a cl.exe to use. +def _msvc_instance_check_files_exist(msvc_instance) -> bool: + """Return status of finding at least one batch file and cl.exe. - Locates cl in the vc_dir depending on TARGET_ARCH, HOST_ARCH and the - msvc version. TARGET_ARCH and HOST_ARCH can be extracted from the - passed env, unless the env is None, in which case the native platform is - assumed for the host and all associated targets. + Locates at least one required vcvars batch file and cl.ex in the vc_dir. + The native platform is assumed for the host and all associated targets. Args: - env: Environment - a construction environment, usually if this is passed its - because there is a desired TARGET_ARCH to be used when searching - for a cl.exe - vc_dir: str - the path to the VC dir in the MSVC installation - msvc_version: str - msvc version (major.minor, e.g. 10.0) + msvc_instance: + MSVC installation record Returns: bool: @@ -1020,21 +913,18 @@ def _check_cl_exists_in_vc_dir(env, vc_dir, msvc_version) -> bool: """ # Find the host, target, and all candidate (host, target) platform combinations: - platforms = get_host_target(env, msvc_version, all_host_targets=True) - debug("host_platform %s, target_platform %s host_target_list %s", *platforms) + platforms = get_host_target(None, msvc_instance.msvc_version, all_host_targets=True) + debug("host_platform=%s, target_platform=%s, host_target_list=%s", *platforms) host_platform, target_platform, host_target_list = platforms - vernum = float(get_msvc_version_numeric(msvc_version)) - vernum_int = int(vernum * 10) - # make sure the cl.exe exists meaning the tool is installed - if vernum_int >= 141: + if msvc_instance.vs_product_numeric >= 2017: # 14.1 (VS2017) and later # 2017 and newer allowed multiple versions of the VC toolset to be # installed at the same time. This changes the layout. # Just get the default tool version for now # TODO: support setting a specific minor VC version - default_toolset_file = os.path.join(vc_dir, _VC_TOOLS_VERSION_FILE) + default_toolset_file = os.path.join(msvc_instance.vc_dir, _VC_TOOLS_VERSION_FILE) try: with open(default_toolset_file) as f: vc_specific_version = f.readlines()[0].strip() @@ -1045,121 +935,176 @@ def _check_cl_exists_in_vc_dir(env, vc_dir, msvc_version) -> bool: debug('failed to find MSVC version in %s', default_toolset_file) return False - if vernum_int >= 143: + if msvc_instance.vs_product_numeric >= 2022: # 14.3 (VS2022) and later - host_target_batchfile_clpathcomps = _GE2022_HOST_TARGET_BATCHFILE_CLPATHCOMPS + host_target_batcharg_batchfile_clpathcomps = _GE2022_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS else: # 14.2 (VS2019) to 14.1 (VS2017) - host_target_batchfile_clpathcomps = _LE2019_HOST_TARGET_BATCHFILE_CLPATHCOMPS + host_target_batcharg_batchfile_clpathcomps = _LE2019_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS for host_platform, target_platform in host_target_list: - debug('host platform %s, target platform %s for version %s', host_platform, target_platform, msvc_version) + debug( + 'host_platform=%s, target_platform=%s, msvc_version=%s', + host_platform, target_platform, msvc_instance.msvc_version + ) - batchfile_clpathcomps = host_target_batchfile_clpathcomps.get((host_platform, target_platform), None) - if batchfile_clpathcomps is None: + batcharg_batchfile_clpathcomps = host_target_batcharg_batchfile_clpathcomps.get( + (host_platform, target_platform), None + ) + if batcharg_batchfile_clpathcomps is None: debug('unsupported host/target platform combo: (%s,%s)', host_platform, target_platform) continue - _, cl_path_comps = batchfile_clpathcomps - cl_path = os.path.join(vc_dir, 'Tools', 'MSVC', vc_specific_version, *cl_path_comps, _CL_EXE_NAME) - debug('checking for %s at %s', _CL_EXE_NAME, cl_path) + _, batfile, cl_path_comps = batcharg_batchfile_clpathcomps - if os.path.exists(cl_path): - debug('found %s!', _CL_EXE_NAME) - return True + batfile_path = os.path.join(msvc_instance.vc_dir, "Auxiliary", "Build", batfile) + if not os.path.exists(batfile_path): + debug("batch file not found: %s", batfile_path) + continue - elif 141 > vernum_int >= 80: + cl_path = os.path.join( + msvc_instance.vc_dir, 'Tools', 'MSVC', vc_specific_version, *cl_path_comps, _CL_EXE_NAME + ) + if not os.path.exists(cl_path): + debug("%s not found: %s", _CL_EXE_NAME, cl_path) + continue + + debug('%s found: %s', _CL_EXE_NAME, cl_path) + return True + + elif 2017 > msvc_instance.vs_product_numeric >= 2005: # 14.0 (VS2015) to 8.0 (VS2005) + if msvc_instance.vs_product_numeric >= 2010: + # 14.0 (VS2015) to 10.0 (VS2010) + host_target_batcharg_batchfile_clpathcomps = _LE2015_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS + else: + # 9.0 (VS2008) to 8.0 (VS2005) + host_target_batcharg_batchfile_clpathcomps = _LE2008_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS + + if msvc_instance.is_vcforpython: + # 9.0 (VS2008) Visual C++ for Python: + # vcvarsall batch file is in installdir not productdir (e.g., vc\..\vcvarsall.bat) + # dependency batch files are not called from vcvarsall.bat + batfile_path = os.path.join(msvc_instance.vc_dir, os.pardir, "vcvarsall.bat") + check_depbat = False + else: + batfile_path = os.path.join(msvc_instance.vc_dir, "vcvarsall.bat") + check_depbat = True + + if not os.path.exists(batfile_path): + debug("batch file not found: %s", batfile_path) + return False + for host_platform, target_platform in host_target_list: - debug('host platform %s, target platform %s for version %s', host_platform, target_platform, msvc_version) + debug( + 'host_platform=%s, target_platform=%s, msvc_version=%s', + host_platform, target_platform, msvc_instance.msvc_version + ) + + batcharg_batchfile_clpathcomps = host_target_batcharg_batchfile_clpathcomps.get( + (host_platform, target_platform), None + ) - batcharg_clpathcomps = _LE2015_HOST_TARGET_BATCHARG_CLPATHCOMPS.get((host_platform, target_platform), None) - if batcharg_clpathcomps is None: + if batcharg_batchfile_clpathcomps is None: debug('unsupported host/target platform combo: (%s,%s)', host_platform, target_platform) continue - _, cl_path_comps = batcharg_clpathcomps - cl_path = os.path.join(vc_dir, *cl_path_comps, _CL_EXE_NAME) - debug('checking for %s at %s', _CL_EXE_NAME, cl_path) + _, batfile, cl_path_comps = batcharg_batchfile_clpathcomps + + if check_depbat: + batfile_path = os.path.join(msvc_instance.vc_dir, *cl_path_comps, batfile) + if not os.path.exists(batfile_path): + debug("batch file not found: %s", batfile_path) + continue + + cl_path = os.path.join(msvc_instance.vc_dir, *cl_path_comps, _CL_EXE_NAME) + if not os.path.exists(cl_path): + debug("%s not found: %s", _CL_EXE_NAME, cl_path) + continue - if os.path.exists(cl_path): - debug('found %s', _CL_EXE_NAME) - return True + debug('%s found: %s', _CL_EXE_NAME, cl_path) + return True - elif 80 > vernum_int >= 60: + elif 2005 > msvc_instance.vs_product_numeric >= 1998: # 7.1 (VS2003) to 6.0 (VS6) - # quick check for vc_dir/bin and vc_dir/ before walk - # need to check root as the walk only considers subdirectories - for cl_dir in ('bin', ''): - cl_path = os.path.join(vc_dir, cl_dir, _CL_EXE_NAME) - if os.path.exists(cl_path): - debug('%s found %s', _CL_EXE_NAME, cl_path) - return True - # not in bin or root: must be in a subdirectory - for cl_root, cl_dirs, _ in os.walk(vc_dir): - for cl_dir in cl_dirs: - cl_path = os.path.join(cl_root, cl_dir, _CL_EXE_NAME) - if os.path.exists(cl_path): - debug('%s found %s', _CL_EXE_NAME, cl_path) - return True - return False + host_target_batcharg_batchfile_clpathcomps = _LE2003_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS + + for host_platform, target_platform in host_target_list: + + debug( + 'host_platform=%s, target_platform=%s, msvc_version=%s', + host_platform, target_platform, msvc_instance.msvc_version + ) + + batcharg_batchfile_clpathcomps = host_target_batcharg_batchfile_clpathcomps.get( + (host_platform, target_platform), None + ) + + if batcharg_batchfile_clpathcomps is None: + debug('unsupported host/target platform combo: (%s,%s)', host_platform, target_platform) + continue + + _, batfile, cl_path_comps = batcharg_batchfile_clpathcomps + + batfile_path = os.path.join(msvc_instance.vc_dir, *cl_path_comps, batfile) + if not os.path.exists(batfile_path): + debug("batch file not found: %s", batfile_path) + continue + + cl_path = os.path.join(msvc_instance.vc_dir, *cl_path_comps, _CL_EXE_NAME) + if not os.path.exists(cl_path): + debug("%s not found: %s", _CL_EXE_NAME, cl_path) + continue + + debug('%s found: %s', _CL_EXE_NAME, cl_path) + return True else: # version not supported return false - debug('unsupported MSVC version: %s', str(vernum)) + debug('unsupported MSVC version: %s', str(msvc_instance.msvc_version)) return False -def get_installed_vcs(env=None): - global __INSTALLED_VCS_RUN +# TODO(JCB): register callback (temporary until toolset detection is added) +MSVC.VSDetect.register_msvc_instance_check_files_exist(_msvc_instance_check_files_exist) - if __INSTALLED_VCS_RUN is not None: - return __INSTALLED_VCS_RUN +def get_installed_msvc_instances(env=None): + global _cache_installed_msvc_instances - save_target_arch = env.get('TARGET_ARCH', UNDEFINED) if env else None - force_target = env and save_target_arch and save_target_arch != UNDEFINED + # the installed instance cache is cleared if new instances are discovered + msvs_manager = MSVC.VSDetect.msvs_detect_env(env) - if force_target: - del env['TARGET_ARCH'] - debug("delete env['TARGET_ARCH']") + if _cache_installed_msvc_instances is not None: + return _cache_installed_msvc_instances - installed_versions = [] + installed_msvc_instances = msvs_manager.get_installed_msvc_instances() - for ver in _VCVER: - debug('trying to find VC %s', ver) - try: - VC_DIR = find_vc_pdir(env, ver) - if VC_DIR: - debug('found VC %s', ver) - if _check_cl_exists_in_vc_dir(env, VC_DIR, ver): - installed_versions.append(ver) - else: - debug('no compiler found %s', ver) - else: - debug('return None for ver %s', ver) - except (MSVCUnsupportedTargetArch, MSVCUnsupportedHostArch): - # Allow this exception to propagate further as it should cause - # SCons to exit with an error code - raise - except VisualCException as e: - debug('did not find VC %s: caught exception %s', ver, str(e)) - - if force_target: - env['TARGET_ARCH'] = save_target_arch - debug("restore env['TARGET_ARCH']=%s", save_target_arch) - - __INSTALLED_VCS_RUN = installed_versions - debug("__INSTALLED_VCS_RUN=%s", __INSTALLED_VCS_RUN) - return __INSTALLED_VCS_RUN + _cache_installed_msvc_instances = installed_msvc_instances + # debug("installed_msvc_instances=%s", _cache_installed_msvc_versions) + return _cache_installed_msvc_instances + +def get_installed_vcs(env=None): + global _cache_installed_msvc_versions + + # the installed version cache is cleared if new instances are discovered + msvs_manager = MSVC.VSDetect.msvs_detect_env(env) + + if _cache_installed_msvc_versions is not None: + return _cache_installed_msvc_versions + + installed_msvc_versions = msvs_manager.get_installed_msvc_versions() + + _cache_installed_msvc_versions = installed_msvc_versions + debug("installed_msvc_versions=%s", _cache_installed_msvc_versions) + return _cache_installed_msvc_versions def reset_installed_vcs() -> None: """Make it try again to find VC. This is just for the tests.""" - global __INSTALLED_VCS_RUN - __INSTALLED_VCS_RUN = None + _reset_installed_vcs() MSVC._reset() def msvc_default_version(env=None): @@ -1332,27 +1277,24 @@ def msvc_find_valid_batch_script(env, version): get it right. """ - # Find the product directory - pdir = None - try: - pdir = find_vc_pdir(env, version) - except UnsupportedVersion: - # Unsupported msvc version (raise MSVCArgumentError?) - pass - debug('product directory: version=%s, pdir=%s', version, pdir) + msvc_instance, msvc_query_key = _find_msvc_instance(version, env) - # Find the host, target, and all candidate (host, target) platform combinations: - platforms = get_host_target(env, version) - debug("host_platform %s, target_platform %s host_target_list %s", *platforms) - host_platform, target_platform, host_target_list = platforms + debug( + 'version=%s, query_key=%s, vc_dir=%s', + version, + repr(msvc_query_key.serialize()) if msvc_query_key else None, + msvc_instance.vc_dir if msvc_instance else None + ) d = None version_installed = False - if pdir: + if msvc_instance: - # Query all candidate sdk (host, target, sdk_pdir) after vc_script pass if necessary - sdk_queries = [] + # Find the host, target, and all candidate (host, target) platform combinations: + platforms = get_host_target(env, msvc_instance.msvc_version) + debug("host_platform %s, target_platform %s host_target_list %s", *platforms) + host_platform, target_platform, host_target_list = platforms for host_arch, target_arch, in host_target_list: # Set to current arch. @@ -1361,7 +1303,7 @@ def msvc_find_valid_batch_script(env, version): # Try to locate a batch file for this host/target platform combo try: - (vc_script, arg, vc_dir, sdk_pdir) = find_batch_file(version, host_arch, target_arch, pdir) + vc_script, arg = find_batch_file(msvc_instance, host_arch, target_arch) debug('vc_script:%s vc_script_arg:%s', vc_script, arg) version_installed = True except VisualCException as e: @@ -1370,18 +1312,18 @@ def msvc_find_valid_batch_script(env, version): version_installed = False continue - # Save (host, target, sdk_pdir) platform combo for sdk queries - if sdk_pdir: - sdk_query = (host_arch, target_arch, sdk_pdir) - if sdk_query not in sdk_queries: - debug('save sdk_query host=%s, target=%s, sdk_pdir=%s', host_arch, target_arch, sdk_pdir) - sdk_queries.append(sdk_query) - if not vc_script: continue + if not target_platform and MSVC.ScriptArguments.msvc_script_arguments_has_uwp(env): + # no target arch specified and is a store/uwp build + if msvc_instance.skip_uwp_target(env): + # store/uwp may not be supported for all express targets (prevent constraint error) + debug('skip uwp target arch: version=%s, target=%s', repr(version), repr(target_arch)) + continue + # Try to use the located batch file for this host/target platform combo - arg = MSVC.ScriptArguments.msvc_script_arguments(env, version, vc_dir, arg) + arg = MSVC.ScriptArguments.msvc_script_arguments(env, msvc_instance, arg) debug('trying vc_script:%s, vc_script_args:%s', repr(vc_script), arg) try: d = script_env(env, vc_script, args=arg) @@ -1398,36 +1340,12 @@ def msvc_find_valid_batch_script(env, version): debug("Found a working script/target: %s/%s", repr(vc_script), arg) break # We've found a working target_platform, so stop looking + # If we cannot find a viable installed compiler, reset the TARGET_ARCH + # To it's initial value if not d: - for host_arch, target_arch, sdk_pdir in sdk_queries: - # Set to current arch. - env['TARGET_ARCH'] = target_arch - - sdk_script = find_batch_file_sdk(host_arch, target_arch, sdk_pdir) - if not sdk_script: - continue - - # Try to use the sdk batch file for this (host, target, sdk_pdir) combo - debug('trying sdk_script:%s', repr(sdk_script)) - try: - d = script_env(env, sdk_script) - version_installed = True - except BatchFileExecutionError as e: - debug('failed sdk_script:%s, error=%s', repr(sdk_script), e) - continue - - have_cl, _ = _check_cl_exists_in_script_env(d) - if not have_cl: - debug('skip cl.exe not found sdk_script:%s', repr(sdk_script)) - continue - - debug("Found a working script/target: %s", repr(sdk_script)) - break # We've found a working script, so stop looking + env['TARGET_ARCH'] = target_platform - # If we cannot find a viable installed compiler, reset the TARGET_ARCH - # To it's initial value if not d: - env['TARGET_ARCH'] = target_platform if version_installed: msg = "MSVC version '{}' working host/target script was not found.\n" \ @@ -1450,38 +1368,78 @@ def msvc_find_valid_batch_script(env, version): return d -def get_use_script_use_settings(env): +class _MSVCAction: - # use_script use_settings return values action - # value ignored (value, None) use script or bypass detection - # undefined value not None (False, value) use dictionary - # undefined undefined/None (True, None) msvc detection + MSVCAction = namedtuple("MSVCAction", [ + 'label', + ]) + + SCRIPT = MSVCAction(label='script') + SELECT = MSVCAction(label='select') + SETTINGS = MSVCAction(label='settings') + BYPASS = MSVCAction(label='bypass') + +def _msvc_action(env): + + # use_script use_settings action description + # ------------- ------------ -------- ---------------- + # defined/None not None SETTINGS use dictionary + # defined/None None BYPASS bypass detection + # defined/string ignored SCRIPT use script + # defined/false ignored BYPASS bypass detection + # defined/true ignored SELECT msvc selection + # undefined not None SETTINGS use dictionary + # undefined None SELECT msvc selection + + msvc_action = None - # None (documentation) or evaluates False (code): bypass detection - # need to distinguish between undefined and None use_script = env.get('MSVC_USE_SCRIPT', UNDEFINED) + use_settings = env.get('MSVC_USE_SETTINGS', None) if use_script != UNDEFINED: - # use_script defined, use_settings ignored (not type checked) - return use_script, None + # use script defined + if use_script is None: + # use script is None + if use_settings is not None: + # use script is None, use_settings is not None + msvc_action = _MSVCAction.SETTINGS + else: + # use script is None, use_settings is None + msvc_action = _MSVCAction.BYPASS + else: + # use script is not None + if SCons.Util.is_String(use_script): + # use_script is string, use_settings ignored + msvc_action = _MSVCAction.SCRIPT + elif not use_script: + # use_script eval false, use_settings ignored + msvc_action = _MSVCAction.BYPASS + else: + # use script eval true, use_settings ignored + msvc_action = _MSVCAction.SELECT + elif use_settings is not None: + # use script undefined, use_settings is defined and not None + msvc_action = _MSVCAction.SETTINGS + else: + # use script undefined, use_settings undefined or None + msvc_action = _MSVCAction.SELECT - # undefined or None: use_settings ignored - use_settings = env.get('MSVC_USE_SETTINGS', None) + if not msvc_action: + errmsg = 'msvc_action is undefined' + debug('MSVCInternalError: %s', errmsg) + raise MSVCInternalError(errmsg) - if use_settings is not None: - # use script undefined, use_settings defined and not None (type checked) - return False, use_settings + debug('msvc_action=%s', repr(msvc_action.label)) - # use script undefined, use_settings undefined or None - return True, None + return msvc_action, use_script, use_settings -def msvc_setup_env(env): +def msvc_setup_env(env) -> None: debug('called') version = get_default_version(env) if version is None: if not msvc_setup_env_user(env): MSVC.SetupEnvDefault.set_nodefault() - return None + return # XXX: we set-up both MSVS version for backward # compatibility with the msvs tool @@ -1489,47 +1447,66 @@ def msvc_setup_env(env): env['MSVS_VERSION'] = version env['MSVS'] = {} - use_script, use_settings = get_use_script_use_settings(env) - if SCons.Util.is_String(use_script): + msvc_action, use_script, use_settings = _msvc_action(env) + if msvc_action == _MSVCAction.SCRIPT: use_script = use_script.strip() if not os.path.exists(use_script): - raise MSVCScriptNotFound(f'Script specified by MSVC_USE_SCRIPT not found: "{use_script}"') + error_msg = f'Script specified by MSVC_USE_SCRIPT not found: {use_script!r}' + debug(error_msg) + raise MSVCScriptNotFound(error_msg) args = env.subst('$MSVC_USE_SCRIPT_ARGS') - debug('use_script 1 %s %s', repr(use_script), repr(args)) + debug('msvc_action=%s, script=%s, args=%s', repr(msvc_action.label), repr(use_script), repr(args)) d = script_env(env, use_script, args) - elif use_script: - d = msvc_find_valid_batch_script(env,version) - debug('use_script 2 %s', d) + elif msvc_action == _MSVCAction.SELECT: + d = msvc_find_valid_batch_script(env, version) + debug('msvc_action=%s, d=%s', repr(msvc_action.label), d) if not d: - return d - elif use_settings is not None: + return + elif msvc_action == _MSVCAction.SETTINGS: if not SCons.Util.is_Dict(use_settings): error_msg = f'MSVC_USE_SETTINGS type error: expected a dictionary, found {type(use_settings).__name__}' + debug(error_msg) raise MSVCUseSettingsError(error_msg) d = use_settings - debug('use_settings %s', d) - else: - debug('MSVC_USE_SCRIPT set to False') - warn_msg = "MSVC_USE_SCRIPT set to False, assuming environment " \ - "set correctly." + debug('msvc_action=%s, d=%s', repr(msvc_action.label), d) + elif msvc_action == _MSVCAction.BYPASS: + debug('msvc_action=%s', repr(msvc_action.label)) + warn_msg = "MSVC detection bypassed, assuming environment set correctly." SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg) - return None + return + else: + label = msvc_action.label if msvc_action else None + errmsg = f'unhandled msvc_action ({label!r})' + debug('MSVCInternalError: %s', errmsg) + raise MSVCInternalError(errmsg) + + found_cl_path = None + found_cl_envpath = None + seen_path = False for k, v in d.items(): + if not seen_path and k == 'PATH': + seen_path = True + found_cl_path = SCons.Util.WhereIs('cl', v) + found_cl_envpath = SCons.Util.WhereIs('cl', env['ENV'].get(k, [])) env.PrependENVPath(k, v, delete_existing=True) debug("env['ENV']['%s'] = %s", k, env['ENV'][k]) - # final check to issue a warning if the compiler is not present - if not find_program_path(env, 'cl'): - debug("did not find %s", _CL_EXE_NAME) + debug("cl paths: d['PATH']=%s, ENV['PATH']=%s", repr(found_cl_path), repr(found_cl_envpath)) + + # final check to issue a warning if the requested compiler is not present + if not found_cl_path: + warn_msg = "Could not find requested MSVC compiler 'cl'." if CONFIG_CACHE: - propose = f"SCONS_CACHE_MSVC_CONFIG caching enabled, remove cache file {CONFIG_CACHE} if out of date." + warn_msg += f" SCONS_CACHE_MSVC_CONFIG caching enabled, remove cache file {CONFIG_CACHE} if out of date." else: - propose = "It may need to be installed separately with Visual Studio." - warn_msg = f"Could not find MSVC compiler 'cl'. {propose}" + warn_msg += " It may need to be installed separately with Visual Studio." + if found_cl_envpath: + warn_msg += " A 'cl' was found on the scons ENV path which may be erroneous." + debug(warn_msg) SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg) -def msvc_exists(env=None, version=None): +def msvc_exists(env=None, version=None) -> bool: vcs = get_installed_vcs(env) if version is None: rval = len(vcs) > 0 @@ -1538,7 +1515,7 @@ def msvc_exists(env=None, version=None): debug('version=%s, return=%s', repr(version), rval) return rval -def msvc_setup_env_user(env=None): +def msvc_setup_env_user(env=None) -> bool: rval = False if env: @@ -1572,7 +1549,7 @@ def msvc_setup_env_user(env=None): debug('return=%s', rval) return rval -def msvc_setup_env_tool(env=None, version=None, tool=None): +def msvc_setup_env_tool(env=None, version=None, tool=None) -> bool: MSVC.SetupEnvDefault.register_tool(env, tool, msvc_exists) rval = False if not rval and msvc_exists(env, version): @@ -1604,7 +1581,6 @@ def msvc_sdk_versions(version=None, msvc_uwp_app: bool=False): def msvc_toolset_versions(msvc_version=None, full: bool=True, sxs: bool=False): debug('msvc_version=%s, full=%s, sxs=%s', repr(msvc_version), repr(full), repr(sxs)) - env = None rval = [] if not msvc_version: @@ -1618,18 +1594,17 @@ def msvc_toolset_versions(msvc_version=None, full: bool=True, sxs: bool=False): msg = f'Unsupported msvc version {msvc_version!r}' raise MSVCArgumentError(msg) - vc_dir = find_vc_pdir(env, msvc_version) - if not vc_dir: - debug('VC folder not found for version %s', repr(msvc_version)) + msvc_instance = find_msvc_instance(msvc_version) + if not msvc_instance: + debug('MSVC instance not found for version %s', repr(msvc_version)) return rval - rval = MSVC.ScriptArguments._msvc_toolset_versions_internal(msvc_version, vc_dir, full=full, sxs=sxs) + rval = MSVC.ScriptArguments._msvc_toolset_versions_internal(msvc_instance, full=full, sxs=sxs) return rval def msvc_toolset_versions_spectre(msvc_version=None): debug('msvc_version=%s', repr(msvc_version)) - env = None rval = [] if not msvc_version: @@ -1643,12 +1618,12 @@ def msvc_toolset_versions_spectre(msvc_version=None): msg = f'Unsupported msvc version {msvc_version!r}' raise MSVCArgumentError(msg) - vc_dir = find_vc_pdir(env, msvc_version) - if not vc_dir: - debug('VC folder not found for version %s', repr(msvc_version)) + msvc_instance = find_msvc_instance(msvc_version) + if not msvc_instance: + debug('MSVC instance not found for version %s', repr(msvc_version)) return rval - rval = MSVC.ScriptArguments._msvc_toolset_versions_spectre_internal(msvc_version, vc_dir) + rval = MSVC.ScriptArguments._msvc_toolset_versions_spectre_internal(msvc_instance) return rval def msvc_query_version_toolset(version=None, prefer_newest: bool=True): @@ -1698,7 +1673,6 @@ def msvc_query_version_toolset(version=None, prefer_newest: bool=True): """ debug('version=%s, prefer_newest=%s', repr(version), repr(prefer_newest)) - env = None msvc_version = None msvc_toolset_version = None @@ -1763,8 +1737,8 @@ def msvc_query_version_toolset(version=None, prefer_newest: bool=True): continue seen_msvc_version.add(query_msvc_version) - vc_dir = find_vc_pdir(env, query_msvc_version) - if not vc_dir: + query_msvc_instance = find_msvc_instance(query_msvc_version) + if not query_msvc_instance: continue if query_msvc_version.startswith('14.0'): @@ -1777,7 +1751,7 @@ def msvc_query_version_toolset(version=None, prefer_newest: bool=True): return query_msvc_version, msvc_toolset_version try: - toolset_vcvars = MSVC.ScriptArguments._msvc_toolset_internal(query_msvc_version, query_msvc_toolset_version, vc_dir) + toolset_vcvars = MSVC.ScriptArguments._msvc_toolset_internal(query_msvc_instance, query_msvc_toolset_version) if toolset_vcvars: msvc_toolset_version = toolset_vcvars debug( @@ -1804,7 +1778,6 @@ def msvc_query_version_toolset(version=None, prefer_newest: bool=True): msg = f'MSVC toolset version {version!r} not found' raise MSVCToolsetVersionNotFound(msg) - -# internal consistency check (should be last) +# internal consistency checks (should be last) MSVC._verify() diff --git a/SCons/Tool/MSCommon/vcTests.py b/SCons/Tool/MSCommon/vcTests.py index 9c2bd0ac8c..c0923278b2 100644 --- a/SCons/Tool/MSCommon/vcTests.py +++ b/SCons/Tool/MSCommon/vcTests.py @@ -29,6 +29,7 @@ import SCons.Node.FS import SCons.Warnings import SCons.Tool.MSCommon.vc +from SCons.Tool.MSCommon.MSVC import VSWhere from SCons.Tool import MSCommon import TestCmd @@ -44,6 +45,7 @@ MS_TOOLS_VERSION = '1.1.1' +native_host = SCons.Tool.MSCommon.vc.get_native_host_platform() class VswhereTestCase(unittest.TestCase): @staticmethod @@ -52,21 +54,61 @@ def _createVSWhere(path) -> None: with open(path, 'w') as f: f.write("Created:%s" % f) + _existing_path = None + + @classmethod + def _path_exists(cls, path): + return (path == cls._existing_path) + def testDefaults(self) -> None: """ Verify that msvc_find_vswhere() find's files in the specified paths """ - # import pdb; pdb.set_trace() - vswhere_dirs = [os.path.splitdrive(p)[1] for p in SCons.Tool.MSCommon.vc.VSWHERE_PATHS] + + restore_vswhere_execs_exist = list(VSWhere._VSWhere.vswhere_executables) + base_dir = test.workpath('fake_vswhere') - test_vswhere_dirs = [os.path.join(base_dir,d[1:]) for d in vswhere_dirs] + norm_dir = os.path.normcase(os.path.normpath(base_dir)) + # import pdb; pdb.set_trace() - SCons.Tool.MSCommon.vc.VSWHERE_PATHS = test_vswhere_dirs - for vsw in test_vswhere_dirs: - VswhereTestCase._createVSWhere(vsw) - find_path = SCons.Tool.MSCommon.vc.msvc_find_vswhere() - self.assertTrue(vsw == find_path, "Didn't find vswhere in %s found in %s" % (vsw, find_path)) - os.remove(vsw) + test_vswhere_dirs = [ + VSWhere._VSWhere.VSWhereExecutable( + path=os.path.join(base_dir,t[0][1:]), + norm=os.path.join(norm_dir,t[1][1:]), + ) + for t in [ + (os.path.splitdrive(vswexec.path)[1], os.path.splitdrive(vswexec.norm)[1]) + for vswexec in VSWhere._VSWhere.vswhere_executables + ] + ] + + for vswexec in test_vswhere_dirs: + VswhereTestCase._createVSWhere(vswexec.path) + VSWhere._VSWhere.vswhere_executables = [vswexec] + find_path = MSCommon.vc.msvc_find_vswhere() + self.assertTrue(vswexec.path == find_path, "Didn't find vswhere in %s found in %s" % (vswexec.path, find_path)) + os.remove(vswexec.path) + + test_vswhere_dirs = [ + os.path.join(base_dir,d[1:]) + for d in [ + os.path.splitdrive(p)[1] + for p in VSWhere.VSWHERE_PATHS + ] + ] + MSCommon.vc.path_exists = VswhereTestCase._path_exists + + for vswpath in test_vswhere_dirs: + VswhereTestCase._createVSWhere(vswpath) + VswhereTestCase._existing_path = vswpath + for front in (True, False): + VSWhere._VSWhere.vswhere_executables = [] + MSCommon.MSVC.VSWhere.vswhere_push_location(vswpath, front=front) + find_path = MSCommon.vc.msvc_find_vswhere() + self.assertTrue(vswpath == find_path, "Didn't find vswhere in %s found in %s" % (vswpath, find_path)) + os.remove(vswpath) + + VSWhere._VSWhere.vswhere_executables = restore_vswhere_execs_exist # def specifiedVswherePathTest(self): # "Verify that msvc.generate() respects VSWHERE Specified" @@ -75,7 +117,31 @@ def testDefaults(self) -> None: class MSVcTestCase(unittest.TestCase): @staticmethod - def _createDummyCl(path, add_bin: bool=True) -> None: + def _createDummyMSVSBase(vc_version, vs_component, vs_dir): + vs_product_def = SCons.Tool.MSCommon.MSVC.Config.MSVC_VERSION_INTERNAL[vc_version] + vs_component_key = (vs_product_def.vs_lookup, vs_component) + msvs_base = SCons.Tool.MSCommon.MSVC.VSDetect.MSVSBase.factory( + vs_product_def=vs_product_def, + vs_channel_def=SCons.Tool.MSCommon.MSVC.Config.MSVS_CHANNEL_RELEASE, + vs_component_def=SCons.Tool.MSCommon.MSVC.Config.MSVS_COMPONENT_INTERNAL[vs_component_key], + vs_sequence_nbr=1, + vs_dir=vs_dir, + vs_version=vs_product_def.vs_version, + ) + return msvs_base + + @classmethod + def _createDummyMSVCInstance(cls, vc_version, vs_component, vc_dir): + msvc_instance = SCons.Tool.MSCommon.MSVC.VSDetect.MSVCInstance.factory( + msvs_base=cls._createDummyMSVSBase(vc_version, vs_component, vc_dir), + vc_version_def=SCons.Tool.MSCommon.MSVC.Util.msvc_version_components(vc_version), + vc_feature_map=None, + vc_dir=vc_dir, + ) + return msvc_instance + + @staticmethod + def _createDummyFile(path, filename, add_bin: bool=True) -> None: """ Creates a dummy cl.exe in the correct directory. It will create all missing parent directories as well @@ -94,7 +160,7 @@ def _createDummyCl(path, add_bin: bool=True) -> None: if create_path and not os.path.isdir(create_path): os.makedirs(create_path) - create_this = os.path.join(create_path,'cl.exe') + create_this = os.path.join(create_path, filename) # print("Creating: %s"%create_this) with open(create_this,'w') as ct: @@ -105,18 +171,10 @@ def runTest(self) -> None: """ Check that all proper HOST_PLATFORM and TARGET_PLATFORM are handled. Verify that improper HOST_PLATFORM and/or TARGET_PLATFORM are properly handled. - by SCons.Tool.MSCommon.vc._check_cl_exists_in_vc_dir() + by SCons.Tool.MSCommon.vc._msvc_instance_check_files_exist() """ - check = SCons.Tool.MSCommon.vc._check_cl_exists_in_vc_dir - - env={'TARGET_ARCH':'x86'} - _, clpathcomps = SCons.Tool.MSCommon.vc._LE2015_HOST_TARGET_BATCHARG_CLPATHCOMPS[('x86','x86')] - path = os.path.join('.', *clpathcomps) - MSVcTestCase._createDummyCl(path, add_bin=False) - - # print("retval:%s"%check(env, '.', '8.0')) - + check = SCons.Tool.MSCommon.vc._msvc_instance_check_files_exist # Setup for 14.1 (VS2017) and later tests @@ -131,130 +189,81 @@ def runTest(self) -> None: except IOError as e: print("Failed trying to write :%s :%s" % (tools_version_file, e)) - # Test 14.3 (VS2022) and later - vc_ge2022_list = SCons.Tool.MSCommon.vc._GE2022_HOST_TARGET_CFG.all_pairs - - for host, target in vc_ge2022_list: - batfile, clpathcomps = SCons.Tool.MSCommon.vc._GE2022_HOST_TARGET_BATCHFILE_CLPATHCOMPS[(host,target)] - # print("GE 14.3 Got: (%s, %s) -> (%s, %s)"%(host,target,batfile,clpathcomps)) - - env={'TARGET_ARCH':target, 'HOST_ARCH':host} - path = os.path.join('.', 'Tools', 'MSVC', MS_TOOLS_VERSION, *clpathcomps) - MSVcTestCase._createDummyCl(path, add_bin=False) - result=check(env, '.', '14.3') - # print("for:(%s, %s) got :%s"%(host, target, result)) - self.assertTrue(result, "Checking host: %s target: %s" % (host, target)) - - # Now test bogus value for HOST_ARCH - env={'TARGET_ARCH':'x86', 'HOST_ARCH':'GARBAGE'} - try: - result=check(env, '.', '14.3') - # print("for:%s got :%s"%(env, result)) - self.assertFalse(result, "Did not fail with bogus HOST_ARCH host: %s target: %s" % (env['HOST_ARCH'], env['TARGET_ARCH'])) - except MSVCUnsupportedHostArch: - pass - else: - self.fail('Did not fail when HOST_ARCH specified as: %s' % env['HOST_ARCH']) - - # Now test bogus value for TARGET_ARCH - env={'TARGET_ARCH':'GARBAGE', 'HOST_ARCH':'x86'} - try: - result=check(env, '.', '14.3') - # print("for:%s got :%s"%(env, result)) - self.assertFalse(result, "Did not fail with bogus TARGET_ARCH host: %s target: %s" % (env['HOST_ARCH'], env['TARGET_ARCH'])) - except MSVCUnsupportedTargetArch: - pass - else: - self.fail('Did not fail when TARGET_ARCH specified as: %s' % env['TARGET_ARCH']) + msvc_instance_VS2022 = MSVcTestCase._createDummyMSVCInstance('14.3', 'Community', '.') + for host in SCons.Tool.MSCommon.vc._GE2022_HOST_TARGET_CFG.host_all_hosts[native_host]: + for target in SCons.Tool.MSCommon.vc._GE2022_HOST_TARGET_CFG.host_all_targets[host]: + _, batfile, clpathcomps = SCons.Tool.MSCommon.vc._GE2022_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS[(host,target)] + # print("GE 14.3 Got: (%s, %s) -> (%s, %s)"%(host,target,batfile,clpathcomps)) + path = os.path.join('.', "Auxiliary", "Build", batfile) + MSVcTestCase._createDummyFile(path, batfile, add_bin=False) + path = os.path.join('.', 'Tools', 'MSVC', MS_TOOLS_VERSION, *clpathcomps) + MSVcTestCase._createDummyFile(path, 'cl.exe', add_bin=False) + result=check(msvc_instance_VS2022) + # print("for:(%s, %s) got :%s"%(host, target, result)) + self.assertTrue(result, "Checking host: %s target: %s" % (host, target)) # Test 14.2 (VS2019) to 14.1 (VS2017) versions - vc_le2019_list = SCons.Tool.MSCommon.vc._LE2019_HOST_TARGET_CFG.all_pairs - - for host, target in vc_le2019_list: - batfile, clpathcomps = SCons.Tool.MSCommon.vc._LE2019_HOST_TARGET_BATCHFILE_CLPATHCOMPS[(host,target)] - # print("LE 14.2 Got: (%s, %s) -> (%s, %s)"%(host,target,batfile,clpathcomps)) - - env={'TARGET_ARCH':target, 'HOST_ARCH':host} - path = os.path.join('.', 'Tools', 'MSVC', MS_TOOLS_VERSION, *clpathcomps) - MSVcTestCase._createDummyCl(path, add_bin=False) - result=check(env, '.', '14.1') - # print("for:(%s, %s) got :%s"%(host, target, result)) - self.assertTrue(result, "Checking host: %s target: %s" % (host, target)) - - # Now test bogus value for HOST_ARCH - env={'TARGET_ARCH':'x86', 'HOST_ARCH':'GARBAGE'} - try: - result=check(env, '.', '14.1') - # print("for:%s got :%s"%(env, result)) - self.assertFalse(result, "Did not fail with bogus HOST_ARCH host: %s target: %s" % (env['HOST_ARCH'], env['TARGET_ARCH'])) - except MSVCUnsupportedHostArch: - pass - else: - self.fail('Did not fail when HOST_ARCH specified as: %s' % env['HOST_ARCH']) - - # Now test bogus value for TARGET_ARCH - env={'TARGET_ARCH':'GARBAGE', 'HOST_ARCH':'x86'} - try: - result=check(env, '.', '14.1') - # print("for:%s got :%s"%(env, result)) - self.assertFalse(result, "Did not fail with bogus TARGET_ARCH host: %s target: %s" % (env['HOST_ARCH'], env['TARGET_ARCH'])) - except MSVCUnsupportedTargetArch: - pass - else: - self.fail('Did not fail when TARGET_ARCH specified as: %s' % env['TARGET_ARCH']) - - # Test 14.0 (VS2015) to 8.0 (VS2005) versions - vc_le2015_list = SCons.Tool.MSCommon.vc._LE2015_HOST_TARGET_CFG.all_pairs - - for host, target in vc_le2015_list: - batarg, clpathcomps = SCons.Tool.MSCommon.vc._LE2015_HOST_TARGET_BATCHARG_CLPATHCOMPS[(host, target)] - # print("LE 14.0 Got: (%s, %s) -> (%s, %s)"%(host,target,batarg,clpathcomps)) - env={'TARGET_ARCH':target, 'HOST_ARCH':host} - path = os.path.join('.', *clpathcomps) - MSVcTestCase._createDummyCl(path, add_bin=False) - result=check(env, '.', '9.0') - # print("for:(%s, %s) got :%s"%(host, target, result)) - self.assertTrue(result, "Checking host: %s target: %s" % (host, target)) - - # Now test bogus value for HOST_ARCH - env={'TARGET_ARCH':'x86', 'HOST_ARCH':'GARBAGE'} - try: - result=check(env, '.', '9.0') - # print("for:%s got :%s"%(env, result)) - self.assertFalse(result, "Did not fail with bogus HOST_ARCH host: %s target: %s" % (env['HOST_ARCH'], env['TARGET_ARCH'])) - except MSVCUnsupportedHostArch: - pass - else: - self.fail('Did not fail when HOST_ARCH specified as: %s' % env['HOST_ARCH']) - - # Now test bogus value for TARGET_ARCH - env={'TARGET_ARCH':'GARBAGE', 'HOST_ARCH':'x86'} - try: - result=check(env, '.', '9.0') - # print("for:%s got :%s"%(env, result)) - self.assertFalse(result, "Did not fail with bogus TARGET_ARCH host: %s target: %s" % (env['HOST_ARCH'], env['TARGET_ARCH'])) - except MSVCUnsupportedTargetArch: - pass - else: - self.fail('Did not fail when TARGET_ARCH specified as: %s' % env['TARGET_ARCH']) + msvc_instance_VS2017 = MSVcTestCase._createDummyMSVCInstance('14.1', 'Community', '.') + for host in SCons.Tool.MSCommon.vc._LE2019_HOST_TARGET_CFG.host_all_hosts[native_host]: + for target in SCons.Tool.MSCommon.vc._LE2019_HOST_TARGET_CFG.host_all_targets[host]: + _, batfile, clpathcomps = SCons.Tool.MSCommon.vc._LE2019_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS[(host,target)] + # print("LE 14.2 Got: (%s, %s) -> (%s, %s)"%(host,target,batfile,clpathcomps)) + path = os.path.join('.', 'Tools', 'MSVC', MS_TOOLS_VERSION, *clpathcomps) + MSVcTestCase._createDummyFile(path, 'cl.exe', add_bin=False) + result=check(msvc_instance_VS2017) + # print("for:(%s, %s) got :%s"%(host, target, result)) + self.assertTrue(result, "Checking host: %s target: %s" % (host, target)) + + # Test 14.0 (VS2015) to 10.0 (VS2010) versions + msvc_instance_VS2010 = MSVcTestCase._createDummyMSVCInstance('10.0', 'Develop', '.') + for host in SCons.Tool.MSCommon.vc._LE2015_HOST_TARGET_CFG.host_all_hosts[native_host]: + for target in SCons.Tool.MSCommon.vc._LE2015_HOST_TARGET_CFG.host_all_targets[host]: + batarg, batfile, clpathcomps = SCons.Tool.MSCommon.vc._LE2015_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS[(host, target)] + # print("LE 14.0 Got: (%s, %s) -> (%s, %s, %s)"%(host,target,batarg,batfile,clpathcomps)) + MSVcTestCase._createDummyFile('.', 'vcvarsall.bat', add_bin=False) + path = os.path.join('.', *clpathcomps) + MSVcTestCase._createDummyFile(path, batfile, add_bin=False) + MSVcTestCase._createDummyFile(path, 'cl.exe', add_bin=False) + result=check(msvc_instance_VS2010) + # print("for:(%s, %s) got :%s"%(host, target, result)) + self.assertTrue(result, "Checking host: %s target: %s" % (host, target)) + + # Test 9.0 (VC2008) to 8.0 (VS2005) + msvc_instance_VS2005 = MSVcTestCase._createDummyMSVCInstance('8.0', 'Develop', '.') + for host in SCons.Tool.MSCommon.vc._LE2008_HOST_TARGET_CFG.host_all_hosts[native_host]: + for target in SCons.Tool.MSCommon.vc._LE2008_HOST_TARGET_CFG.host_all_targets[host]: + batarg, batfile, clpathcomps = SCons.Tool.MSCommon.vc._LE2008_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS[(host, target)] + # print("LE 9.0 Got: (%s, %s) -> (%s, %s, %s)"%(host,target,batarg,batfile,clpathcomps)) + MSVcTestCase._createDummyFile('.', 'vcvarsall.bat', add_bin=False) + path = os.path.join('.', *clpathcomps) + MSVcTestCase._createDummyFile(path, batfile, add_bin=False) + MSVcTestCase._createDummyFile(path, 'cl.exe', add_bin=False) + # check will fail if '9.0' and VCForPython (layout different) + result=check(msvc_instance_VS2005) + # print("for:(%s, %s) got :%s"%(host, target, result)) + self.assertTrue(result, "Checking host: %s target: %s" % (host, target)) # Test 7.1 (VS2003) and earlier - vc_le2003_list = SCons.Tool.MSCommon.vc._LE2003_HOST_TARGET_CFG.all_pairs - - for host, target in vc_le2003_list: - # print("LE 7.1 Got: (%s, %s)"%(host,target)) - env={'TARGET_ARCH':target, 'HOST_ARCH':host} - path = os.path.join('.') - MSVcTestCase._createDummyCl(path) - result=check(env, '.', '6.0') - # print("for:(%s, %s) got :%s"%(host, target, result)) - self.assertTrue(result, "Checking host: %s target: %s" % (host, target)) + msvc_instance_VS6 = MSVcTestCase._createDummyMSVCInstance('6.0', 'Develop', '.') + for host in SCons.Tool.MSCommon.vc._LE2003_HOST_TARGET_CFG.host_all_hosts[native_host]: + for target in SCons.Tool.MSCommon.vc._LE2003_HOST_TARGET_CFG.host_all_targets[host]: + batarg, batfile, clpathcomps = SCons.Tool.MSCommon.vc._LE2003_HOST_TARGET_BATCHARG_BATCHFILE_CLPATHCOMPS[(host, target)] + # print("LE 7.1 Got: (%s, %s)"%(host,target)) + path = os.path.join('.', *clpathcomps) + MSVcTestCase._createDummyFile(path, batfile, add_bin=False) + MSVcTestCase._createDummyFile(path, 'cl.exe', add_bin=False) + result=check(msvc_instance_VS6) + # print("for:(%s, %s) got :%s"%(host, target, result)) + self.assertTrue(result, "Checking host: %s target: %s" % (host, target)) + + check = SCons.Tool.MSCommon.vc.get_host_target # Now test bogus value for HOST_ARCH env={'TARGET_ARCH':'x86', 'HOST_ARCH':'GARBAGE'} try: - result=check(env, '.', '6.0') + check(env, msvc_instance_VS2022.vc_version_def.msvc_version) + result = True # print("for:%s got :%s"%(env, result)) self.assertFalse(result, "Did not fail with bogus HOST_ARCH host: %s target: %s" % (env['HOST_ARCH'], env['TARGET_ARCH'])) except MSVCUnsupportedHostArch: @@ -265,7 +274,8 @@ def runTest(self) -> None: # Now test bogus value for TARGET_ARCH env={'TARGET_ARCH':'GARBAGE', 'HOST_ARCH':'x86'} try: - result=check(env, '.', '6.0') + check(env, msvc_instance_VS2022.vc_version_def.msvc_version) + result = True # print("for:%s got :%s"%(env, result)) self.assertFalse(result, "Did not fail with bogus TARGET_ARCH host: %s target: %s" % (env['HOST_ARCH'], env['TARGET_ARCH'])) except MSVCUnsupportedTargetArch: @@ -273,7 +283,6 @@ def runTest(self) -> None: else: self.fail('Did not fail when TARGET_ARCH specified as: %s' % env['TARGET_ARCH']) - class Data: HAVE_MSVC = True if MSCommon.vc.msvc_default_version() else False @@ -369,12 +378,13 @@ def test_valid_default_msvc(self) -> None: def test_valid_vcver(self) -> None: for symbol in MSCommon.vc._VCVER: version_def = MSCommon.msvc_version_components(symbol) - for msvc_uwp_app in (True, False): - sdk_list = MSCommon.vc.msvc_sdk_versions(version=symbol, msvc_uwp_app=msvc_uwp_app) - if Data.HAVE_MSVC and version_def.msvc_vernum >= 14.0: - self.assertTrue(sdk_list, "SDK list is empty for msvc version {}".format(repr(symbol))) - else: - self.assertFalse(sdk_list, "SDK list is not empty for msvc version {}".format(repr(symbol))) + if Data.HAVE_MSVC: + for msvc_uwp_app in (True, False): + sdk_list = MSCommon.vc.msvc_sdk_versions(version=symbol, msvc_uwp_app=msvc_uwp_app) + if version_def.msvc_vernum >= 14.0: + self.assertTrue(sdk_list, "SDK list is empty for msvc version {}".format(repr(symbol))) + else: + self.assertFalse(sdk_list, "SDK list is not empty for msvc version {}".format(repr(symbol))) def test_valid_vcver_toolsets(self) -> None: for symbol in MSCommon.vc._VCVER: diff --git a/SCons/Tool/MSCommon/vs.py b/SCons/Tool/MSCommon/vs.py index af0fd26e5a..695ad44923 100644 --- a/SCons/Tool/MSCommon/vs.py +++ b/SCons/Tool/MSCommon/vs.py @@ -33,6 +33,7 @@ from .common import ( debug, + debug_extra, get_output, is_win64, normalize_env, @@ -40,6 +41,7 @@ read_reg, ) +from . import MSVC class VisualStudio: """ @@ -47,97 +49,91 @@ class VisualStudio: Visual Studio. """ def __init__(self, version, **kw) -> None: + self.debug_extra = debug_extra(self.__class__) self.version = version - kw['vc_version'] = kw.get('vc_version', version) + #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 = SCons.Tool.MSCommon.vc.find_vc_pdir(env, self.vc_version) - 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 + self._msvs_instance = None + + def register_msvs_instance(self, msvs_instance): + self._msvs_instance = msvs_instance + + #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): + # msvc_instance = SCons.Tool.MSCommon.vc.find_msvc_instance(self.vc_version, env) + # if not msvc_instance: + # debug('no installed VC %s', self.vc_version) + # return None + # return os.path.abspath(os.path.join(msvc_instance.vc_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 + batch_file = self._msvs_instance.vs_script if self._msvs_instance else None + debug('batch_file=%s', repr(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 + executable = self._msvs_instance.vs_executable if self._msvs_instance else None + debug('executable=%s', repr(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 + vs_dir = self._msvs_instance.vs_dir if self._msvs_instance else None + debug('vs_dir=%s', repr(vs_dir)) + return vs_dir def get_supported_arch(self): try: @@ -194,219 +190,221 @@ def reset(self) -> None: # 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. +# If you update this list, update: +# MSVS_PRODUCT_DEFINITIONS in Tool/MSCommon/MSVC/Config.py, +# _VCVER in Tool/MSCommon/vc.py, and MSVC_VERSION documentation +# in Tool/msvc.xml. SupportedVSList = [ # Visual Studio 2022 VisualStudio('14.3', - vc_version='14.3', + #vc_version='14.3', sdk_version='10.0A', - hkeys=[], + #hkeys=[], common_tools_var='VS170COMNTOOLS', - executable_path=r'Common7\IDE\devenv.com', + #executable_path=r'Common7\IDE\devenv.com', # should be a fallback, prefer use vswhere installationPath - batch_file_path=r'Common7\Tools\VsDevCmd.bat', + #batch_file_path=r'Common7\Tools\VsDevCmd.bat', supported_arch=['x86', 'amd64', "arm", 'arm64'], ), # Visual Studio 2019 VisualStudio('14.2', - vc_version='14.2', + #vc_version='14.2', sdk_version='10.0A', - hkeys=[], + #hkeys=[], common_tools_var='VS160COMNTOOLS', - executable_path=r'Common7\IDE\devenv.com', + #executable_path=r'Common7\IDE\devenv.com', # should be a fallback, prefer use vswhere installationPath - batch_file_path=r'Common7\Tools\VsDevCmd.bat', + #batch_file_path=r'Common7\Tools\VsDevCmd.bat', supported_arch=['x86', 'amd64', "arm", 'arm64'], ), # Visual Studio 2017 VisualStudio('14.1', - vc_version='14.1', + #vc_version='14.1', sdk_version='10.0A', - hkeys=[], + #hkeys=[], common_tools_var='VS150COMNTOOLS', - executable_path=r'Common7\IDE\devenv.com', + #executable_path=r'Common7\IDE\devenv.com', # should be a fallback, prefer use vswhere installationPath - batch_file_path=r'Common7\Tools\VsDevCmd.bat', + #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', + #vc_version='14.1', sdk_version='10.0A', - hkeys=[], + #hkeys=[], common_tools_var='VS150COMNTOOLS', - executable_path=r'Common7\IDE\WDExpress.exe', + #executable_path=r'Common7\IDE\WDExpress.exe', # should be a fallback, prefer use vswhere installationPath - batch_file_path=r'Common7\Tools\VsDevCmd.bat', + #batch_file_path=r'Common7\Tools\VsDevCmd.bat', supported_arch=['x86', 'amd64', "arm", 'arm64'], ), # Visual Studio 2015 VisualStudio('14.0', - vc_version='14.0', + #vc_version='14.0', sdk_version='10.0', - hkeys=[r'Microsoft\VisualStudio\14.0\Setup\VS\ProductDir'], + #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', + #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', + #vc_version='14.0', sdk_version='10.0A', - hkeys=[r'Microsoft\VisualStudio\14.0\Setup\VS\ProductDir'], + #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', + #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', + #vc_version='12.0', sdk_version='8.1A', - hkeys=[r'Microsoft\VisualStudio\12.0\Setup\VS\ProductDir'], + #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', + #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', + #vc_version='12.0', sdk_version='8.1A', - hkeys=[r'Microsoft\VisualStudio\12.0\Setup\VS\ProductDir'], + #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', + #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'], + #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', + #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', + #vc_version='11.0', sdk_version='8.0A', - hkeys=[r'Microsoft\VisualStudio\11.0\Setup\VS\ProductDir'], + #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', + #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'], + #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', + #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', + #vc_version='10.0', sdk_version='7.0A', - hkeys=[r'Microsoft\VCExpress\10.0\Setup\VS\ProductDir'], + #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', + #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'], + #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', + #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', + #vc_version='9.0', sdk_version='6.0A', - hkeys=[r'Microsoft\VCExpress\9.0\Setup\VS\ProductDir'], + #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', + #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'], + #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', + #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', + #vc_version='8.0Exp', sdk_version='6.0A', - hkeys=[r'Microsoft\VCExpress\8.0\Setup\VS\ProductDir'], + #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', + #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'], + #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', + #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'], + #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', + #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'], + #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', + #executable_path=r'Common\MSDev98\Bin\MSDEV.COM', + #batch_file_path=r'Common7\Tools\vsvars32.bat', + #default_dirname='Microsoft Visual Studio', supported_arch=['x86'], ), ] @@ -428,18 +426,33 @@ def reset(self) -> None: def get_installed_visual_studios(env=None): global InstalledVSList global InstalledVSMap + # the cache is cleared if new instances are discovered + msvs_manager = MSVC.VSDetect.msvs_detect_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) - InstalledVSMap[vs.version] = vs + msvs_instances, _ = msvs_manager.query_msvs_instances(msvc_version=vs.version) + if not msvs_instances: + continue + msvs_instance = msvs_instances[0] + if not msvs_instance.vs_executable: + continue + msvc_instance = msvs_instance.msvc_instance + if not msvc_instance: + continue + vs.register_msvs_instance(msvs_instance) + debug('found VS %s (msvs_instance=%s)', repr(vs.version), repr(msvs_instance.id_str)) + InstalledVSList.append(vs) + InstalledVSMap[vs.version] = vs + #if vs.get_executable(env): + # debug('found VS %s', vs.version) + # InstalledVSList.append(vs) + # InstalledVSMap[vs.version] = vs return InstalledVSList -def reset_installed_visual_studios() -> None: +def _reset_installed_visual_studios() -> None: global InstalledVSList global InstalledVSMap InstalledVSList = None @@ -447,6 +460,11 @@ def reset_installed_visual_studios() -> None: for vs in SupportedVSList: vs.reset() +# register visual studios cache reset function with vs detection +MSVC.VSDetect.register_reset_func(_reset_installed_visual_studios) + +def reset_installed_visual_studios() -> None: + _reset_installed_visual_studios() # Need to clear installed VC's as well as they are used in finding # installed VS's SCons.Tool.MSCommon.vc.reset_installed_vcs() diff --git a/SCons/Tool/msvc.xml b/SCons/Tool/msvc.xml index bf2e267347..8beebf9a2d 100644 --- a/SCons/Tool/msvc.xml +++ b/SCons/Tool/msvc.xml @@ -370,22 +370,10 @@ loaded into the environment. -The valid values for &cv-MSVC_VERSION; represent major versions -of the compiler, except that versions ending in Exp -refer to "Express" or "Express for Desktop" Visual Studio editions, -which require distict entries because they use a different -filesystem layout and have some feature limitations compared to -the full version. -The following table shows correspondence -of the selector string to various version indicators -('x' is used as a placeholder for -a single digit that can vary). -Note that it is not necessary to install Visual Studio -to build with &SCons; (for example, you can install only -Build Tools), but if Visual Studio is installed, -additional builders such as &b-link-MSVSSolution; and -&b-link-MSVSProject; become avaialable and will -correspond to the indicated versions. +The valid values for &cv-MSVC_VERSION; represent major versions of the +compiler suite. The following table shows the correspondence of +&cv-MSVC_VERSION; version specifications to various Visual Studio version +numbers. 'x' is used as a placeholder for a single digit that may vary. @@ -542,6 +530,31 @@ correspond to the indicated versions. + + + + + It is not necessary to install a Visual Studio IDE + to build with &SCons; (for example, you can install only + Build Tools), but when a Visual Studio IDE is installed, + additional builders such as &b-link-MSVSSolution; and + &b-link-MSVSProject; become available and correspond to + the specified versions. + + + + Versions ending in Exp refer to historical + "Express" or "Express for Desktop" Visual Studio editions, + which had feature limitations compared to the full editions. + It is only necessary to specify the Exp + suffix to select the express edition when both express and + non-express editions of the same product are installed + simulaneously. The Exp suffix is unnecessary, + but accepted, when only the express edition is installed. + + + + The compilation environment can be further or more precisely specified through the use of several other &consvars;: see the descriptions of diff --git a/SCons/Tool/msvsTests.py b/SCons/Tool/msvsTests.py index dd708d0a7e..b92ac62a88 100644 --- a/SCons/Tool/msvsTests.py +++ b/SCons/Tool/msvsTests.py @@ -359,6 +359,8 @@ "EnvironmentDirectory"="C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\Common7\\IDE\\" "VS7CommonDir"="C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\Common7\\" "VS7CommonBinDir"="" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\14.0\Setup\VC] +"ProductDir"="C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC" [HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\14.0\Setup\VS\BuildNumber] "1033"="14.0" [HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\14.0\Setup\VS\Community] @@ -576,12 +578,23 @@ def DummyQueryValue(key, value): # print "Query Value",key.name+"\\"+value,"=>",rv return rv -def DummyExists(path) -> bool: - return True +exists_maps = None -def DummyVsWhere(msvc_version, env): - # not testing versions with vswhere, so return none - return None +def DummyExists(path) -> bool: + global exists_maps + rval = True + if exists_maps: + p = os.path.normcase(os.path.normpath(path)) + for exists_map in exists_maps: + if p.startswith(exists_map['prefix']) and p not in exists_map['paths']: + rval = False + break + # print("DummyExists", repr(path), rval) + return rval + +def DummyVsWhereExecutables(vswhere_env=None): + # not testing versions with vswhere, so return empty list + return [] class msvsTestCase(unittest.TestCase): """This test case is run several times with different defaults. @@ -589,7 +602,9 @@ class msvsTestCase(unittest.TestCase): def setUp(self) -> None: debug("THIS TYPE :%s"%self) global registry + global exists_maps registry = self.registry + exists_maps = self.exists_maps from SCons.Tool.MSCommon.vs import reset_installed_visual_studios reset_installed_visual_studios() @@ -605,13 +620,13 @@ def test_get_default_version(self) -> None: env = DummyEnv() v1 = get_default_version(env) if v1: - assert env['MSVS_VERSION'] == self.default_version, \ - ("env['MSVS_VERSION'] != self.default_version", - env['MSVS_VERSION'],self.default_version) - assert env['MSVS']['VERSION'] == self.default_version, \ - ("env['MSVS']['VERSION'] != self.default_version", - env['MSVS']['VERSION'], self.default_version) - assert v1 == self.default_version, (self.default_version, v1) + assert env['MSVS_VERSION'] in self.default_versions, \ + ("env['MSVS_VERSION'] not in self.default_versions", + env['MSVS_VERSION'],self.default_versions) + assert env['MSVS']['VERSION'] in self.default_versions, \ + ("env['MSVS']['VERSION'] not in self.default_versions", + env['MSVS']['VERSION'], self.default_versions) + assert v1 in self.default_versions, (self.default_versions, v1) env = DummyEnv({'MSVS_VERSION':'7.0'}) v2 = get_default_version(env) @@ -638,8 +653,9 @@ def _TODO_test_merge_default_version(self) -> None: def test_query_versions(self) -> None: """Test retrieval of the list of visual studio versions""" v1 = query_versions() - assert not v1 or str(v1[0]) == self.highest_version, \ - (v1, self.highest_version) + vh = v1[:len(self.highest_versions)] if v1 else [] + assert not v1 or vh == self.highest_versions, \ + (v1, self.highest_versions) assert len(v1) == self.number_of_versions, v1 def test_config_generation(self) -> None: @@ -798,11 +814,25 @@ def subst(self, string, *args, **kwargs): except OSError: pass +def _exists_map(prefix, suffixes): + prefix = os.path.normpath(os.path.normcase(prefix)) + paths = [ + os.path.normpath(os.path.join(prefix, os.path.normcase(suffix))) + for suffix in suffixes + ] + exists_map = { + 'prefix': prefix, + 'paths': paths, + } + return exists_map + class msvs6aTestCase(msvsTestCase): """Test MSVS 6 Registry""" registry = DummyRegistry(regdata_6a + regdata_cv) default_version = '6.0' highest_version = '6.0' + default_versions = [default_version] + highest_versions = [highest_version] number_of_versions = 1 install_locs = { '6.0' : {'VSINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio\\VC98', 'VCINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio\\VC98\\Bin'}, @@ -812,12 +842,25 @@ class msvs6aTestCase(msvsTestCase): '8.0Exp' : {}, } default_install_loc = install_locs['6.0'] + exists_maps = [ + _exists_map( + r'C:\Program Files\Microsoft Visual Studio', + [ + r'VC98', + r'VC98\bin\vcvars32.bat', + r'VC98\bin\cl.exe', + r'Common\MSDev98\Bin\msdev.com', + ] + ) + ] class msvs6bTestCase(msvsTestCase): """Test Other MSVS 6 Registry""" registry = DummyRegistry(regdata_6b + regdata_cv) default_version = '6.0' highest_version = '6.0' + default_versions = [default_version] + highest_versions = [highest_version] number_of_versions = 1 install_locs = { '6.0' : {'VSINSTALLDIR': 'C:\\VS6\\VC98', 'VCINSTALLDIR': 'C:\\VS6\\VC98\\Bin'}, @@ -827,16 +870,28 @@ class msvs6bTestCase(msvsTestCase): '8.0Exp' : {}, } default_install_loc = install_locs['6.0'] + exists_maps = [ + _exists_map( + r'C:\VS6', + [ + r'VC98', + r'VC98\bin\vcvars32.bat', + r'VC98\bin\cl.exe', + r'Common\MSDev98\Bin\msdev.com', + ] + ) + ] -class msvs6and7TestCase(msvsTestCase): - """Test MSVS 6 & 7 Registry""" - registry = DummyRegistry(regdata_6b + regdata_7 + regdata_cv) +class msvs7TestCase(msvsTestCase): + """Test MSVS 7 Registry""" + registry = DummyRegistry(regdata_7 + regdata_cv) default_version = '7.0' highest_version = '7.0' - number_of_versions = 2 + default_versions = [default_version] + highest_versions = [highest_version] + number_of_versions = 1 install_locs = { - '6.0' : {'VSINSTALLDIR': 'C:\\VS6\\VC98', - 'VCINSTALLDIR': 'C:\\VS6\\VC98\\Bin'}, + '6.0' : {}, '7.0' : {'VSINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio .NET\\Common7', 'VCINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio .NET\\Common7\\Tools'}, '7.1' : {}, @@ -844,15 +899,30 @@ class msvs6and7TestCase(msvsTestCase): '8.0Exp' : {}, } default_install_loc = install_locs['7.0'] + exists_maps = [ + _exists_map( + r'C:\Program Files\Microsoft Visual Studio .NET', + [ + r'Vc7', + r'Vc7\bin\vcvars32.bat', + r'Vc7\bin\cl.exe', + r'Common7\IDE\devenv.com', + r'Common7\Tools\vsvars32.bat', + ] + ) + ] -class msvs7TestCase(msvsTestCase): - """Test MSVS 7 Registry""" - registry = DummyRegistry(regdata_7 + regdata_cv) +class msvs6and7TestCase(msvsTestCase): + """Test MSVS 6 & 7 Registry""" + registry = DummyRegistry(regdata_6b + regdata_7 + regdata_cv) default_version = '7.0' highest_version = '7.0' - number_of_versions = 1 + default_versions = [default_version] + highest_versions = [highest_version] + number_of_versions = 2 install_locs = { - '6.0' : {}, + '6.0' : {'VSINSTALLDIR': 'C:\\VS6\\VC98', + 'VCINSTALLDIR': 'C:\\VS6\\VC98\\Bin'}, '7.0' : {'VSINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio .NET\\Common7', 'VCINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio .NET\\Common7\\Tools'}, '7.1' : {}, @@ -860,12 +930,15 @@ class msvs7TestCase(msvsTestCase): '8.0Exp' : {}, } default_install_loc = install_locs['7.0'] + exists_maps = msvs6bTestCase.exists_maps + msvs7TestCase.exists_maps class msvs71TestCase(msvsTestCase): """Test MSVS 7.1 Registry""" registry = DummyRegistry(regdata_7_1 + regdata_cv) default_version = '7.1' highest_version = '7.1' + default_versions = [default_version] + highest_versions = [highest_version] number_of_versions = 1 install_locs = { '6.0' : {}, @@ -876,13 +949,27 @@ class msvs71TestCase(msvsTestCase): '8.0Exp' : {}, } default_install_loc = install_locs['7.1'] + exists_maps = [ + _exists_map( + r'C:\Program Files\Microsoft Visual Studio .NET 2003', + [ + r'Vc7', + r'Vc7\bin\vcvars32.bat', + r'Vc7\bin\cl.exe', + r'Common7\IDE\devenv.com', + r'Common7\Tools\vsvars32.bat', + ] + ) + ] -class msvs8ExpTestCase(msvsTestCase): # XXX: only one still not working +class msvs8ExpTestCase(msvsTestCase): """Test MSVS 8 Express Registry""" registry = DummyRegistry(regdata_8exp + regdata_cv) default_version = '8.0Exp' highest_version = '8.0Exp' - number_of_versions = 1 + default_versions = ['8.0', default_version] + highest_versions = ['8.0', highest_version] + number_of_versions = 2 install_locs = { '6.0' : {}, '7.0' : {}, @@ -892,12 +979,27 @@ class msvs8ExpTestCase(msvsTestCase): # XXX: only one still not working 'VCINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio 8\\VC'}, } default_install_loc = install_locs['8.0Exp'] + exists_maps = [ + _exists_map( + r'C:\Program Files\Microsoft Visual Studio 8', + [ + r'VC', + r'VC\bin\cl.exe', + r'VC\bin\vcvars32.bat', + r'VC\vcvarsall.bat', + r'Common7\IDE\VCExpress.exe', + r'Common7\Tools\vsvars32.bat', + ] + ) + ] class msvs80TestCase(msvsTestCase): """Test MSVS 8 Registry""" registry = DummyRegistry(regdata_80 + regdata_cv) default_version = '8.0' highest_version = '8.0' + default_versions = [default_version] + highest_versions = [highest_version] number_of_versions = 1 install_locs = { '6.0' : {}, @@ -908,24 +1010,55 @@ class msvs80TestCase(msvsTestCase): '8.0Exp' : {}, } default_install_loc = install_locs['8.0'] + exists_maps = [ + _exists_map( + r'C:\Program Files\Microsoft Visual Studio 8', + [ + r'VC', + r'VC\bin\cl.exe', + r'VC\bin\vcvars32.bat', + r'VC\vcvarsall.bat', + r'Common7\IDE\devenv.com', + r'Common7\Tools\vsvars32.bat', + ] + ) + ] class msvs140TestCase(msvsTestCase): """Test MSVS 140 Registry""" registry = DummyRegistry(regdata_140 + regdata_cv) default_version = '14.0' highest_version = '14.0' - number_of_versions = 2 + default_versions = [default_version] + highest_versions = [highest_version] + number_of_versions = 1 install_locs = { - '14.0' : {'VSINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio 14.0', - 'VCINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio 14.0\\VC'}, + '14.0' : {'VSINSTALLDIR': 'C:\\Program Files (x86)\\Microsoft Visual Studio 14.0', + 'VCINSTALLDIR': 'C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC'}, } default_install_loc = install_locs['14.0'] + exists_maps = None + exists_maps = [ + _exists_map( + r'C:\Program Files (x86)\Microsoft Visual Studio 14.0', + [ + r'VC', + r'VC\bin\cl.exe', + r'VC\bin\vcvars32.bat', + r'VC\\vcvarsall.bat', + r'Common7\\IDE\\devenv.com', + r'Common7\\Tools\\vsvars32.bat', + ] + ) + ] class msvsEmptyTestCase(msvsTestCase): """Test Empty Registry""" registry = DummyRegistry(regdata_none) default_version = SupportedVSList[0].version highest_version = None + default_versions = [default_version] + highest_versions = None number_of_versions = 0 install_locs = { '6.0' : {}, @@ -934,7 +1067,8 @@ class msvsEmptyTestCase(msvsTestCase): '8.0' : {}, '8.0Exp' : {}, } - default_install_loc = install_locs['8.0Exp'] + default_install_loc = {} + exists_maps = None if __name__ == "__main__": @@ -947,7 +1081,8 @@ class msvsEmptyTestCase(msvsTestCase): SCons.Util.RegEnumKey = DummyEnumKey SCons.Util.RegEnumValue = DummyEnumValue SCons.Util.RegQueryValueEx = DummyQueryValue - SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere + + SCons.Tool.MSCommon.MSVC.VSWhere.vswhere_get_executables = DummyVsWhereExecutables os.path.exists = DummyExists # make sure all files exist :-) os.path.isfile = DummyExists # make sure all files are files :-) @@ -956,17 +1091,23 @@ class msvsEmptyTestCase(msvsTestCase): exit_val = 0 test_classes = [ - msvs6aTestCase, - msvs6bTestCase, - msvs6and7TestCase, - msvs7TestCase, - msvs71TestCase, - msvs8ExpTestCase, - msvs80TestCase, - msvs140TestCase, msvsEmptyTestCase, ] + vs_channel = SCons.Tool.MSCommon.msvs_get_channel_default() + if vs_channel != 'Preview': + + test_classes += [ + msvs6aTestCase, + msvs6bTestCase, + msvs6and7TestCase, + msvs7TestCase, + msvs71TestCase, + msvs8ExpTestCase, + msvs80TestCase, + msvs140TestCase, + ] + for test_class in test_classes: # DEBUG # print("TEST: ", test_class.__doc__) diff --git a/test/Help.py b/test/Help.py index a090d42125..d2922e7b6e 100644 --- a/test/Help.py +++ b/test/Help.py @@ -23,6 +23,7 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +import re import TestSCons test = TestSCons.TestSCons() @@ -144,6 +145,8 @@ # Enhancement: keep_local flag saves the AddOption help, # but not SCons' own help. test.write('SConstruct', r""" +import sys + AddOption( '--debugging', dest='debugging', @@ -159,14 +162,54 @@ DefaultEnvironment(tools=[]) env = Environment() +print("") +if 'SCons.Tool.MSCommon.MSVC.Options' in sys.modules.keys(): + print("MSVC_OPTIONS") +print("") + Help(vars.GenerateHelpText(env), append=True, keep_local=True) """) -expect = """\ +test.run(arguments='-h') + +def process_output(test_output): + + head, control, tail = re.split(r'\n', test_output) + + output = head + tail + + expected_locals = [ + ('--debugging', 'Compile with debugging symbols'), + ] + + known_options = [ + local for local in [ + option.strip() for option in control.splitlines() + ] if local + ] + + if 'MSVC_OPTIONS' in known_options: + expected_locals.extend([ + ('--msvs-channel=CHANNEL', 'Set default msvs CHANNEL [Release, Preview, Any].'), + ('--vswhere=EXEPATH', 'Add vswhere executable located at EXEPATH.'), + ]) + + maxarglen = len(max(expected_locals, key=lambda item: len(item[0]))[0]) + + locals_str = '\n'.join([ + f' {arg:{maxarglen}} {help}' + for arg, help in expected_locals + ]) + + return output, locals_str + +output, local_options = process_output(test.stdout()) + +expect = f"""\ scons: Reading SConscript files ... scons: done reading SConscript files. Local Options: - --debugging Compile with debugging symbols +{local_options} buildmod: List of modules to build (all|none|comma-separated list of names) @@ -177,8 +220,15 @@ Use scons -H for help about SCons built-in command-line options. """ -test.run(arguments='-h', stdout=expect) - +if output != expect: + n = 80 + print('\nOutput:\n' + '=' * n) + print(output) + print('=' * n) + print('\nExpected:\n' + '=' * n) + print(expect) + print('=' * n) + test.fail_test() test.pass_test() diff --git a/test/MSVC/MSVC_SCRIPTERROR_POLICY.py b/test/MSVC/MSVC_SCRIPTERROR_POLICY.py index 5efa753155..81686daa1b 100644 --- a/test/MSVC/MSVC_SCRIPTERROR_POLICY.py +++ b/test/MSVC/MSVC_SCRIPTERROR_POLICY.py @@ -30,13 +30,16 @@ import TestSCons import textwrap -from SCons.Tool.MSCommon.vc import get_installed_vcs_components +from SCons.Tool.MSCommon.vc import get_installed_msvc_instances test = TestSCons.TestSCons() test.skip_if_not_msvc() -installed_versions = get_installed_vcs_components() -default_version = installed_versions[0] +installed_instances = get_installed_msvc_instances() +if not installed_instances: + test.skip_test("No MSVC instances, skipping.") + +default_instance = installed_instances[0] # Test construction variable with valid symbols test.write('SConstruct', textwrap.dedent( @@ -51,7 +54,7 @@ )) test.run(arguments='-Q -s', stdout='') -if default_version.msvc_vernum >= 14.1: +if default_instance.vs_product_numeric >= 2017: # Need VS2017 or later for MSVC_SCRIPT_ARGS # Test environment construction with construction variable (invalid) diff --git a/test/MSVC/MSVC_SDK_VERSION.py b/test/MSVC/MSVC_SDK_VERSION.py index e5eae6762c..3b54c5f1bb 100644 --- a/test/MSVC/MSVC_SDK_VERSION.py +++ b/test/MSVC/MSVC_SDK_VERSION.py @@ -28,7 +28,7 @@ """ import textwrap -from SCons.Tool.MSCommon.vc import get_installed_vcs_components +from SCons.Tool.MSCommon.vc import get_installed_msvc_instances from SCons.Tool.MSCommon import msvc_sdk_versions from SCons.Tool.MSCommon import msvc_toolset_versions import TestSCons @@ -36,16 +36,33 @@ test = TestSCons.TestSCons() test.skip_if_not_msvc() -installed_versions = get_installed_vcs_components() -default_version = installed_versions[0] -GE_VS2015_versions = [v for v in installed_versions if v.msvc_vernum >= 14.0] -LT_VS2015_versions = [v for v in installed_versions if v.msvc_vernum < 14.0] +installed_instances = get_installed_msvc_instances() +if not installed_instances: + test.skip_test("No MSVC instances, skipping.") + +GE_VS2015_supported_instances = [] +GE_VS2015_unsupported_instances = [] +LT_VS2015_unsupported_instances = [] + +for msvc_instance in installed_instances: + if msvc_instance.vs_product_numeric > 2015: + GE_VS2015_supported_instances.append(msvc_instance) + elif msvc_instance.vs_product_numeric == 2015: + if msvc_instance.is_express: + GE_VS2015_unsupported_instances.append((msvc_instance, 'Express')) + elif msvc_instance.is_buildtools: + GE_VS2015_unsupported_instances.append((msvc_instance, 'BuildTools')) + else: + GE_VS2015_supported_instances.append(msvc_instance) + else: + LT_VS2015_unsupported_instances.append(msvc_instance) + default_sdk_versions_uwp = msvc_sdk_versions(version=None, msvc_uwp_app=True) default_sdk_versions_def = msvc_sdk_versions(version=None, msvc_uwp_app=False) -have_140 = any(v.msvc_verstr == '14.0' for v in GE_VS2015_versions) +have_2015 = any(msvc_instance.vs_product_numeric == 2015 for v in installed_instances) -def version_major(version): +def sdk_version_major(version): components = version.split('.') if len(components) >= 2: return components[0] + '.' + components[1][0] @@ -53,26 +70,27 @@ def version_major(version): return components[0] + '.0' return version -def version_major_list(version_list): +def sdk_version_major_list(version_list): versions = [] seen_major = set() for version in version_list: - major = version_major(version) + major = sdk_version_major(version) if major in seen_major: continue versions.append(version) seen_major.add(major) return versions -if GE_VS2015_versions: +if GE_VS2015_supported_instances: - for supported in GE_VS2015_versions: + for supported in GE_VS2015_supported_instances: + # VS2017+ and VS2015 ('14.0') sdk_versions_uwp = msvc_sdk_versions(version=supported.msvc_version, msvc_uwp_app=True) sdk_versions_def = msvc_sdk_versions(version=supported.msvc_version, msvc_uwp_app=False) # find sdk version for each major SDK - sdk_versions = version_major_list(sdk_versions_def) + sdk_versions = sdk_version_major_list(sdk_versions_def) for sdk_version in sdk_versions: @@ -144,13 +162,13 @@ def version_major_list(version_list): # platform contraints: 8.1 and UWP if '8.1' in sdk_versions: - if supported.msvc_vernum > 14.0: + if supported.vs_product_numeric > 2015: toolset_full_versions = msvc_toolset_versions(supported.msvc_version, full=True, sxs=False) - toolset_versions = version_major_list(toolset_full_versions) + toolset_versions = sdk_version_major_list(toolset_full_versions) # toolset msvc_version != current msvc_version and toolset msvc_version != 14.0 - toolset_candidates = [v for v in toolset_versions if version_major(v) not in (supported.msvc_verstr, '14.0')] + toolset_candidates = [v for v in toolset_versions if sdk_version_major(v) not in (supported.msvc_verstr, '14.0')] toolset_version = toolset_candidates[0] if toolset_candidates else None # sdk version 8.1, UWP, and msvc_verson > VS2015 @@ -181,7 +199,7 @@ def version_major_list(version_list): ) test.must_contain_all(test.stderr(), expect) - if have_140: + if have_2015: # sdk version 8.1, UWP, and msvc_toolset_version > VS2015 test.write('SConstruct', textwrap.dedent( @@ -192,7 +210,7 @@ def version_major_list(version_list): )) test.run(arguments='-Q -s', stdout='') - elif supported.msvc_vernum == 14.0: + elif supported.vs_product_numeric == 2015: # sdk version 8.1, UWP, and msvc_verson == VS2015 test.write('SConstruct', textwrap.dedent( @@ -203,9 +221,37 @@ def version_major_list(version_list): )) test.run(arguments='-Q -s', stdout='') -if LT_VS2015_versions: +if GE_VS2015_unsupported_instances: + + for unsupported, kind_str in GE_VS2015_unsupported_instances: + # VS2015 Express + + sdk_version = default_sdk_versions_def[0] if default_sdk_versions_def else '8.1' + + test.write('SConstruct', textwrap.dedent( + """ + DefaultEnvironment(tools=[]) + env = Environment(MSVC_VERSION={}, MSVC_SDK_VERSION={}, tools=['msvc']) + """.format(repr(unsupported.msvc_version), repr(sdk_version)) + )) + test.run(arguments='-Q -s', status=2, stderr=None) + expect = "MSVCArgumentError: MSVC_SDK_VERSION ({}) is not supported for MSVC_VERSION {} ({}):".format( + repr(sdk_version), repr(unsupported.msvc_version), repr(kind_str) + ) + test.must_contain_all(test.stderr(), expect) + + # MSVC_SCRIPT_ARGS sdk_version is not validated + test.write('SConstruct', textwrap.dedent( + """ + DefaultEnvironment(tools=[]) + env = Environment(MSVC_VERSION={}, MSVC_SCRIPT_ARGS={}, tools=['msvc']) + """.format(repr(unsupported.msvc_version), repr(sdk_version)) + )) + test.run(arguments='-Q -s', stdout='') + +if LT_VS2015_unsupported_instances: - for unsupported in LT_VS2015_versions: + for unsupported in LT_VS2015_unsupported_instances: # must be VS2015 or later sdk_version = default_sdk_versions_def[0] if default_sdk_versions_def else '8.1' diff --git a/test/MSVC/MSVC_SPECTRE_LIBS.py b/test/MSVC/MSVC_SPECTRE_LIBS.py index 4747e4ba42..27595348b8 100644 --- a/test/MSVC/MSVC_SPECTRE_LIBS.py +++ b/test/MSVC/MSVC_SPECTRE_LIBS.py @@ -30,20 +30,29 @@ import TestSCons import textwrap -from SCons.Tool.MSCommon.vc import get_installed_vcs_components +from SCons.Tool.MSCommon.vc import get_installed_msvc_instances from SCons.Tool.MSCommon import msvc_toolset_versions_spectre test = TestSCons.TestSCons() test.skip_if_not_msvc() -installed_versions = get_installed_vcs_components() -GE_VS2017_versions = [v for v in installed_versions if v.msvc_vernum >= 14.1] -LT_VS2017_versions = [v for v in installed_versions if v.msvc_vernum < 14.1] +installed_instances = get_installed_msvc_instances() +if not installed_instances: + test.skip_test("No MSVC instances, skipping.") -if GE_VS2017_versions: +GE_VS2017_instances = [] +LT_VS2017_instances = [] + +for msvc_instance in installed_instances: + if msvc_instance.vs_product_numeric >= 2017: + GE_VS2017_instances.append(msvc_instance) + else: + LT_VS2017_instances.append(msvc_instance) + +if GE_VS2017_instances: # VS2017 and later for toolset argument - for supported in GE_VS2017_versions: + for supported in GE_VS2017_instances: spectre_toolset_versions = msvc_toolset_versions_spectre(supported.msvc_version) spectre_toolset_version = spectre_toolset_versions[0] if spectre_toolset_versions else None @@ -121,10 +130,10 @@ )) test.run(arguments='-Q -s', stdout='') -if LT_VS2017_versions: +if LT_VS2017_instances: # VS2015 and earlier for toolset argument error - for unsupported in LT_VS2017_versions: + for unsupported in LT_VS2017_instances: # must be VS2017 or later test.write('SConstruct', textwrap.dedent( diff --git a/test/MSVC/MSVC_TOOLSET_VERSION.py b/test/MSVC/MSVC_TOOLSET_VERSION.py index a20cf8a319..6d879d9ea5 100644 --- a/test/MSVC/MSVC_TOOLSET_VERSION.py +++ b/test/MSVC/MSVC_TOOLSET_VERSION.py @@ -28,7 +28,7 @@ """ import textwrap -from SCons.Tool.MSCommon.vc import get_installed_vcs_components +from SCons.Tool.MSCommon.vc import get_installed_msvc_instances from SCons.Tool.MSCommon import msvc_toolset_versions import TestSCons @@ -36,16 +36,26 @@ test = TestSCons.TestSCons() test.skip_if_not_msvc() -installed_versions = get_installed_vcs_components() -default_version = installed_versions[0] -GE_VS2017_versions = [v for v in installed_versions if v.msvc_vernum >= 14.1] -LT_VS2017_versions = [v for v in installed_versions if v.msvc_vernum < 14.1] -LT_VS2015_versions = [v for v in LT_VS2017_versions if v.msvc_vernum < 14.0] +installed_instances = get_installed_msvc_instances() +if not installed_instances: + test.skip_test("No MSVC instances, skipping.") -if GE_VS2017_versions: +GE_VS2017_instances = [] +LT_VS2017_instances = [] +LE_VS2013_instances = [] + +for msvc_instance in installed_instances: + if msvc_instance.vs_product_numeric >= 2017: + GE_VS2017_instances.append(msvc_instance) + else: + LT_VS2017_instances.append(msvc_instance) + if msvc_instance.vs_product_numeric <= 2013: + LE_VS2013_instances.append(msvc_instance) + +if GE_VS2017_instances: # VS2017 and later for toolset argument - for supported in GE_VS2017_versions: + for supported in GE_VS2017_instances: toolset_full_versions = msvc_toolset_versions(supported.msvc_version, full=True, sxs=False) toolset_full_version = toolset_full_versions[0] if toolset_full_versions else None @@ -192,10 +202,10 @@ ) test.must_contain_all(test.stderr(), expect) -if LT_VS2017_versions: +if LT_VS2017_instances: # VS2015 and earlier for toolset argument error - for unsupported in LT_VS2017_versions: + for unsupported in LT_VS2017_instances: # must be VS2017 or later test.write('SConstruct', textwrap.dedent( @@ -210,10 +220,10 @@ ) test.must_contain_all(test.stderr(), expect) -if LT_VS2015_versions: +if LE_VS2013_instances: # VS2013 and earlier for script argument error - for unsupported in LT_VS2015_versions: + for unsupported in LE_VS2013_instances: # must be VS2015 or later for MSVC_SCRIPT_ARGS test.write('SConstruct', textwrap.dedent( diff --git a/test/MSVC/MSVC_USE_SETTINGS.py b/test/MSVC/MSVC_USE_SETTINGS.py index 7c58c7b9a5..fd6f85ceb5 100644 --- a/test/MSVC/MSVC_USE_SETTINGS.py +++ b/test/MSVC/MSVC_USE_SETTINGS.py @@ -56,7 +56,7 @@ """ % locals()) test.run(arguments="--warn=visual-c-missing .", status=0, stderr=None) -test.must_contain_all(test.stderr(), "Could not find MSVC compiler 'cl'") +test.must_contain_all(test.stderr(), "Could not find requested MSVC compiler 'cl'") test.write('SConstruct', """ env = Environment(MSVC_USE_SETTINGS='dict or None') diff --git a/test/MSVC/MSVC_UWP_APP.py b/test/MSVC/MSVC_UWP_APP.py index 30b07ef771..726175abba 100644 --- a/test/MSVC/MSVC_UWP_APP.py +++ b/test/MSVC/MSVC_UWP_APP.py @@ -29,15 +29,32 @@ import textwrap import re -from SCons.Tool.MSCommon.vc import get_installed_vcs_components +from SCons.Tool.MSCommon.vc import get_installed_msvc_instances +from SCons.Tool.MSCommon.vc import get_native_host_platform +from SCons.Tool.MSCommon.vc import _GE2022_HOST_TARGET_CFG import TestSCons test = TestSCons.TestSCons() test.skip_if_not_msvc() -installed_versions = get_installed_vcs_components() -GE_VS2015_versions = [v for v in installed_versions if v.msvc_vernum >= 14.0] -LT_VS2015_versions = [v for v in installed_versions if v.msvc_vernum < 14.0] +installed_instances = get_installed_msvc_instances() +if not installed_instances: + test.skip_test("No MSVC instances, skipping.") + +GE_VS2015_supported_instances = [] +GE_VS2015_unsupported_instances = [] +LT_VS2015_unsupported_instances = [] + +for msvc_instance in installed_instances: + if msvc_instance.vs_product_numeric > 2015: + GE_VS2015_supported_instances.append(msvc_instance) + elif msvc_instance.vs_product_numeric == 2015: + if msvc_instance.is_buildtools: + GE_VS2015_unsupported_instances.append((msvc_instance, 'BuildTools')) + else: + GE_VS2015_supported_instances.append(msvc_instance) + else: + LT_VS2015_unsupported_instances.append(msvc_instance) # Look for the Store VC Lib paths in the LIBPATH: # [VS install path]\VC\LIB\store[\arch] and @@ -46,49 +63,71 @@ # C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\LIB\store\amd64 # C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\LIB\store\references +# By default, 2015 Express supports the store argument only for x86 targets. +# Using MSVC_SCRIPT_ARGS to set the store argument is not validated and +# will result in the store paths not found on 64-bit hosts when using the +# default target architecture. + +# By default, 2015 BuildTools silently ignores the store argument. +# Using MSVC_SCRIPT_ARGS to set the store argument is not validated and +# will result in the store paths not found. + +re_lib_eq2015exp_1 = re.compile(r'\\vc\\lib\\store', re.IGNORECASE) + re_lib_eq2015_1 = re.compile(r'\\vc\\lib\\store\\references', re.IGNORECASE) re_lib_eq2015_2 = re.compile(r'\\vc\\lib\\store', re.IGNORECASE) re_lib_ge2017_1 = re.compile(r'\\lib\\x86\\store\\references', re.IGNORECASE) re_lib_ge2017_2 = re.compile(r'\\lib\\x64\\store', re.IGNORECASE) +def check_libpath(msvc_instance, active, output): -def check_libpath(msvc, active, output): - def _check_libpath(msvc, output): + def _check_libpath(msvc_instance, output): + is_supported = True outdict = {key.strip(): val.strip() for key, val in [line.split('|') for line in output.splitlines()]} platform = outdict.get('PLATFORM', '') libpath = outdict.get('LIBPATH', '') + uwpsupported = outdict.get('UWPSUPPORTED', '') + if uwpsupported and uwpsupported.split('|')[-1] == '0': + is_supported = False n_matches = 0 - if msvc.msvc_verstr == '14.0': + if msvc_instance.vs_product_numeric == 2015: + if msvc_instance.is_express: + for regex in (re_lib_eq2015exp_1,): + if regex.search(libpath): + n_matches += 1 + return n_matches > 0, 'store', libpath, is_supported for regex in (re_lib_eq2015_1, re_lib_eq2015_2): if regex.search(libpath): n_matches += 1 - return n_matches >= 2, 'store', libpath + return n_matches >= 2, 'store', libpath, is_supported elif platform == 'UWP': for regex in (re_lib_ge2017_1, re_lib_ge2017_2): if regex.search(libpath): n_matches += 1 - return n_matches > 0, 'uwp', libpath - return False, 'uwp', libpath + return n_matches > 0, 'uwp', libpath, is_supported + return False, 'uwp', libpath, is_supported - found, kind, libpath = _check_libpath(msvc, output) + found, kind, libpath, is_supported = _check_libpath(msvc_instance, output) failmsg = None if active and not found: failmsg = 'msvc version {} {} paths not found in lib path {}'.format( - repr(msvc.msvc_version), repr(kind), repr(libpath) + repr(msvc_instance.msvc_version), repr(kind), repr(libpath) ) elif not active and found: failmsg = 'msvc version {} {} paths found in lib path {}'.format( - repr(msvc.msvc_version), repr(kind), repr(libpath) + repr(msvc_instance.msvc_version), repr(kind), repr(libpath) ) - return failmsg + return failmsg, is_supported -if GE_VS2015_versions: +if GE_VS2015_supported_instances: # VS2015 and later for uwp/store argument - for supported in GE_VS2015_versions: + + for supported in GE_VS2015_supported_instances: + for msvc_uwp_app in (True, '1', False, '0', None): active = msvc_uwp_app in (True, '1') @@ -102,7 +141,7 @@ def _check_libpath(msvc, output): """.format(repr(supported.msvc_version), repr(msvc_uwp_app)) )) test.run(arguments='-Q -s', stdout=None) - failmsg = check_libpath(supported, active, test.stdout()) + failmsg, _ = check_libpath(supported, active, test.stdout()) if failmsg: test.fail_test(message=failmsg) @@ -120,23 +159,78 @@ def _check_libpath(msvc, output): if not test.stderr().strip().startswith("MSVCArgumentError: multiple uwp declarations:"): test.fail_test(message='Expected MSVCArgumentError') - # uwp using script argument + if supported.vs_product_numeric == 2015 and supported.is_express: + + # uwp using script argument may not be supported for default target architecture + test.write('SConstruct', textwrap.dedent( + """ + from SCons.Tool.MSCommon.vc import find_msvc_instance + DefaultEnvironment(tools=[]) + env = Environment(MSVC_VERSION={0}, MSVC_SCRIPT_ARGS='store', tools=['msvc']) + msvc_instance = find_msvc_instance({0}, env) + is_supported, is_target = msvc_instance.is_uwp_target_supported(env['TARGET_ARCH']) + uwpsupported = '1' if is_supported else '0' + print('LIBPATH|' + env['ENV'].get('LIBPATH', '')) + print('PLATFORM|' + env['ENV'].get('VSCMD_ARG_app_plat','')) + print('UWPSUPPORTED|' + uwpsupported) + """.format(repr(supported.msvc_version)) + )) + test.run(arguments='-Q -s', stdout=None) + + else: + + # uwp using script argument + test.write('SConstruct', textwrap.dedent( + """ + DefaultEnvironment(tools=[]) + env = Environment(MSVC_VERSION={}, MSVC_SCRIPT_ARGS='store', tools=['msvc']) + print('LIBPATH|' + env['ENV'].get('LIBPATH', '')) + print('PLATFORM|' + env['ENV'].get('VSCMD_ARG_app_plat','')) + """.format(repr(supported.msvc_version)) + )) + test.run(arguments='-Q -s', stdout=None) + + failmsg, is_supported = check_libpath(supported, True, test.stdout()) + if failmsg and is_supported: + test.fail_test(message=failmsg) + +if GE_VS2015_unsupported_instances: + # VS2015 and later for uwp/store error + + for unsupported, kind_str in GE_VS2015_unsupported_instances: + + for msvc_uwp_app in (True, '1'): + + # uwp using construction variable + test.write('SConstruct', textwrap.dedent( + """ + DefaultEnvironment(tools=[]) + env = Environment(MSVC_VERSION={}, MSVC_UWP_APP={}, tools=['msvc']) + """.format(repr(unsupported.msvc_version), repr(msvc_uwp_app)) + )) + test.run(arguments='-Q -s', status=2, stderr=None) + expect = "MSVCArgumentError: MSVC_UWP_APP ({}) is not supported for MSVC_VERSION {} ({}):".format( + repr(msvc_uwp_app), repr(unsupported.msvc_version), repr(kind_str) + ) + test.must_contain_all(test.stderr(), expect) + + # MSVC_SCRIPT_ARGS store is not validated test.write('SConstruct', textwrap.dedent( """ DefaultEnvironment(tools=[]) env = Environment(MSVC_VERSION={}, MSVC_SCRIPT_ARGS='store', tools=['msvc']) - print('LIBPATH|' + env['ENV'].get('LIBPATH', '')) - print('PLATFORM|' + env['ENV'].get('VSCMD_ARG_app_plat','')) - """.format(repr(supported.msvc_version)) + """.format(repr(unsupported.msvc_version)) )) - test.run(arguments='-Q -s', stdout=None) - failmsg = check_libpath(supported, True, test.stdout()) - if failmsg: - test.fail_test(message=failmsg) + test.run(arguments='-Q -s', stdout='') + failmsg, _ = check_libpath(unsupported, True, test.stdout()) + if not failmsg: + test.fail_test(message='unexpected: store found in libpath') -if LT_VS2015_versions: +if LT_VS2015_unsupported_instances: # VS2013 and earlier for uwp/store error - for unsupported in LT_VS2015_versions: + + for unsupported in LT_VS2015_unsupported_instances: + for msvc_uwp_app in (True, '1', False, '0', None): active = msvc_uwp_app in (True, '1') diff --git a/test/MSVC/VSWHERE-fixture/SConstruct b/test/MSVC/VSWHERE-fixture/SConstruct index 74eea18c16..aff4c784b3 100644 --- a/test/MSVC/VSWHERE-fixture/SConstruct +++ b/test/MSVC/VSWHERE-fixture/SConstruct @@ -5,7 +5,7 @@ import os import os.path -from SCons.Tool.MSCommon.vc import VSWHERE_PATHS +from SCons.Tool.MSCommon.MSVC.VSWhere import VSWHERE_PATHS # Dump out expected paths for vw_path in VSWHERE_PATHS: diff --git a/test/MSVC/VSWHERE.py b/test/MSVC/VSWHERE.py index 8212415f37..64addd052d 100644 --- a/test/MSVC/VSWHERE.py +++ b/test/MSVC/VSWHERE.py @@ -28,6 +28,7 @@ Also test that vswhere.exe is found and sets VSWHERE to the correct values """ import os.path +import SCons.Tool.MSCommon import TestSCons _python_ = TestSCons._python_ @@ -36,6 +37,14 @@ test.skip_if_not_msvc() test.verbose_set(1) +installed_instances = SCons.Tool.MSCommon.vc.get_installed_msvc_instances() +if not installed_instances: + test.skip_test("No MSVC instances, skipping.") + +_default_instance = installed_instances[0] +if _default_instance.vs_product_numeric < 2017: + test.skip_test("no installed msvc requires vswhere.exe; skipping test\n") + test.dir_fixture('VSWHERE-fixture') test.run(arguments=".") diff --git a/test/MSVC/msvc_cache_force_defaults.py b/test/MSVC/msvc_cache_force_defaults.py index e0ed1c3543..ffb7fcdb1c 100644 --- a/test/MSVC/msvc_cache_force_defaults.py +++ b/test/MSVC/msvc_cache_force_defaults.py @@ -29,16 +29,19 @@ import textwrap -from SCons.Tool.MSCommon.vc import get_installed_vcs_components +from SCons.Tool.MSCommon.vc import get_installed_msvc_instances import TestSCons test = TestSCons.TestSCons() test.skip_if_not_msvc() -installed_versions = get_installed_vcs_components() -default_version = installed_versions[0] -if default_version.msvc_vernum < 14.0: - test.skip_test("MSVC version earlier than 14.0, skipping.") +installed_instances = get_installed_msvc_instances() +if not installed_instances: + test.skip_test("No MSVC instances, skipping.") + +default_instance = installed_instances[0] +if default_instance.vs_product_numeric < 2015: + test.skip_test("MSVC product earlier than 2015, skipping.") # VS2015 and later # force SDK version and toolset version as msvc batch file arguments @@ -67,9 +70,16 @@ )) test.run(arguments="-Q -s", status=0, stdout=None) cache_arg = test.stdout().strip() -if default_version.msvc_verstr == '14.0': - # VS2015: target_arch msvc_sdk_version - expect = r'^SCRIPT_ARGS: .* [0-9.]+$' +if default_instance.vs_product_numeric == 2015: + if default_instance.is_express: + # VS2015 Express: target_arch + expect = r'^SCRIPT_ARGS: [a-zA-Z0-9_]+$' + elif default_instance.is_buildtools: + # VS2015 BuildTools: target_arch + expect = r'^SCRIPT_ARGS: [a-zA-Z0-9_]+$' + else: + # VS2015: target_arch msvc_sdk_version + expect = r'^SCRIPT_ARGS: [a-zA-Z0-9_]+ [0-9.]+$' else: # VS2017+ msvc_sdk_version msvc_toolset_version expect = r'^SCRIPT_ARGS: [0-9.]+ -vcvars_ver=[0-9.]+$' diff --git a/test/MSVC/no_msvc.py b/test/MSVC/no_msvc.py index 35cce9258d..6b24106b32 100644 --- a/test/MSVC/no_msvc.py +++ b/test/MSVC/no_msvc.py @@ -35,7 +35,7 @@ if sys.platform != 'win32': test.skip_test("Not win32 platform. Skipping test\n") -# test find_vc_pdir_vswhere by removing all other VS's reg keys +# test msvc selection by removing all other VS's reg keys test.file_fixture('no_msvc/no_regs_sconstruct.py', 'SConstruct') test.run(arguments='-Q -s', stdout='') diff --git a/test/fixture/no_msvc/no_msvcs_sconstruct.py b/test/fixture/no_msvc/no_msvcs_sconstruct.py index 1aed9ecc1c..437383c950 100644 --- a/test/fixture/no_msvc/no_msvcs_sconstruct.py +++ b/test/fixture/no_msvc/no_msvcs_sconstruct.py @@ -7,15 +7,13 @@ DefaultEnvironment(tools=[]) -def DummyVsWhere(msvc_version, env): - # not testing versions with vswhere, so return none - return None +def DummyVsWhereExecutables(vswhere_env=None): + # not testing versions with vswhere, so return empty list + return [] -for key in SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR: - SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR[key] = [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'') - ] +for detect_cfg in SCons.Tool.MSCommon.MSVC.VSDetect._VSDetectRegistry.DETECT_CONFIG.values(): + detect_cfg.vc_cfg.regkeys.clear() -SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere +SCons.Tool.MSCommon.MSVC.VSWhere.vswhere_get_executables = DummyVsWhereExecutables env = SCons.Environment.Environment() print('MSVC_VERSION=' + str(env.get('MSVC_VERSION'))) diff --git a/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_query_toolset_version.py b/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_query_toolset_version.py index fc5558b54a..6bf0e58665 100644 --- a/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_query_toolset_version.py +++ b/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_query_toolset_version.py @@ -7,15 +7,13 @@ DefaultEnvironment(tools=[]) -def DummyVsWhere(msvc_version, env): - # not testing versions with vswhere, so return none - return None +def DummyVsWhereExecutables(vswhere_env=None): + # not testing versions with vswhere, so return empty list + return [] -for key in SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR: - SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR[key] = [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'') - ] +for detect_cfg in SCons.Tool.MSCommon.MSVC.VSDetect._VSDetectRegistry.DETECT_CONFIG.values(): + detect_cfg.vc_cfg.regkeys.clear() -SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere +SCons.Tool.MSCommon.MSVC.VSWhere.vswhere_get_executables = DummyVsWhereExecutables msvc_version, msvc_toolset_version = SCons.Tool.MSCommon.msvc_query_version_toolset() print(f'msvc_version={msvc_version!r}, msvc_toolset_version={msvc_toolset_version!r}') diff --git a/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_sdk_versions.py b/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_sdk_versions.py index 6e7562d390..64699b724f 100644 --- a/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_sdk_versions.py +++ b/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_sdk_versions.py @@ -7,15 +7,13 @@ DefaultEnvironment(tools=[]) -def DummyVsWhere(msvc_version, env): - # not testing versions with vswhere, so return none - return None +def DummyVsWhereExecutables(vswhere_env=None): + # not testing versions with vswhere, so return empty list + return [] -for key in SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR: - SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR[key] = [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'') - ] +for detect_cfg in SCons.Tool.MSCommon.MSVC.VSDetect._VSDetectRegistry.DETECT_CONFIG.values(): + detect_cfg.vc_cfg.regkeys.clear() -SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere +SCons.Tool.MSCommon.MSVC.VSWhere.vswhere_get_executables = DummyVsWhereExecutables sdk_version_list = SCons.Tool.MSCommon.msvc_sdk_versions() print('sdk_version_list=' + repr(sdk_version_list)) diff --git a/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_toolset_versions.py b/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_toolset_versions.py index c2208cd393..e1d4bd10ce 100644 --- a/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_toolset_versions.py +++ b/test/fixture/no_msvc/no_msvcs_sconstruct_msvc_toolset_versions.py @@ -7,15 +7,13 @@ DefaultEnvironment(tools=[]) -def DummyVsWhere(msvc_version, env): - # not testing versions with vswhere, so return none - return None +def DummyVsWhereExecutables(vswhere_env=None): + # not testing versions with vswhere, so return empty list + return [] -for key in SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR: - SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR[key] = [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'') - ] +for detect_cfg in SCons.Tool.MSCommon.MSVC.VSDetect._VSDetectRegistry.DETECT_CONFIG.values(): + detect_cfg.vc_cfg.regkeys.clear() -SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere +SCons.Tool.MSCommon.MSVC.VSWhere.vswhere_get_executables = DummyVsWhereExecutables toolset_version_list = SCons.Tool.MSCommon.msvc_toolset_versions() print('toolset_version_list=' + repr(toolset_version_list)) diff --git a/test/fixture/no_msvc/no_msvcs_sconstruct_tools.py b/test/fixture/no_msvc/no_msvcs_sconstruct_tools.py index 2f2f07aedc..0724b81d25 100644 --- a/test/fixture/no_msvc/no_msvcs_sconstruct_tools.py +++ b/test/fixture/no_msvc/no_msvcs_sconstruct_tools.py @@ -7,15 +7,13 @@ DefaultEnvironment(tools=[]) -def DummyVsWhere(msvc_version, env): - # not testing versions with vswhere, so return none - return None +def DummyVsWhereExecutables(vswhere_env=None): + # not testing versions with vswhere, so return empty list + return [] -for key in SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR: - SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR[key] = [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'') - ] +for detect_cfg in SCons.Tool.MSCommon.MSVC.VSDetect._VSDetectRegistry.DETECT_CONFIG.values(): + detect_cfg.vc_cfg.regkeys.clear() -SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere +SCons.Tool.MSCommon.MSVC.VSWhere.vswhere_get_executables = DummyVsWhereExecutables env = SCons.Environment.Environment(tools=['myignoredefaultmsvctool']) diff --git a/test/fixture/no_msvc/no_msvcs_sconstruct_version.py b/test/fixture/no_msvc/no_msvcs_sconstruct_version.py index bd81688e35..e8a6caf9cb 100644 --- a/test/fixture/no_msvc/no_msvcs_sconstruct_version.py +++ b/test/fixture/no_msvc/no_msvcs_sconstruct_version.py @@ -7,15 +7,13 @@ DefaultEnvironment(tools=[]) -def DummyVsWhere(msvc_version, env): - # not testing versions with vswhere, so return none - return None +def DummyVsWhereExecutables(vswhere_env=None): + # not testing versions with vswhere, so return empty list + return [] -for key in SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR: - SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR[key] = [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'') - ] +for detect_cfg in SCons.Tool.MSCommon.MSVC.VSDetect._VSDetectRegistry.DETECT_CONFIG.values(): + detect_cfg.vc_cfg.regkeys.clear() -SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere +SCons.Tool.MSCommon.MSVC.VSWhere.vswhere_get_executables = DummyVsWhereExecutables SCons.Tool.MSCommon.msvc_set_notfound_policy('error') env = SCons.Environment.Environment(MSVC_VERSION='14.3') diff --git a/test/fixture/no_msvc/no_regs_sconstruct.py b/test/fixture/no_msvc/no_regs_sconstruct.py index 9dcc7a8bd5..1b4e5cec38 100644 --- a/test/fixture/no_msvc/no_regs_sconstruct.py +++ b/test/fixture/no_msvc/no_regs_sconstruct.py @@ -7,9 +7,7 @@ DefaultEnvironment(tools=[]) -for key in SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR: - SCons.Tool.MSCommon.vc._VCVER_TO_PRODUCT_DIR[key] = [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'') - ] +for detect_cfg in SCons.Tool.MSCommon.MSVC.VSDetect._VSDetectRegistry.DETECT_CONFIG.values(): + detect_cfg.vc_cfg.regkeys.clear() env = SCons.Environment.Environment()