diff options
author | Joseph Brill <48932340+jcbrill@users.noreply.github.com> | 2022-06-16 15:41:17 (GMT) |
---|---|---|
committer | Joseph Brill <48932340+jcbrill@users.noreply.github.com> | 2022-06-16 15:41:17 (GMT) |
commit | 5187917d8a99b86e965ddeb676b0f8fe6c670318 (patch) | |
tree | 2419e29fcd5e9284cd4708418bcddc87946d179b /SCons/Tool | |
parent | 5b8d1c4ba03b422f7415bea638cb84c4b974a494 (diff) | |
download | SCons-5187917d8a99b86e965ddeb676b0f8fe6c670318.zip SCons-5187917d8a99b86e965ddeb676b0f8fe6c670318.tar.gz SCons-5187917d8a99b86e965ddeb676b0f8fe6c670318.tar.bz2 |
Add SDK version support and validate all arguments.
Diffstat (limited to 'SCons/Tool')
-rw-r--r-- | SCons/Tool/MSCommon/vc.py | 1079 |
1 files changed, 731 insertions, 348 deletions
diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py index 0c9fcee..c44c698 100644 --- a/SCons/Tool/MSCommon/vc.py +++ b/SCons/Tool/MSCommon/vc.py @@ -101,6 +101,17 @@ class BatchFileExecutionWarning(SCons.Warnings.WarningOnByDefault): class _Const: + BOOLEAN_KEYS = {} + BOOLEAN_SYMBOLS = {} + + for bool, symbol_list in [ + (False, (0, '0', False, 'False', 'FALSE', 'false', 'No', 'NO', 'no', None, '')), + (True, (1, '1', True, 'True', 'TRUE', 'true', 'Yes', 'YES', 'yes', )), + ]: + BOOLEAN_KEYS[bool] = symbol_list + for symbol in symbol_list: + BOOLEAN_SYMBOLS[symbol] = bool + MSVC_RUNTIME_DEFINITION = namedtuple('MSVCRuntime', [ 'vc_runtime', 'vc_runtime_numeric', @@ -1197,7 +1208,8 @@ def reset_installed_vcs(): global __INSTALLED_VCS_RUN __INSTALLED_VCS_RUN = None _MSVCSetupEnvDefault.reset() - _MSVCScriptArguments.reset() + _WindowsSDK.reset() + _ScriptArguments.reset() # Running these batch files isn't cheap: most of the time spent in # msvs.generate() is due to vcvars*.bat. In a build that uses "tools='msvs'" @@ -1622,10 +1634,571 @@ def msvc_setup_env_once(env, tool=None): " Requested tool(s) are: {}".format(req_tools) _msvc_notfound_policy_handler(env, msg) -class _MSVCScriptArguments: +def msvc_find_valid_batch_script(env, version): + """Find and execute appropriate batch script to set up build env. + + The MSVC build environment depends heavily on having the shell + environment set. SCons does not inherit that, and does not count + on that being set up correctly anyway, so it tries to find the right + MSVC batch script, or the right arguments to the generic batch script + vcvarsall.bat, and run that, so we have a valid environment to build in. + There are dragons here: the batch scripts don't fail (see comments + elsewhere), they just leave you with a bad setup, so try hard to + get it right. + """ + + # 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 + + d = None + version_installed = False + for host_arch, target_arch, in host_target_list: + # Set to current arch. + env['TARGET_ARCH'] = target_arch + arg = '' + + # Try to locate a batch file for this host/target platform combo + try: + (vc_script, arg, vc_dir, sdk_script) = find_batch_file(env, version, host_arch, target_arch) + debug('vc_script:%s vc_script_arg:%s sdk_script:%s', vc_script, arg, sdk_script) + version_installed = True + except VisualCException as e: + msg = str(e) + debug('Caught exception while looking for batch file (%s)', msg) + version_installed = False + continue + + # Try to use the located batch file for this host/target platform combo + debug('use_script 2 %s, args:%s', repr(vc_script), arg) + found = None + if vc_script: + arg = _ScriptArguments.msvc_script_arguments(env, version, vc_dir, arg) + try: + d = script_env(vc_script, args=arg) + found = vc_script + except BatchFileExecutionError as e: + debug('use_script 3: failed running VC script %s: %s: Error:%s', repr(vc_script), arg, e) + vc_script=None + continue + if not vc_script and sdk_script: + debug('use_script 4: trying sdk script: %s', sdk_script) + try: + d = script_env(sdk_script) + found = sdk_script + except BatchFileExecutionError as e: + debug('use_script 5: failed running SDK script %s: Error:%s', repr(sdk_script), e) + continue + elif not vc_script and not sdk_script: + debug('use_script 6: Neither VC script nor SDK script found') + continue + + debug("Found a working script/target: %s/%s", repr(found), 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: + env['TARGET_ARCH'] = target_platform + + if version_installed: + msg = "MSVC version '{}' working host/target script was not found.\n" \ + " Host = '{}', Target = '{}'\n" \ + " Visual Studio C/C++ compilers may not be set correctly".format( + version, host_platform, target_platform + ) + else: + installed_vcs = get_installed_vcs(env) + if installed_vcs: + msg = "MSVC version '{}' was not found.\n" \ + " Visual Studio C/C++ compilers may not be set correctly.\n" \ + " Installed versions are: {}".format(version, installed_vcs) + else: + msg = "MSVC version '{}' was not found.\n" \ + " No versions of the MSVC compiler were found.\n" \ + " Visual Studio C/C++ compilers may not be set correctly".format(version) + + _msvc_notfound_policy_handler(env, msg) + + return d + +_undefined = None + +def get_use_script_use_settings(env): + global _undefined + + if _undefined is None: + _undefined = object() + + # 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 + + # None (documentation) or evaluates False (code): bypass detection + # need to distinguish between undefined and None + use_script = env.get('MSVC_USE_SCRIPT', _undefined) + + if use_script != _undefined: + # use_script defined, use_settings ignored (not type checked) + return (use_script, None) + + # undefined or None: use_settings ignored + use_settings = env.get('MSVC_USE_SETTINGS', None) + + if use_settings is not None: + # use script undefined, use_settings defined and not None (type checked) + return (False, use_settings) + + # use script undefined, use_settings undefined or None + return (True, None) + +def msvc_setup_env(env): + debug('called') + version = get_default_version(env) + if version is None: + if not msvc_setup_env_user(env): + _MSVCSetupEnvDefault.set_nodefault() + return None + + # XXX: we set-up both MSVS version for backward + # compatibility with the msvs tool + env['MSVC_VERSION'] = version + env['MSVS_VERSION'] = version + env['MSVS'] = {} + + use_script, use_settings = get_use_script_use_settings(env) + if SCons.Util.is_String(use_script): + use_script = use_script.strip() + if not os.path.exists(use_script): + raise MSVCScriptNotFound('Script specified by MSVC_USE_SCRIPT not found: "{}"'.format(use_script)) + args = env.subst('$MSVC_USE_SCRIPT_ARGS') + debug('use_script 1 %s %s', repr(use_script), repr(args)) + d = script_env(use_script, args) + elif use_script: + d = msvc_find_valid_batch_script(env,version) + debug('use_script 2 %s', d) + if not d: + return d + elif use_settings is not None: + if not SCons.Util.is_Dict(use_settings): + error_msg = 'MSVC_USE_SETTINGS type error: expected a dictionary, found {}'.format(type(use_settings).__name__) + 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." + SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg) + return None + + for k, v in d.items(): + 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) + if CONFIG_CACHE: + propose = "SCONS_CACHE_MSVC_CONFIG caching enabled, remove cache file {} if out of date.".format(CONFIG_CACHE) + else: + propose = "It may need to be installed separately with Visual Studio." + warn_msg = "Could not find MSVC compiler 'cl'. {}".format(propose) + SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg) + +def msvc_exists(env=None, version=None): + debug('version=%s', repr(version)) + vcs = get_installed_vcs(env) + if version is None: + rval = len(vcs) > 0 + else: + rval = version in vcs + debug('version=%s, return=%s', repr(version), rval) + return rval + +def msvc_setup_env_user(env=None): + rval = False + if env: + + # Intent is to use msvc tools: + # MSVC_VERSION or MSVS_VERSION: defined and is True + # MSVC_USE_SCRIPT: defined and (is string or is False) + # MSVC_USE_SETTINGS: defined and is not None + + # defined and is True + for key in ['MSVC_VERSION', 'MSVS_VERSION']: + if key in env and env[key]: + rval = True + debug('key=%s, return=%s', repr(key), rval) + return rval + + # defined and (is string or is False) + for key in ['MSVC_USE_SCRIPT']: + if key in env and (SCons.Util.is_String(env[key]) or not env[key]): + rval = True + debug('key=%s, return=%s', repr(key), rval) + return rval + + # defined and is not None + for key in ['MSVC_USE_SETTINGS']: + if key in env and env[key] is not None: + rval = True + debug('key=%s, return=%s', repr(key), rval) + return rval + + debug('return=%s', rval) + return rval + +def msvc_setup_env_tool(env=None, version=None, tool=None): + debug('tool=%s, version=%s', repr(tool), repr(version)) + _MSVCSetupEnvDefault.register_tool(env, tool) + rval = False + if not rval and msvc_exists(env, version): + rval = True + if not rval and msvc_setup_env_user(env): + rval = True + debug('tool=%s, version=%s, return=%s', repr(tool), repr(version), rval) + return rval + +class _Util: + + @staticmethod + def listdir_dirs(p): + dirs = [] + for dir_name in os.listdir(p): + dir_path = os.path.join(p, dir_name) + if os.path.isdir(dir_path): + dirs.append((dir_name, dir_path)) + return dirs + + @staticmethod + def process_path(p): + if p: + p = os.path.normpath(p) + p = os.path.realpath(p) + p = os.path.normcase(p) + return p + +class _Registry: + + def read_value(hkey, subkey_valname): + try: + rval = common.read_reg(subkey_valname, hkroot=hkey) + except OSError: + debug('OSError: hkey=%s, subkey=%s', repr(hkey), repr(subkey_valname)) + return None + except IndexError: + debug('IndexError: hkey=%s, subkey=%s', repr(hkey), repr(subkey_valname)) + return None + debug('hkey=%s, subkey=%s, rval=%s', repr(hkey), repr(subkey_valname), repr(rval)) + return rval + + @classmethod + def registry_query_path(cls, key, val, suffix): + extval = val + '\\' + suffix if suffix else val + qpath = cls.read_value(key, extval) + if qpath and os.path.exists(qpath): + qpath = _Util.process_path(qpath) + else: + qpath = None + return (qpath, key, val, extval) + + REG_SOFTWARE_MICROSOFT = [ + (SCons.Util.HKEY_LOCAL_MACHINE, r'Software\Wow6432Node\Microsoft'), + (SCons.Util.HKEY_CURRENT_USER, r'Software\Wow6432Node\Microsoft'), # SDK queries + (SCons.Util.HKEY_LOCAL_MACHINE, r'Software\Microsoft'), + (SCons.Util.HKEY_CURRENT_USER, r'Software\Microsoft'), + ] + + @classmethod + def microsoft_query_paths(cls, suffix, usrval=None): + paths = [] + records = [] + for key, val in cls.REG_SOFTWARE_MICROSOFT: + extval = val + '\\' + suffix if suffix else val + qpath = cls.read_value(key, extval) + if qpath and os.path.exists(qpath): + qpath = _Util.process_path(qpath) + if qpath not in paths: + paths.append(qpath) + records.append((qpath, key, val, extval, usrval)) + return records + + @classmethod + def microsoft_query_keys(cls, suffix, usrval=None): + records = [] + for key, val in cls.REG_SOFTWARE_MICROSOFT: + extval = val + '\\' + suffix if suffix else val + rval = cls.read_value(key, extval) + if rval: + records.append((key, val, extval, usrval)) + return records + + @classmethod + def microsoft_sdks(cls, version): + return '\\'.join([r'Microsoft SDKs\Windows', 'v' + version, r'InstallationFolder']) + + @classmethod + def sdk_query_paths(cls, version): + q = cls.microsoft_sdks(version) + return cls.microsoft_query_paths(q) + + @classmethod + def windows_kits(cls, version): + return r'Windows Kits\Installed Roots\KitsRoot' + version + + @classmethod + def windows_kit_query_paths(cls, version): + q = cls.windows_kits(version) + return cls.microsoft_query_paths(q) + +class _WindowsSDK: + + sdk_map_cache = {} + sdk_cache = {} + + @classmethod + def reset(cls): + cls.sdk_map_cache = {} + cls.sdk_cache = {} + + @classmethod + def _new_sdk_map(cls): + sdk_map = { + 'desktop': [], + 'uwp': [], + } + return sdk_map + + @classmethod + def _sdk_10_layout(cls, version): + + folder_prefix = version + '.' + + sdk_map = cls._new_sdk_map() + + sdk_roots = _Registry.sdk_query_paths(version) + + sdk_version_platform_seen = set() + sdk_roots_seen = set() + + for sdk_t in sdk_roots: + + sdk_root = sdk_t[0] + if sdk_root in sdk_roots_seen: + continue + sdk_roots_seen.add(sdk_root) + + if not os.path.exists(sdk_root): + continue + + sdk_include_path = os.path.join(sdk_root, 'include') + if not os.path.exists(sdk_include_path): + continue + + for version_nbr, version_nbr_path in _Util.listdir_dirs(sdk_include_path): + + if not version_nbr.startswith(folder_prefix): + continue + + sdk_inc_path = _Util.process_path(os.path.join(version_nbr_path, 'um')) + if not os.path.exists(sdk_inc_path): + continue + + for platform, sdk_inc_file in [ + ('desktop', 'winsdkver.h'), + ('uwp', 'windows.h'), + ]: + + if not os.path.exists(os.path.join(sdk_inc_path, sdk_inc_file)): + continue + + key = (version_nbr, platform) + if key in sdk_version_platform_seen: + continue + sdk_version_platform_seen.add(key) - # Force -vcvars_ver argument for default toolset - MSVC_TOOLSET_DEFAULT_VCVARSVER = False + sdk_map[platform].append(version_nbr) + + for key, val in sdk_map.items(): + val.sort(reverse=True) + + return sdk_map + + @classmethod + def _sdk_81_layout(cls, version): + + version_nbr = version + + sdk_map = cls._new_sdk_map() + + sdk_roots = _Registry.sdk_query_paths(version) + + sdk_version_platform_seen = set() + sdk_roots_seen = set() + + sdk_targets = [] + + for sdk_t in sdk_roots: + + sdk_root = sdk_t[0] + if sdk_root in sdk_roots_seen: + continue + sdk_roots_seen.add(sdk_root) + + # msvc does not check for existence of root or other files + + sdk_inc_path = _Util.process_path(os.path.join(sdk_root, r'include\um')) + if not os.path.exists(sdk_inc_path): + continue + + for platform, sdk_inc_file in [ + ('desktop', 'winsdkver.h'), + ('uwp', 'windows.h'), + ]: + + if not os.path.exists(os.path.join(sdk_inc_path, sdk_inc_file)): + continue + + key = (version_nbr, platform) + if key in sdk_version_platform_seen: + continue + sdk_version_platform_seen.add(key) + + sdk_map[platform].append(version_nbr) + + for key, val in sdk_map.items(): + val.sort(reverse=True) + + return sdk_map + + @classmethod + def _sdk_10(cls, key, reg_version): + if key in cls.sdk_map_cache: + sdk_map = cls.sdk_map_cache[key] + else: + sdk_map = cls._sdk_10_layout(reg_version) + cls.sdk_map_cache[key] = sdk_map + return sdk_map + + @classmethod + def _sdk_81(cls, key, reg_version): + if key in cls.sdk_map_cache: + sdk_map = cls.sdk_map_cache[key] + else: + sdk_map = cls._sdk_81_layout(reg_version) + cls.sdk_map_cache[key] = sdk_map + return sdk_map + + @classmethod + def _combine_sdk_map_list(cls, sdk_map_list): + combined_sdk_map = cls._new_sdk_map() + for sdk_map in sdk_map_list: + for key, val in sdk_map.items(): + combined_sdk_map[key].extend(val) + return combined_sdk_map + + sdk_dispatch_map = None + + @classmethod + def _version_list_sdk_map(cls, version_list): + + if not cls.sdk_dispatch_map: + cls.sdk_dispatch_map = { + '10.0': (cls._sdk_10, '10.0'), + '8.1': (cls._sdk_81, '8.1'), + } + + sdk_map_list = [] + for version in version_list: + func, reg_version = cls.sdk_dispatch_map[version] + sdk_map = func(version, reg_version) + sdk_map_list.append(sdk_map) + + combined_sdk_map = cls._combine_sdk_map_list(sdk_map_list) + return combined_sdk_map + + @classmethod + def _sdk_map(cls, version_list): + key = tuple(version_list) + if key in cls.sdk_cache: + sdk_map = cls.sdk_cache[key] + else: + version_numlist = [float(v) for v in version_list] + version_numlist.sort(reverse=True) + key = tuple([str(v) for v in version_numlist]) + sdk_map = cls._version_list_sdk_map(key) + cls.sdk_cache[key] = sdk_map + return sdk_map + + @classmethod + def _get_sdk_version_list(cls, version_list, platform): + sdk_map = cls._sdk_map(version_list) + sdk_list = sdk_map.get(platform, []) + return sdk_list + +def get_sdk_versions(MSVC_VERSION=None, MSVC_UWP_APP=False): + + sdk_versions = [] + + if not MSVC_VERSION: + vcs = get_installed_vcs() + if not vcs: + return sdk_versions + MSVC_VERSION = vcs[0] + + verstr = get_msvc_version_numeric(MSVC_VERSION) + vs_def = _Const.MSVC_VERSION_EXTERNAL.get(verstr, None) + if not vs_def: + return sdk_versions + + is_uwp = True if MSVC_UWP_APP in _Const.BOOLEAN_KEYS[True] else False + platform = 'uwp' if is_uwp else 'desktop' + sdk_list = _WindowsSDK._get_sdk_version_list(vs_def.vc_sdk_versions, platform) + + sdk_versions.extend(sdk_list) + return sdk_versions + +class _ScriptArguments: + + # TODO: verify SDK 10 version folder names 10.0.XXXXX.0 {1,3} last? + re_sdk_version_100 = re.compile(r'^10[.][0-9][.][0-9]{5}[.][0-9]{1}$') + re_sdk_version_81 = re.compile(r'^8[.]1$') + + re_sdk_dispatch_map = { + '10.0': re_sdk_version_100, + '8.1': re_sdk_version_81, + } + + # capture msvc version + re_toolset_version = re.compile(r'^(?P<version>[1-9][0-9]?[.][0-9])[0-9.]*$', re.IGNORECASE) + + re_toolset_full = re.compile(r'''^(?: + (?:[1-9][0-9][.][0-9]{1,2})| # XX.Y - XX.YY + (?:[1-9][0-9][.][0-9]{2}[.][0-9]{1,5}) # XX.YY.Z - XX.YY.ZZZZZ + )$''', re.VERBOSE) + + re_toolset_140 = re.compile(r'''^(?: + (?:14[.]0{1,2})| # 14.0 - 14.00 + (?:14[.]0{2}[.]0{1,5}) # 14.00.0 - 14.00.00000 + )$''', re.VERBOSE) + + # valid SxS formats will be matched with re_toolset_full: match 3 '.' format + re_toolset_sxs = re.compile(r'^[1-9][0-9][.][0-9]{2}[.][0-9]{2}[.][0-9]{1,2}$') + + # MSVC_SCRIPT_ARGS + re_vcvars_uwp = re.compile(r'(?:(?<!\S)|^)(?P<uwp>(?:uwp|store))(?:(?!\S)|$)',re.IGNORECASE) + re_vcvars_sdk = re.compile(r'(?:(?<!\S)|^)(?P<sdk>(?:[1-9][0-9]*[.]\S*))(?:(?!\S)|$)',re.IGNORECASE) + re_vcvars_toolset = re.compile(r'(?:(?<!\S)|^)(?P<toolset_arg>(?:[-]{1,2}|[/])vcvars_ver[=](?P<toolset>\S*))(?:(?!\S)|$)', re.IGNORECASE) + re_vcvars_spectre = re.compile(r'(?:(?<!\S)|^)(?P<spectre_arg>(?:[-]{1,2}|[/])vcvars_spectre_libs[=](?P<spectre>\S*))(?:(?!\S)|$)',re.IGNORECASE) + + # Force default sdk argument + MSVC_FORCE_DEFAULT_SDK = False + + # Force default toolset argument + MSVC_FORCE_DEFAULT_TOOLSET = False # MSVC batch file arguments: # @@ -1681,7 +2254,7 @@ class _MSVCScriptArguments: if not uwp_app: return None - if uwp_app not in (True, '1'): + if uwp_app not in _Const.BOOLEAN_KEYS[True]: return None if msvc.vs_def.vc_buildtools_def.vc_version_numeric < cls.VS2015.vc_buildtools_def.vc_version_numeric: @@ -1704,8 +2277,29 @@ class _MSVCScriptArguments: return uwp_arg - # TODO: verify SDK 10 version folder names 10.0.XXXXX.0 {1,3} last? - re_sdk_version_10 = re.compile(r'^10[.][0-9][.][0-9]{5}[.][0-9]{1}$') + @classmethod + def _user_script_argument_uwp(cls, env, uwp, user_argstr): + + matches = [m for m in cls.re_vcvars_uwp.finditer(user_argstr)] + if not matches: + return None + + if len(matches) > 1: + debug('multiple uwp declarations: MSVC_SCRIPT_ARGS=%s', repr(user_argstr)) + err_msg = "multiple uwp declarations: MSVC_SCRIPT_ARGS={}".format(repr(user_argstr)) + raise MSVCArgumentError(err_msg) + + if not uwp: + return None + + env_argstr = env.get('MSVC_UWP_APP','') + debug('multiple uwp declarations: MSVC_UWP_APP=%s, MSVC_SCRIPT_ARGS=%s', repr(env_argstr), repr(user_argstr)) + + err_msg = "multiple uwp declarations: MSVC_UWP_APP={} and MSVC_SCRIPT_ARGS={}".format( + repr(env_argstr), repr(user_argstr) + ) + + raise MSVCArgumentError(err_msg) @classmethod def _msvc_script_argument_sdk_constraints(cls, msvc, sdk_version): @@ -1721,27 +2315,23 @@ class _MSVCScriptArguments: ) return err_msg - # TODO: check sdk against vs_def/vc_buildtools_def - - if sdk_version == '8.1': - debug('valid: sdk_version=%s', repr(sdk_version)) - return None - - if cls.re_sdk_version_10.match(sdk_version): - debug('valid: sdk_version=%s', repr(sdk_version)) - return None + for msvc_sdk_version in msvc.vs_def.vc_sdk_versions: + re_sdk_version = cls.re_sdk_dispatch_map[msvc_sdk_version] + if re_sdk_version.match(sdk_version): + debug('valid: sdk_version=%s', repr(sdk_version)) + return None debug('invalid: method exit: sdk_version=%s', repr(sdk_version)) err_msg = "MSVC_SDK_VERSION ({}) is not supported".format(repr(sdk_version)) return err_msg @classmethod - def _msvc_script_argument_sdk(cls, env, msvc, is_uwp, arglist): + def _msvc_script_argument_sdk(cls, env, msvc, platform, arglist): sdk_version = env['MSVC_SDK_VERSION'] debug( - 'MSVC_VERSION=%s, MSVC_SDK_VERSION=%s, uwp=%s', - repr(msvc.version), repr(sdk_version), repr(is_uwp) + 'MSVC_VERSION=%s, MSVC_SDK_VERSION=%s, platform=%s', + repr(msvc.version), repr(sdk_version), repr(platform) ) if not sdk_version: @@ -1751,6 +2341,14 @@ class _MSVCScriptArguments: if err_msg: raise MSVCArgumentError(err_msg) + sdk_list = _WindowsSDK._get_sdk_version_list(msvc.vs_def.vc_sdk_versions, platform) + + if sdk_version not in sdk_list: + err_msg = "MSVC_SDK_VERSION {} not found for platform {}".format( + repr(sdk_version), repr(platform) + ) + raise MSVCArgumentError(err_msg) + # sdk folder may not exist argpair = (cls.SortOrder.SDK, sdk_version) arglist.append(argpair) @@ -1758,6 +2356,53 @@ class _MSVCScriptArguments: return sdk_version @classmethod + def _msvc_script_default_sdk(cls, env, msvc, platform, arglist): + + if msvc.vs_def.vc_buildtools_def.vc_version_numeric < cls.VS2015.vc_buildtools_def.vc_version_numeric: + return None + + sdk_list = _WindowsSDK._get_sdk_version_list(msvc.vs_def.vc_sdk_versions, platform) + if not len(sdk_list): + return None + + sdk_default = sdk_list[0] + + debug( + 'MSVC_VERSION=%s, sdk_default=%s, platform=%s', + repr(msvc.version), repr(sdk_default), repr(platform) + ) + + argpair = (cls.SortOrder.SDK, sdk_default) + arglist.append(argpair) + + return sdk_default + + @classmethod + def _user_script_argument_sdk(cls, env, sdk_version, user_argstr): + + matches = [m for m in cls.re_vcvars_sdk.finditer(user_argstr)] + if not matches: + return None + + if len(matches) > 1: + debug('multiple sdk version declarations: MSVC_SCRIPT_ARGS=%s', repr(user_argstr)) + err_msg = "multiple sdk version declarations: MSVC_SCRIPT_ARGS={}".format(repr(user_argstr)) + raise MSVCArgumentError(err_msg) + + if not sdk_version: + user_sdk = matches[0].group('sdk') + return user_sdk + + env_argstr = env.get('MSVC_SDK_VERSION','') + debug('multiple sdk version declarations: MSVC_SDK_VERSION=%s, MSVC_SCRIPT_ARGS=%s', repr(env_argstr), repr(user_argstr)) + + err_msg = "multiple sdk version declarations: MSVC_SDK_VERSION={} and MSVC_SCRIPT_ARGS={}".format( + repr(env_argstr), repr(user_argstr) + ) + + raise MSVCArgumentError(err_msg) + + @classmethod def _msvc_read_toolset_file(cls, msvc, filename): toolset_version = None try: @@ -1904,22 +2549,6 @@ class _MSVCScriptArguments: return None - # capture msvc version - re_toolset_version = re.compile(r'^(?P<version>[1-9][0-9]?[.][0-9])[0-9.]*$', re.IGNORECASE) - - re_toolset_full = re.compile(r'''^(?: - (?:[1-9][0-9][.][0-9]{1,2})| # XX.Y - XX.YY - (?:[1-9][0-9][.][0-9]{2}[.][0-9]{1,5}) # XX.YY.Z - XX.YY.ZZZZZ - )$''', re.VERBOSE) - - re_toolset_140 = re.compile(r'''^(?: - (?:14[.]0{1,2})| # 14.0 - 14.00 - (?:14[.]0{2}[.]0{1,5}) # 14.00.0 - 14.00.00000 - )$''', re.VERBOSE) - - # valid SxS formats will be matched with re_toolset_full: match 3 '.' format - re_toolset_sxs = re.compile(r'^[1-9][0-9][.][0-9]{2}[.][0-9]{2}[.][0-9]{1,2}$') - @classmethod def _msvc_script_argument_toolset_constraints(cls, msvc, toolset_version): @@ -2047,6 +2676,31 @@ class _MSVCScriptArguments: return toolset_default @classmethod + def _user_script_argument_toolset(cls, env, toolset_version, user_argstr): + + matches = [m for m in cls.re_vcvars_toolset.finditer(user_argstr)] + if not matches: + return None + + if len(matches) > 1: + debug('multiple toolset version declarations: MSVC_SCRIPT_ARGS=%s', repr(user_argstr)) + err_msg = "multiple toolset version declarations: MSVC_SCRIPT_ARGS={}".format(repr(user_argstr)) + raise MSVCArgumentError(err_msg) + + if not toolset_version: + user_toolset = matches[0].group('toolset') + return user_toolset + + env_argstr = env.get('MSVC_TOOLSET_VERSION','') + debug('multiple toolset version declarations: MSVC_TOOLSET_VERSION=%s, MSVC_SCRIPT_ARGS=%s', repr(env_argstr), repr(user_argstr)) + + err_msg = "multiple toolset version declarations: MSVC_TOOLSET_VERSION={} and MSVC_SCRIPT_ARGS={}".format( + repr(env_argstr), repr(user_argstr) + ) + + raise MSVCArgumentError(err_msg) + + @classmethod def _msvc_script_argument_spectre(cls, env, msvc, arglist): spectre_libs = env['MSVC_SPECTRE_LIBS'] @@ -2104,79 +2758,21 @@ class _MSVCScriptArguments: return script_args - re_vcvars_uwp = re.compile(r'(?:\s|^)(?P<uwp>(?:uwp|store))(?:\s|$)',re.IGNORECASE) - re_vcvars_sdk = re.compile(r'(?:\s|^)(?P<sdk>(?:[1-9][0-9]*[.]\S*))(?:\s|$)',re.IGNORECASE) - re_vcvars_spectre = re.compile(r'(?:\s|^)(?P<spectre_arg>(?:[-]{1,2}|[/])vcvars_spectre_libs[=](?P<spectre>\S*))(?:\s|$)',re.IGNORECASE) - re_vcvars_toolset = re.compile(r'(?:\s|^)(?P<toolset_arg>(?:[-]{1,2}|[/])vcvars_ver[=](?P<toolset>\S*))(?:\s|$)', re.IGNORECASE) - - @classmethod - def _user_script_argument_uwp(cls, env, uwp, user_argstr): - - if not uwp: - return None - - m = cls.re_vcvars_uwp.search(user_argstr) - if not m: - return None - - env_argstr = env.get('MSVC_UWP_APP','') - debug('multiple uwp declarations: MSVC_UWP_APP=%s, MSVC_SCRIPT_ARGS=%s', repr(env_argstr), repr(user_argstr)) - - err_msg = "multiple uwp declarations: MSVC_UWP_APP={} and MSVC_SCRIPT_ARGS={}".format( - repr(env_argstr), repr(user_argstr) - ) - - raise MSVCArgumentError(err_msg) - - @classmethod - def _user_script_argument_sdk(cls, env, sdk_version, user_argstr): - - if not sdk_version: - return None - - m = cls.re_vcvars_sdk.search(user_argstr) - if not m: - return None - - env_argstr = env.get('MSVC_SDK_VERSION','') - debug('multiple sdk version declarations: MSVC_SDK_VERSION=%s, MSVC_SCRIPT_ARGS=%s', repr(env_argstr), repr(user_argstr)) - - err_msg = "multiple sdk version declarations: MSVC_SDK_VERSION={} and MSVC_SCRIPT_ARGS={}".format( - repr(env_argstr), repr(user_argstr) - ) - - raise MSVCArgumentError(err_msg) - @classmethod - def _user_script_argument_toolset(cls, env, toolset_version, user_argstr): + def _user_script_argument_spectre(cls, env, spectre, user_argstr): - m = cls.re_vcvars_toolset.search(user_argstr) - if not m: + matches = [m for m in cls.re_vcvars_spectre.finditer(user_argstr)] + if not matches: return None - if not toolset_version: - user_toolset = m.group('toolset') - return user_toolset - - env_argstr = env.get('MSVC_TOOLSET_VERSION','') - debug('multiple toolset version declarations: MSVC_TOOLSET_VERSION=%s, MSVC_SCRIPT_ARGS=%s', repr(env_argstr), repr(user_argstr)) - - err_msg = "multiple toolset version declarations: MSVC_TOOLSET_VERSION={} and MSVC_SCRIPT_ARGS={}".format( - repr(env_argstr), repr(user_argstr) - ) - - raise MSVCArgumentError(err_msg) - - @classmethod - def _user_script_argument_spectre(cls, env, spectre, user_argstr): + if len(matches) > 1: + debug('multiple spectre declarations: MSVC_SCRIPT_ARGS=%s', repr(user_argstr)) + err_msg = "multiple spectre declarations: MSVC_SCRIPT_ARGS={}".format(repr(user_argstr)) + raise MSVCArgumentError(err_msg) if not spectre: return None - m = cls.re_vcvars_spectre.search(user_argstr) - if not m: - return None - env_argstr = env.get('MSVC_SPECTRE_LIBS','') debug('multiple spectre declarations: MSVC_SPECTRE_LIBS=%s, MSVC_SCRIPT_ARGS=%s', repr(env_argstr), repr(user_argstr)) @@ -2189,55 +2785,70 @@ class _MSVCScriptArguments: @classmethod def msvc_script_arguments(cls, env, version, vc_dir, arg): - msvc = cls._msvc_version(version) - - argstr = '' arglist = [] + msvc = cls._msvc_version(version) + if arg: argpair = (cls.SortOrder.ARCH, arg) arglist.append(argpair) - user_argstr = None - user_toolset = None - - uwp = None - sdk_version = None - toolset_version = None - spectre = None - if 'MSVC_SCRIPT_ARGS' in env: user_argstr = cls._msvc_script_argument_user(env, msvc, arglist) + else: + user_argstr = None if 'MSVC_UWP_APP' in env: uwp = cls._msvc_script_argument_uwp(env, msvc, arglist) - if uwp and user_argstr: - cls._user_script_argument_uwp(env, uwp, user_argstr) + else: + uwp = None + + if user_argstr: + cls._user_script_argument_uwp(env, uwp, user_argstr) + + platform = 'uwp' if uwp else 'desktop' if 'MSVC_SDK_VERSION' in env: - is_uwp = True if uwp else False - sdk_version = cls._msvc_script_argument_sdk(env, msvc, is_uwp, arglist) - if sdk_version and user_argstr: - cls._user_script_argument_sdk(env, sdk_version, user_argstr) + sdk_version = cls._msvc_script_argument_sdk(env, msvc, platform, arglist) + else: + sdk_version = None + + if user_argstr: + user_sdk = cls._user_script_argument_sdk(env, sdk_version, user_argstr) + else: + user_sdk = None + + if cls.MSVC_FORCE_DEFAULT_SDK: + if not sdk_version and not user_sdk: + sdk_version = cls._msvc_script_default_sdk(env, msvc, platform, arglist) if 'MSVC_TOOLSET_VERSION' in env: toolset_version = cls._msvc_script_argument_toolset(env, msvc, vc_dir, arglist) + else: + toolset_version = None if user_argstr: user_toolset = cls._user_script_argument_toolset(env, toolset_version, user_argstr) + else: + user_toolset = None - if cls.MSVC_TOOLSET_DEFAULT_VCVARSVER: + if cls.MSVC_FORCE_DEFAULT_TOOLSET: if not toolset_version and not user_toolset: toolset_version = cls._msvc_script_default_toolset(env, msvc, vc_dir, arglist) if 'MSVC_SPECTRE_LIBS' in env: spectre = cls._msvc_script_argument_spectre(env, msvc, arglist) - if spectre and user_argstr: - cls._user_script_argument_spectre(env, spectre, user_argstr) + else: + spectre = None + + if user_argstr: + cls._user_script_argument_spectre(env, spectre, user_argstr) if arglist: arglist.sort() argstr = ' '.join([argpair[-1] for argpair in arglist]).strip() + else: + argstr = '' debug('arguments: %s', repr(argstr)) return argstr @@ -2247,231 +2858,3 @@ class _MSVCScriptArguments: debug('reset') cls._reset_toolsets() -def msvc_find_valid_batch_script(env, version): - """Find and execute appropriate batch script to set up build env. - - The MSVC build environment depends heavily on having the shell - environment set. SCons does not inherit that, and does not count - on that being set up correctly anyway, so it tries to find the right - MSVC batch script, or the right arguments to the generic batch script - vcvarsall.bat, and run that, so we have a valid environment to build in. - There are dragons here: the batch scripts don't fail (see comments - elsewhere), they just leave you with a bad setup, so try hard to - get it right. - """ - - # 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 - - d = None - version_installed = False - for host_arch, target_arch, in host_target_list: - # Set to current arch. - env['TARGET_ARCH'] = target_arch - arg = '' - - # Try to locate a batch file for this host/target platform combo - try: - (vc_script, arg, vc_dir, sdk_script) = find_batch_file(env, version, host_arch, target_arch) - debug('vc_script:%s vc_script_arg:%s sdk_script:%s', vc_script, arg, sdk_script) - version_installed = True - except VisualCException as e: - msg = str(e) - debug('Caught exception while looking for batch file (%s)', msg) - version_installed = False - continue - - # Try to use the located batch file for this host/target platform combo - debug('use_script 2 %s, args:%s', repr(vc_script), arg) - found = None - if vc_script: - arg = _MSVCScriptArguments.msvc_script_arguments(env, version, vc_dir, arg) - try: - d = script_env(vc_script, args=arg) - found = vc_script - except BatchFileExecutionError as e: - debug('use_script 3: failed running VC script %s: %s: Error:%s', repr(vc_script), arg, e) - vc_script=None - continue - if not vc_script and sdk_script: - debug('use_script 4: trying sdk script: %s', sdk_script) - try: - d = script_env(sdk_script) - found = sdk_script - except BatchFileExecutionError as e: - debug('use_script 5: failed running SDK script %s: Error:%s', repr(sdk_script), e) - continue - elif not vc_script and not sdk_script: - debug('use_script 6: Neither VC script nor SDK script found') - continue - - debug("Found a working script/target: %s/%s", repr(found), 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: - env['TARGET_ARCH'] = target_platform - - if version_installed: - msg = "MSVC version '{}' working host/target script was not found.\n" \ - " Host = '{}', Target = '{}'\n" \ - " Visual Studio C/C++ compilers may not be set correctly".format( - version, host_platform, target_platform - ) - else: - installed_vcs = get_installed_vcs(env) - if installed_vcs: - msg = "MSVC version '{}' was not found.\n" \ - " Visual Studio C/C++ compilers may not be set correctly.\n" \ - " Installed versions are: {}".format(version, installed_vcs) - else: - msg = "MSVC version '{}' was not found.\n" \ - " No versions of the MSVC compiler were found.\n" \ - " Visual Studio C/C++ compilers may not be set correctly".format(version) - - _msvc_notfound_policy_handler(env, msg) - - return d - -_undefined = None - -def get_use_script_use_settings(env): - global _undefined - - if _undefined is None: - _undefined = object() - - # 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 - - # None (documentation) or evaluates False (code): bypass detection - # need to distinguish between undefined and None - use_script = env.get('MSVC_USE_SCRIPT', _undefined) - - if use_script != _undefined: - # use_script defined, use_settings ignored (not type checked) - return (use_script, None) - - # undefined or None: use_settings ignored - use_settings = env.get('MSVC_USE_SETTINGS', None) - - if use_settings is not None: - # use script undefined, use_settings defined and not None (type checked) - return (False, use_settings) - - # use script undefined, use_settings undefined or None - return (True, None) - -def msvc_setup_env(env): - debug('called') - version = get_default_version(env) - if version is None: - if not msvc_setup_env_user(env): - _MSVCSetupEnvDefault.set_nodefault() - return None - - # XXX: we set-up both MSVS version for backward - # compatibility with the msvs tool - env['MSVC_VERSION'] = version - env['MSVS_VERSION'] = version - env['MSVS'] = {} - - use_script, use_settings = get_use_script_use_settings(env) - if SCons.Util.is_String(use_script): - use_script = use_script.strip() - if not os.path.exists(use_script): - raise MSVCScriptNotFound('Script specified by MSVC_USE_SCRIPT not found: "{}"'.format(use_script)) - args = env.subst('$MSVC_USE_SCRIPT_ARGS') - debug('use_script 1 %s %s', repr(use_script), repr(args)) - d = script_env(use_script, args) - elif use_script: - d = msvc_find_valid_batch_script(env,version) - debug('use_script 2 %s', d) - if not d: - return d - elif use_settings is not None: - if not SCons.Util.is_Dict(use_settings): - error_msg = 'MSVC_USE_SETTINGS type error: expected a dictionary, found {}'.format(type(use_settings).__name__) - 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." - SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg) - return None - - for k, v in d.items(): - 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) - if CONFIG_CACHE: - propose = "SCONS_CACHE_MSVC_CONFIG caching enabled, remove cache file {} if out of date.".format(CONFIG_CACHE) - else: - propose = "It may need to be installed separately with Visual Studio." - warn_msg = "Could not find MSVC compiler 'cl'. {}".format(propose) - SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg) - -def msvc_exists(env=None, version=None): - debug('version=%s', repr(version)) - vcs = get_installed_vcs(env) - if version is None: - rval = len(vcs) > 0 - else: - rval = version in vcs - debug('version=%s, return=%s', repr(version), rval) - return rval - -def msvc_setup_env_user(env=None): - rval = False - if env: - - # Intent is to use msvc tools: - # MSVC_VERSION or MSVS_VERSION: defined and is True - # MSVC_USE_SCRIPT: defined and (is string or is False) - # MSVC_USE_SETTINGS: defined and is not None - - # defined and is True - for key in ['MSVC_VERSION', 'MSVS_VERSION']: - if key in env and env[key]: - rval = True - debug('key=%s, return=%s', repr(key), rval) - return rval - - # defined and (is string or is False) - for key in ['MSVC_USE_SCRIPT']: - if key in env and (SCons.Util.is_String(env[key]) or not env[key]): - rval = True - debug('key=%s, return=%s', repr(key), rval) - return rval - - # defined and is not None - for key in ['MSVC_USE_SETTINGS']: - if key in env and env[key] is not None: - rval = True - debug('key=%s, return=%s', repr(key), rval) - return rval - - debug('return=%s', rval) - return rval - -def msvc_setup_env_tool(env=None, version=None, tool=None): - debug('tool=%s, version=%s', repr(tool), repr(version)) - _MSVCSetupEnvDefault.register_tool(env, tool) - rval = False - if not rval and msvc_exists(env, version): - rval = True - if not rval and msvc_setup_env_user(env): - rval = True - debug('tool=%s, version=%s, return=%s', repr(tool), repr(version), rval) - return rval - |