From 8cf60974f5b08e80004b843189747f5d0d226d42 Mon Sep 17 00:00:00 2001 From: Joseph Brill <48932340+jcbrill@users.noreply.github.com> Date: Mon, 4 Jul 2022 23:10:30 -0400 Subject: Add msvc script error global policy and construction variable. Move msvc not found policy and msvc script error policy to Policy.py. Rework vcvars bugfix handling for SxS toolset 14.28. Add method to return msvc toolsets. Add experimental function to return msvc version and msvc toolset version given a version specification (proxy for selection). Add API.py to manage symbols imported in vc.py. Update documentation. --- CHANGES.txt | 30 ++- RELEASE.txt | 30 ++- SCons/Tool/MSCommon/MSVC/API.py | 38 ++++ SCons/Tool/MSCommon/MSVC/Config.py | 98 ++++----- SCons/Tool/MSCommon/MSVC/Exceptions.py | 14 ++ SCons/Tool/MSCommon/MSVC/NotFound.py | 133 ------------ SCons/Tool/MSCommon/MSVC/Policy.py | 301 ++++++++++++++++++++++++++++ SCons/Tool/MSCommon/MSVC/ScriptArguments.py | 173 ++++++++++------ SCons/Tool/MSCommon/MSVC/Util.py | 46 ++++- SCons/Tool/MSCommon/MSVC/Warnings.py | 44 ++++ SCons/Tool/MSCommon/MSVC/__init__.py | 16 +- SCons/Tool/MSCommon/vc.py | 224 ++++++++++++++++++--- SCons/Tool/msvc.xml | 125 ++++++++++++ doc/generated/variables.gen | 126 ++++++++++++ doc/generated/variables.mod | 2 + 15 files changed, 1112 insertions(+), 288 deletions(-) create mode 100644 SCons/Tool/MSCommon/MSVC/API.py delete mode 100644 SCons/Tool/MSCommon/MSVC/NotFound.py create mode 100644 SCons/Tool/MSCommon/MSVC/Policy.py create mode 100644 SCons/Tool/MSCommon/MSVC/Warnings.py diff --git a/CHANGES.txt b/CHANGES.txt index 381b061..a4cf13a 100755 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -76,19 +76,39 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER present, there is no easy way to detect during msvc initialization if the default environment will be used later to build a program and/or library. There is no error/warning issued for the default tools as there are legitimate SCons uses that do not require a c compiler. - - Added a global policy setting and an environment policy variable for specifying the action to - be taken when an msvc request cannot be satisfied. The available options are "error", + - Added a global policy setting and an environment construction variable for specifying the + action to be taken when an msvc request cannot be satisfied. The available options are "error", "exception", "warning", "warn", "ignore", and "suppress". The global policy variable may be set and retrieved via the functions set_msvc_notfound_policy and get_msvc_notfound_policy, - respectively. These two methods may be imported from SCons.Tool.MSCommon. The environment - policy variable introduced is MSVC_NOTFOUND_POLICY. When defined, the environment policy + respectively. These two methods may be imported from SCons.Tool.MSCommon.vc. The environment + construction variable is MSVC_NOTFOUND_POLICY. When defined, the environment construction variable overrides the global policy setting for a given environment. When the active policy is "error" or "exception", an MSVCVersionNotFound exception is raised. When the active policy - is "warning" or "warn", a VisualCMissingWarning warning is issued and the constructed + is "warning" or "warn", an MSVCVersionNotFoundWarning warning is issued and the constructed environment is likely incomplete. When the active policy is "ignore" or "suppress", no action is taken and the constructed environment is likely incomplete. As implemented, the default global policy is "warning". The ability to set the global policy via an SCons command-line option may be added in a future enhancement. + - Added a global policy setting and an environment construction variable for specifying the + action to be taken when msvc script errors are detected. The available options are "error", + "exception", "warning", "warn", "ignore", and "suppress". The global policy variable may be + set and retrieved via the functions set_msvc_scripterror_policy and get_msvc_scripterror_policy, + respectively. These two methods may be imported from SCons.Tool.MSCommon.vc. The environment + construction variable is MSVC_SCRIPTERROR_POLICY. When defined, the environment construction + variable overrides the global policy setting for a given environment. When the active policy + is "error" or "exception", an MSVCScriptExecutionError exception is raised when msvc batch file + errors are detected. When the active policy is "warning" or "warn", an MSVCScriptExecutionWarning + warning is issued when msvc batch file errors are detected. When the active policy is "ignore" or + "suppress", msvc batch error messages are suppressed. As implemented, the default global policy + is "ignore". The ability to set the global policy via an SCons command-line option may be added + in a future enhancement. + - Experimental: added function find_msvc_version_toolset to SCons.Tool.MSCommon.vc. Given a version + specification, this function will return a msvc version and a toolset version. The toolset version + may be None. The msvc version and toolset version can be used in the environment construction + variables MSVC_VERSION and MSVC_TOOLSET_VERSION, respectively. The version specification may be an + msvc version or an msvc toolset version. This is an experimental proxy for using a toolset version + to perform instance selection. This function may be removed when toolset version is taken into + account during msvc instance selection. From William Deegan: - Fix check for unsupported Python version. It was broken. Also now the error message diff --git a/RELEASE.txt b/RELEASE.txt index 12dec62..d7a2690 100755 --- a/RELEASE.txt +++ b/RELEASE.txt @@ -45,19 +45,39 @@ NEW FUNCTIONALITY NOTE: Test for this requires python psutil module. It will be skipped if not present. - Ninja: Added command line variable NINJA_CMD_ARGS that allows to pass through ninja command line args. This can also be set in your Environment(). -- Added a global policy setting and an environment policy variable for specifying the action to - be taken when an msvc request cannot be satisfied. The available options are "error", +- Added a global policy setting and an environment construction variable for specifying the + action to be taken when an msvc request cannot be satisfied. The available options are "error", "exception", "warning", "warn", "ignore", and "suppress". The global policy variable may be set and retrieved via the functions set_msvc_notfound_policy and get_msvc_notfound_policy, - respectively. These two methods may be imported from SCons.Tool.MSCommon. The environment - policy variable introduced is MSVC_NOTFOUND_POLICY. When defined, the environment policy + respectively. These two methods may be imported from SCons.Tool.MSCommon.vc. The environment + construction variable is MSVC_NOTFOUND_POLICY. When defined, the environment construction variable overrides the global policy setting for a given environment. When the active policy is "error" or "exception", an MSVCVersionNotFound exception is raised. When the active policy - is "warning" or "warn", a VisualCMissingWarning warning is issued and the constructed + is "warning" or "warn", an MSVCVersionNotFoundWarning warning is issued and the constructed environment is likely incomplete. When the active policy is "ignore" or "suppress", no action is taken and the constructed environment is likely incomplete. As implemented, the default global policy is "warning". The ability to set the global policy via an SCons command-line option may be added in a future enhancement. +- Added a global policy setting and an environment construction variable for specifying the + action to be taken when msvc script errors are detected. The available options are "error", + "exception", "warning", "warn", "ignore", and "suppress". The global policy variable may be + set and retrieved via the functions set_msvc_scripterror_policy and get_msvc_scripterror_policy, + respectively. These two methods may be imported from SCons.Tool.MSCommon.vc. The environment + construction variable is MSVC_SCRIPTERROR_POLICY. When defined, the environment construction + variable overrides the global policy setting for a given environment. When the active policy + is "error" or "exception", an MSVCScriptExecutionError exception is raised when msvc batch file + errors are detected. When the active policy is "warning" or "warn", an MSVCScriptExecutionWarning + warning is issued when msvc batch file errors are detected. When the active policy is "ignore" or + "suppress", msvc batch error messages are suppressed. As implemented, the default global policy + is "ignore". The ability to set the global policy via an SCons command-line option may be added + in a future enhancement. +- Experimental: added function find_msvc_version_toolset to SCons.Tool.MSCommon.vc. Given a version + specification, this function will return a msvc version and a toolset version. The toolset version + may be None. The msvc version and toolset version can be used in the environment construction + variables MSVC_VERSION and MSVC_TOOLSET_VERSION, respectively. The version specification may be an + msvc version or an msvc toolset version. This is an experimental proxy for using a toolset version + to perform instance selection. This function may be removed when toolset version is taken into + account during msvc instance selection. - Fortran: a new construction variable FORTRANCOMMONFLAGS is added which is applied to all Fortran dialects, to enable global (all-dialect) settings. diff --git a/SCons/Tool/MSCommon/MSVC/API.py b/SCons/Tool/MSCommon/MSVC/API.py new file mode 100644 index 0000000..cfa4195 --- /dev/null +++ b/SCons/Tool/MSCommon/MSVC/API.py @@ -0,0 +1,38 @@ +# 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. + +""" +Public API for Microsoft Visual C/C++. +""" + +from .Exceptions import * # noqa: F401 +from .Warnings import * # noqa: F401 + +from .Policy import set_msvc_notfound_policy # noqa: F401 +from .Policy import get_msvc_notfound_policy # noqa: F401 + +from .Policy import set_msvc_scripterror_policy # noqa: F401 +from .Policy import get_msvc_scripterror_policy # noqa: F401 + +from .Util import get_version_elements # noqa: F401 + diff --git a/SCons/Tool/MSCommon/MSVC/Config.py b/SCons/Tool/MSCommon/MSVC/Config.py index fa7dbc1..60e2910 100644 --- a/SCons/Tool/MSCommon/MSVC/Config.py +++ b/SCons/Tool/MSCommon/MSVC/Config.py @@ -128,6 +128,7 @@ MSVC_BUILDTOOLS_DEFINITION = namedtuple('MSVCBuildtools', [ 'cl_version', 'cl_version_numeric', 'vc_runtime_def', + 'vc_istoolset', ]) MSVC_BUILDTOOLS_DEFINITION_LIST = [] @@ -137,19 +138,19 @@ MSVC_BUILDTOOLS_EXTERNAL = {} VC_VERSION_MAP = {} -for vc_buildtools, vc_version, cl_version, vc_runtime in [ - ('v143', '14.3', '19.3', '140'), - ('v142', '14.2', '19.2', '140'), - ('v141', '14.1', '19.1', '140'), - ('v140', '14.0', '19.0', '140'), - ('v120', '12.0', '18.0', '120'), - ('v110', '11.0', '17.0', '110'), - ('v100', '10.0', '16.0', '100'), - ('v90', '9.0', '15.0', '90'), - ('v80', '8.0', '14.0', '80'), - ('v71', '7.1', '13.1', '71'), - ('v70', '7.0', '13.0', '70'), - ('v60', '6.0', '12.0', '60'), +for vc_buildtools, vc_version, cl_version, vc_runtime, vc_istoolset in [ + ('v143', '14.3', '19.3', '140', True), + ('v142', '14.2', '19.2', '140', True), + ('v141', '14.1', '19.1', '140', True), + ('v140', '14.0', '19.0', '140', True), + ('v120', '12.0', '18.0', '120', False), + ('v110', '11.0', '17.0', '110', False), + ('v100', '10.0', '16.0', '100', False), + ('v90', '9.0', '15.0', '90', False), + ('v80', '8.0', '14.0', '80', False), + ('v71', '7.1', '13.1', '71', False), + ('v70', '7.0', '13.0', '70', False), + ('v60', '6.0', '12.0', '60', False), ]: vc_runtime_def = MSVC_RUNTIME_INTERNAL[vc_runtime] @@ -162,6 +163,7 @@ for vc_buildtools, vc_version, cl_version, vc_runtime in [ cl_version = cl_version, cl_version_numeric = float(cl_version), vc_runtime_def = vc_runtime_def, + vc_istoolset = vc_istoolset, ) MSVC_BUILDTOOLS_DEFINITION_LIST.append(vc_buildtools_def) @@ -270,6 +272,43 @@ for vs_product, vs_version, vs_envvar, vs_express, vs_lookup, vc_sdk, vc_ucrt, v MSVC_SDK_VERSIONS.update(vc_sdk) +# EXPERIMENTAL: msvc version/toolset search lists +# +# VS2017 example: +# +# defaults['14.1'] = ['14.1', '14.1Exp'] +# defaults['14.1Exp'] = ['14.1Exp'] +# +# search['14.1'] = ['14.3', '14.2', '14.1', '14.1Exp'] +# search['14.1Exp'] = ['14.1Exp'] + +MSVC_VERSION_TOOLSET_DEFAULTS_MAP = {} +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: + continue + version_key = vs_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: + 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: + continue + version_key = vs_def.vc_buildtools_def.vc_version + for vc_buildtools in vs_def.vc_buildtools_all: + toolset_buildtools_def = MSVC_BUILDTOOLS_INTERNAL[vc_buildtools] + toolset_vs_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)] @@ -282,39 +321,6 @@ for vdict in (MSVS_VERSION_EXTERNAL, MSVC_VERSION_INTERNAL): MSVS_VERSION_LEGACY[key] = vs_def MSVC_VERSION_LEGACY[key] = vs_def -# MSVC_NOTFOUND_POLICY definition: -# error: raise exception -# warning: issue warning and continue -# ignore: continue - -MSVC_NOTFOUND_POLICY_DEFINITION = namedtuple('MSVCNotFoundPolicyDefinition', [ - 'value', - 'symbol', -]) - -MSVC_NOTFOUND_DEFINITION_LIST = [] - -MSVC_NOTFOUND_POLICY_INTERNAL = {} -MSVC_NOTFOUND_POLICY_EXTERNAL = {} - -for policy_value, policy_symbol_list in [ - (True, ['Error', 'Exception']), - (False, ['Warning', 'Warn']), - (None, ['Ignore', 'Suppress']), -]: - - policy_symbol = policy_symbol_list[0].lower() - policy_def = MSVC_NOTFOUND_POLICY_DEFINITION(policy_value, policy_symbol) - - MSVC_NOTFOUND_DEFINITION_LIST.append(vs_def) - - MSVC_NOTFOUND_POLICY_INTERNAL[policy_symbol] = policy_def - - for policy_symbol in policy_symbol_list: - MSVC_NOTFOUND_POLICY_EXTERNAL[policy_symbol.lower()] = policy_def - MSVC_NOTFOUND_POLICY_EXTERNAL[policy_symbol] = policy_def - MSVC_NOTFOUND_POLICY_EXTERNAL[policy_symbol.upper()] = policy_def - def verify(): from .. import vc for msvc_version in vc._VCVER: diff --git a/SCons/Tool/MSCommon/MSVC/Exceptions.py b/SCons/Tool/MSCommon/MSVC/Exceptions.py index 015fede..6a311d4 100644 --- a/SCons/Tool/MSCommon/MSVC/Exceptions.py +++ b/SCons/Tool/MSCommon/MSVC/Exceptions.py @@ -25,12 +25,26 @@ Exceptions for Microsoft Visual C/C++. """ +__all__ = [ + 'VisualCException', + 'MSVCInternalError', + 'MSVCScriptExecutionError', + 'MSVCVersionNotFound', + 'MSVCSDKVersionNotFound', + 'MSVCToolsetVersionNotFound', + 'MSVCSpectreLibsNotFound', + 'MSVCArgumentError', +] + class VisualCException(Exception): pass class MSVCInternalError(VisualCException): pass +class MSVCScriptExecutionError(VisualCException): + pass + class MSVCVersionNotFound(VisualCException): pass diff --git a/SCons/Tool/MSCommon/MSVC/NotFound.py b/SCons/Tool/MSCommon/MSVC/NotFound.py deleted file mode 100644 index 6ade285..0000000 --- a/SCons/Tool/MSCommon/MSVC/NotFound.py +++ /dev/null @@ -1,133 +0,0 @@ -# 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. - -""" -Microsoft Visual C/C++ not found policy. - -Notes: - * As implemented, the default is that a warning is issued. This can - be changed globally via the function set_msvc_notfound_policy and/or - through the environment via the MSVC_NOTFOUND_POLICY variable. -""" - -import SCons.Warnings - -from ..common import ( - debug, -) - -from . import Config - -from .Exceptions import ( - MSVCVersionNotFound, -) - -from . import Dispatcher -Dispatcher.register_modulename(__name__) - - -_MSVC_NOTFOUND_POLICY_DEF = Config.MSVC_NOTFOUND_POLICY_INTERNAL['warning'] - -def _msvc_notfound_policy_lookup(symbol): - - try: - notfound_policy_def = Config.MSVC_NOTFOUND_POLICY_EXTERNAL[symbol] - except KeyError: - err_msg = "Value specified for MSVC_NOTFOUND_POLICY is not supported: {}.\n" \ - " Valid values are: {}".format( - repr(symbol), - ', '.join([repr(s) for s in Config.MSVC_NOTFOUND_POLICY_EXTERNAL.keys()]) - ) - raise ValueError(err_msg) - - return notfound_policy_def - -def set_msvc_notfound_policy(MSVC_NOTFOUND_POLICY=None): - """ Set the default policy when MSVC is not found. - - Args: - MSVC_NOTFOUND_POLICY: - string representing the policy behavior - when MSVC is not found or None - - Returns: - The previous policy is returned when the MSVC_NOTFOUND_POLICY argument - is not None. The active policy is returned when the MSVC_NOTFOUND_POLICY - argument is None. - - """ - global _MSVC_NOTFOUND_POLICY_DEF - - prev_policy = _MSVC_NOTFOUND_POLICY_DEF.symbol - - policy = MSVC_NOTFOUND_POLICY - if policy is not None: - _MSVC_NOTFOUND_POLICY_DEF = _msvc_notfound_policy_lookup(policy) - - debug( - 'prev_policy=%s, set_policy=%s, policy.symbol=%s, policy.value=%s', - repr(prev_policy), repr(policy), - repr(_MSVC_NOTFOUND_POLICY_DEF.symbol), repr(_MSVC_NOTFOUND_POLICY_DEF.value) - ) - - return prev_policy - -def get_msvc_notfound_policy(): - """Return the active policy when MSVC is not found.""" - debug( - 'policy.symbol=%s, policy.value=%s', - repr(_MSVC_NOTFOUND_POLICY_DEF.symbol), repr(_MSVC_NOTFOUND_POLICY_DEF.value) - ) - return _MSVC_NOTFOUND_POLICY_DEF.symbol - -def policy_handler(env, msg): - - if env and 'MSVC_NOTFOUND_POLICY' in env: - # environment setting - notfound_policy_src = 'environment' - policy = env['MSVC_NOTFOUND_POLICY'] - if policy is not None: - # user policy request - notfound_policy_def = _msvc_notfound_policy_lookup(policy) - else: - # active global setting - notfound_policy_def = _MSVC_NOTFOUND_POLICY_DEF - else: - # active global setting - notfound_policy_src = 'default' - policy = None - notfound_policy_def = _MSVC_NOTFOUND_POLICY_DEF - - debug( - 'source=%s, set_policy=%s, policy.symbol=%s, policy.value=%s', - notfound_policy_src, repr(policy), repr(notfound_policy_def.symbol), repr(notfound_policy_def.value) - ) - - if notfound_policy_def.value is None: - # ignore - pass - elif notfound_policy_def.value: - raise MSVCVersionNotFound(msg) - else: - SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, msg) - diff --git a/SCons/Tool/MSCommon/MSVC/Policy.py b/SCons/Tool/MSCommon/MSVC/Policy.py new file mode 100644 index 0000000..870c73c --- /dev/null +++ b/SCons/Tool/MSCommon/MSVC/Policy.py @@ -0,0 +1,301 @@ +# 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. + +""" +Microsoft Visual C/C++ policy handlers. + +Notes: + * The default msvc not found policy is that a warning is issued. This can be + changed globally via the function set_msvc_notfound_policy and/or through + the environment via the MSVC_NOTFOUND_POLICY construction variable. + * The default msvc script error policy is to suppress all msvc batch file + error messages. This can be changed globally via the function + set_msvc_scripterror_policy and/or through the environment via the + MSVC_SCRIPTERROR_POLICY construction variable. +""" + +from collections import ( + namedtuple, +) + +import SCons.Warnings + +from ..common import ( + debug, +) + +from .Exceptions import ( + MSVCVersionNotFound, + MSVCScriptExecutionError, +) + +from .Warnings import ( + MSVCVersionNotFoundWarning, + MSVCScriptExecutionWarning, +) + +from . import Dispatcher +Dispatcher.register_modulename(__name__) + + +# MSVC_NOTFOUND_POLICY definition: +# error: raise exception +# warning: issue warning and continue +# ignore: continue + +MSVC_NOTFOUND_POLICY_DEFINITION = namedtuple('MSVCNotFoundPolicyDefinition', [ + 'value', + 'symbol', +]) + +MSVC_NOTFOUND_DEFINITION_LIST = [] + +MSVC_NOTFOUND_POLICY_INTERNAL = {} +MSVC_NOTFOUND_POLICY_EXTERNAL = {} + +for policy_value, policy_symbol_list in [ + (True, ['Error', 'Exception']), + (False, ['Warning', 'Warn']), + (None, ['Ignore', 'Suppress']), +]: + + policy_symbol = policy_symbol_list[0].lower() + policy_def = MSVC_NOTFOUND_POLICY_DEFINITION(policy_value, policy_symbol) + + MSVC_NOTFOUND_DEFINITION_LIST.append(policy_def) + + MSVC_NOTFOUND_POLICY_INTERNAL[policy_symbol] = policy_def + + for policy_symbol in policy_symbol_list: + MSVC_NOTFOUND_POLICY_EXTERNAL[policy_symbol.lower()] = policy_def + MSVC_NOTFOUND_POLICY_EXTERNAL[policy_symbol] = policy_def + MSVC_NOTFOUND_POLICY_EXTERNAL[policy_symbol.upper()] = policy_def + +# default definition +_MSVC_NOTFOUND_POLICY_DEF = MSVC_NOTFOUND_POLICY_INTERNAL['warning'] + + +# MSVC_SCRIPTERROR_POLICY definition: +# error: raise exception +# warning: issue warning and continue +# ignore: continue + +MSVC_SCRIPTERROR_POLICY_DEFINITION = namedtuple('MSVCBatchErrorPolicyDefinition', [ + 'value', + 'symbol', +]) + +MSVC_SCRIPTERROR_DEFINITION_LIST = [] + +MSVC_SCRIPTERROR_POLICY_INTERNAL = {} +MSVC_SCRIPTERROR_POLICY_EXTERNAL = {} + +for policy_value, policy_symbol_list in [ + (True, ['Error', 'Exception']), + (False, ['Warning', 'Warn']), + (None, ['Ignore', 'Suppress']), +]: + + policy_symbol = policy_symbol_list[0].lower() + policy_def = MSVC_SCRIPTERROR_POLICY_DEFINITION(policy_value, policy_symbol) + + MSVC_SCRIPTERROR_DEFINITION_LIST.append(policy_def) + + MSVC_SCRIPTERROR_POLICY_INTERNAL[policy_symbol] = policy_def + + for policy_symbol in policy_symbol_list: + MSVC_SCRIPTERROR_POLICY_EXTERNAL[policy_symbol.lower()] = policy_def + MSVC_SCRIPTERROR_POLICY_EXTERNAL[policy_symbol] = policy_def + MSVC_SCRIPTERROR_POLICY_EXTERNAL[policy_symbol.upper()] = policy_def + +# default definition +_MSVC_SCRIPTERROR_POLICY_DEF = MSVC_SCRIPTERROR_POLICY_INTERNAL['ignore'] + + +def _msvc_notfound_policy_lookup(symbol): + + try: + notfound_policy_def = MSVC_NOTFOUND_POLICY_EXTERNAL[symbol] + except KeyError: + err_msg = "Value specified for MSVC_NOTFOUND_POLICY is not supported: {}.\n" \ + " Valid values are: {}".format( + repr(symbol), + ', '.join([repr(s) for s in MSVC_NOTFOUND_POLICY_EXTERNAL.keys()]) + ) + raise ValueError(err_msg) + + return notfound_policy_def + +def set_msvc_notfound_policy(MSVC_NOTFOUND_POLICY=None): + """ Set the default policy when MSVC is not found. + + Args: + MSVC_NOTFOUND_POLICY: + string representing the policy behavior + when MSVC is not found or None + + Returns: + The previous policy is returned when the MSVC_NOTFOUND_POLICY argument + is not None. The active policy is returned when the MSVC_NOTFOUND_POLICY + argument is None. + + """ + global _MSVC_NOTFOUND_POLICY_DEF + + prev_policy = _MSVC_NOTFOUND_POLICY_DEF.symbol + + policy = MSVC_NOTFOUND_POLICY + if policy is not None: + _MSVC_NOTFOUND_POLICY_DEF = _msvc_notfound_policy_lookup(policy) + + debug( + 'prev_policy=%s, set_policy=%s, policy.symbol=%s, policy.value=%s', + repr(prev_policy), repr(policy), + repr(_MSVC_NOTFOUND_POLICY_DEF.symbol), repr(_MSVC_NOTFOUND_POLICY_DEF.value) + ) + + return prev_policy + +def get_msvc_notfound_policy(): + """Return the active policy when MSVC is not found.""" + debug( + 'policy.symbol=%s, policy.value=%s', + repr(_MSVC_NOTFOUND_POLICY_DEF.symbol), repr(_MSVC_NOTFOUND_POLICY_DEF.value) + ) + return _MSVC_NOTFOUND_POLICY_DEF.symbol + +def msvc_notfound_handler(env, msg): + + if env and 'MSVC_NOTFOUND_POLICY' in env: + # environment setting + notfound_policy_src = 'environment' + policy = env['MSVC_NOTFOUND_POLICY'] + if policy is not None: + # user policy request + notfound_policy_def = _msvc_notfound_policy_lookup(policy) + else: + # active global setting + notfound_policy_def = _MSVC_NOTFOUND_POLICY_DEF + else: + # active global setting + notfound_policy_src = 'default' + policy = None + notfound_policy_def = _MSVC_NOTFOUND_POLICY_DEF + + debug( + 'source=%s, set_policy=%s, policy.symbol=%s, policy.value=%s', + notfound_policy_src, repr(policy), repr(notfound_policy_def.symbol), repr(notfound_policy_def.value) + ) + + if notfound_policy_def.value is None: + # ignore + pass + elif notfound_policy_def.value: + raise MSVCVersionNotFound(msg) + else: + SCons.Warnings.warn(MSVCVersionNotFoundWarning, msg) + + +def _msvc_scripterror_policy_lookup(symbol): + + try: + scripterror_policy_def = MSVC_SCRIPTERROR_POLICY_EXTERNAL[symbol] + except KeyError: + err_msg = "Value specified for MSVC_SCRIPTERROR_POLICY is not supported: {}.\n" \ + " Valid values are: {}".format( + repr(symbol), + ', '.join([repr(s) for s in MSVC_SCRIPTERROR_POLICY_EXTERNAL.keys()]) + ) + raise ValueError(err_msg) + + return scripterror_policy_def + +def set_msvc_scripterror_policy(MSVC_SCRIPTERROR_POLICY=None): + """ Set the default policy when msvc batch file execution errors are detected. + + Args: + MSVC_SCRIPTERROR_POLICY: + string representing the policy behavior + when msvc batch file execution errors are detected or None + + Returns: + The previous policy is returned when the MSVC_SCRIPTERROR_POLICY argument + is not None. The active policy is returned when the MSVC_SCRIPTERROR_POLICY + argument is None. + + """ + global _MSVC_SCRIPTERROR_POLICY_DEF + + prev_policy = _MSVC_SCRIPTERROR_POLICY_DEF.symbol + + policy = MSVC_SCRIPTERROR_POLICY + if policy is not None: + _MSVC_SCRIPTERROR_POLICY_DEF = _msvc_scripterror_policy_lookup(policy) + + debug( + 'prev_policy=%s, set_policy=%s, policy.symbol=%s, policy.value=%s', + repr(prev_policy), repr(policy), + repr(_MSVC_SCRIPTERROR_POLICY_DEF.symbol), repr(_MSVC_SCRIPTERROR_POLICY_DEF.value) + ) + + return prev_policy + +def get_msvc_scripterror_policy(): + """Return the active policy when msvc batch file execution errors are detected.""" + debug( + 'policy.symbol=%s, policy.value=%s', + repr(_MSVC_SCRIPTERROR_POLICY_DEF.symbol), repr(_MSVC_SCRIPTERROR_POLICY_DEF.value) + ) + return _MSVC_SCRIPTERROR_POLICY_DEF.symbol + +def msvc_scripterror_handler(env, msg): + + if env and 'MSVC_SCRIPTERROR_POLICY' in env: + # environment setting + scripterror_policy_src = 'environment' + policy = env['MSVC_SCRIPTERROR_POLICY'] + if policy is not None: + # user policy request + scripterror_policy_def = _msvc_scripterror_policy_lookup(policy) + else: + # active global setting + scripterror_policy_def = _MSVC_SCRIPTERROR_POLICY_DEF + else: + # active global setting + scripterror_policy_src = 'default' + policy = None + scripterror_policy_def = _MSVC_SCRIPTERROR_POLICY_DEF + + debug( + 'source=%s, set_policy=%s, policy.symbol=%s, policy.value=%s', + scripterror_policy_src, repr(policy), repr(scripterror_policy_def.symbol), repr(scripterror_policy_def.value) + ) + + if scripterror_policy_def.value is None: + # ignore + pass + elif scripterror_policy_def.value: + raise MSVCScriptExecutionError(msg) + else: + SCons.Warnings.warn(MSVCScriptExecutionWarning, msg) + diff --git a/SCons/Tool/MSCommon/MSVC/ScriptArguments.py b/SCons/Tool/MSCommon/MSVC/ScriptArguments.py index 14c8c6c..d07b78f 100644 --- a/SCons/Tool/MSCommon/MSVC/ScriptArguments.py +++ b/SCons/Tool/MSCommon/MSVC/ScriptArguments.py @@ -94,7 +94,8 @@ re_toolset_140 = re.compile(r'''^(?: re_toolset_sxs = re.compile(r'^[1-9][0-9][.][0-9]{2}[.][0-9]{2}[.][0-9]{1,2}$') # SxS version bugfix -_msvc_sxs_bugfix_folder = set() +_msvc_sxs_bugfix_map = {} +_msvc_sxs_bugfix_folder = {} _msvc_sxs_bugfix_version = {} for msvc_version, sxs_version, sxs_bugfix in [ @@ -103,7 +104,8 @@ for msvc_version, sxs_version, sxs_bugfix in [ # if SxS version 14.28 not present/installed, fallback selection of toolset VC\Tools\MSVC\14.28.nnnnn. ('14.2', '14.28.16.8', '14.28') ]: - _msvc_sxs_bugfix_folder.add((msvc_version, sxs_bugfix)) + _msvc_sxs_bugfix_map.setdefault(msvc_version, []).append((sxs_version, sxs_bugfix)) + _msvc_sxs_bugfix_folder[(msvc_version, sxs_bugfix)] = sxs_version _msvc_sxs_bugfix_version[(msvc_version, sxs_version)] = sxs_bugfix # MSVC_SCRIPT_ARGS @@ -383,6 +385,24 @@ def _user_script_argument_sdk(env, sdk_version, user_argstr): raise MSVCArgumentError(err_msg) +_toolset_have140_cache = None + +def _msvc_have140_toolset(): + global _toolset_have140_cache + + if _toolset_have140_cache is None: + suffix = Registry.vstudio_sxs_vc7('14.0') + vcinstalldirs = [record[0] for record in Registry.microsoft_query_paths(suffix)] + debug('vc140 toolset: paths=%s', repr(vcinstalldirs)) + _toolset_have140_cache = True if vcinstalldirs else False + + return _toolset_have140_cache + +def _reset_have140_cache(): + global _toolset_have140_cache + debug('reset: cache') + _toolset_have140_cache = None + def _msvc_read_toolset_file(msvc, filename): toolset_version = None try: @@ -398,30 +418,18 @@ def _msvc_read_toolset_file(msvc, filename): debug('IndexError: msvc_version=%s, filename=%s', repr(msvc.version), repr(filename)) return toolset_version -def _msvc_sxs_toolset_folder(msvc, sxs_version): +def _msvc_sxs_toolset_folder(msvc, sxs_folder): - if re_toolset_sxs.match(sxs_version): - return sxs_version + if re_toolset_sxs.match(sxs_folder): + return sxs_folder, sxs_folder - key = (msvc.vs_def.vc_buildtools_def.vc_version, sxs_version) + key = (msvc.vs_def.vc_buildtools_def.vc_version, sxs_folder) if key in _msvc_sxs_bugfix_folder: - return sxs_version + sxs_version = _msvc_sxs_bugfix_folder[key] + return sxs_folder, sxs_version - debug('sxs folder: ignore version=%s', repr(sxs_version)) - return None - -def _msvc_sxs_toolset_version(msvc, sxs_toolset): - - if not re_toolset_sxs.match(sxs_toolset): - return None, False - - key = (msvc.vs_def.vc_buildtools_def.vc_version, sxs_toolset) - sxs_bugfix = _msvc_sxs_bugfix_version.get(key) - if not sxs_bugfix: - return sxs_toolset, False - - debug('sxs bugfix: version=%s => version=%s', repr(sxs_toolset), repr(sxs_bugfix)) - return sxs_bugfix, True + debug('sxs folder: ignore version=%s', repr(sxs_folder)) + return None, None def _msvc_read_toolset_folders(msvc, vc_dir): @@ -430,11 +438,11 @@ def _msvc_read_toolset_folders(msvc, vc_dir): build_dir = os.path.join(vc_dir, "Auxiliary", "Build") if os.path.exists(build_dir): - for sxs_version, sxs_path in Util.listdir_dirs(build_dir): - sxs_version = _msvc_sxs_toolset_folder(msvc, sxs_version) + for sxs_folder, sxs_path in Util.listdir_dirs(build_dir): + sxs_folder, sxs_version = _msvc_sxs_toolset_folder(msvc, sxs_folder) if not sxs_version: continue - filename = 'Microsoft.VCToolsVersion.{}.txt'.format(sxs_version) + filename = 'Microsoft.VCToolsVersion.{}.txt'.format(sxs_folder) filepath = os.path.join(sxs_path, filename) debug('sxs toolset: check file=%s', repr(filepath)) if os.path.exists(filepath): @@ -459,7 +467,34 @@ def _msvc_read_toolset_folders(msvc, vc_dir): repr(msvc.version), repr(toolset_version) ) + vcvars140 = os.path.join(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) + ) + 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 sxs_version in toolsets_sxs: + # have SxS version (folder/file mapping exists) + continue + for toolset_version in toolsets_full: + if not toolset_version.startswith(sxs_bugfix): + continue + debug( + 'sxs toolset: msvc_version=%s, sxs_version=%s, toolset_version=%s', + repr(msvc.version), repr(sxs_version), repr(toolset_version) + ) + # SxS compatible bugfix version (equivalent to toolset search) + toolsets_sxs[sxs_version] = toolset_version + break + debug('msvc_version=%s, toolsets=%s', repr(msvc.version), repr(toolsets_full)) return toolsets_sxs, toolsets_full @@ -492,16 +527,13 @@ def _msvc_read_toolset_default(msvc, vc_dir): _toolset_version_cache = {} _toolset_default_cache = {} -_toolset_have140_cache = None def _reset_toolset_cache(): global _toolset_version_cache global _toolset_default_cache - global _toolset_have140_cache debug('reset: toolset cache') _toolset_version_cache = {} _toolset_default_cache = {} - _toolset_have140_cache = None def _msvc_version_toolsets(msvc, vc_dir): @@ -523,24 +555,8 @@ def _msvc_default_toolset(msvc, vc_dir): return toolset_default -def _msvc_have140_toolset(): - global _toolset_have140_cache - - if _toolset_have140_cache is None: - suffix = Registry.vstudio_sxs_vc7('14.0') - vcinstalldirs = [record[0] for record in Registry.microsoft_query_paths(suffix)] - debug('vc140 toolset: paths=%s', repr(vcinstalldirs)) - _toolset_have140_cache = True if vcinstalldirs else False - - return _toolset_have140_cache - def _msvc_version_toolset_vcvars(msvc, vc_dir, toolset_version): - if toolset_version == '14.0': - if _msvc_have140_toolset(): - return toolset_version - return None - toolsets_sxs, toolsets_full = _msvc_version_toolsets(msvc, vc_dir) if toolset_version in toolsets_full: @@ -548,21 +564,14 @@ def _msvc_version_toolset_vcvars(msvc, vc_dir, toolset_version): toolset_vcvars = toolset_version return toolset_vcvars - sxs_toolset, sxs_isbugfix = _msvc_sxs_toolset_version(msvc, toolset_version) - if sxs_toolset: + if re_toolset_sxs.match(toolset_version): # SxS version provided - sxs_version = toolsets_sxs.get(sxs_toolset, None) - if sxs_version: + sxs_version = toolsets_sxs.get(toolset_version, None) + if sxs_version and sxs_version in toolsets_full: # SxS full toolset version - if sxs_version in toolsets_full: - toolset_vcvars = sxs_version - return toolset_vcvars - return None - # SxS version file missing - if not sxs_isbugfix: - return None - # SxS version bugfix: check toolset version - toolset_version = sxs_toolset + toolset_vcvars = sxs_version + return toolset_vcvars + return None for toolset_full in toolsets_full: if toolset_full.startswith(toolset_version): @@ -641,13 +650,7 @@ def _msvc_script_argument_toolset_constraints(msvc, toolset_version): err_msg = "MSVC_TOOLSET_VERSION ({}) format is not supported".format(repr(toolset_version)) return err_msg -def _msvc_script_argument_toolset(env, msvc, vc_dir, arglist): - - toolset_version = env['MSVC_TOOLSET_VERSION'] - debug('MSVC_VERSION=%s, MSVC_TOOLSET_VERSION=%s', repr(msvc.version), repr(toolset_version)) - - if not toolset_version: - return None +def _msvc_script_argument_toolset_vcvars(msvc, toolset_version, vc_dir): err_msg = _msvc_script_argument_toolset_constraints(msvc, toolset_version) if err_msg: @@ -673,6 +676,18 @@ def _msvc_script_argument_toolset(env, msvc, vc_dir, arglist): ) raise MSVCToolsetVersionNotFound(err_msg) + return toolset_vcvars + +def _msvc_script_argument_toolset(env, msvc, vc_dir, arglist): + + toolset_version = env['MSVC_TOOLSET_VERSION'] + debug('MSVC_VERSION=%s, MSVC_TOOLSET_VERSION=%s', repr(msvc.version), repr(toolset_version)) + + if not toolset_version: + return None + + toolset_vcvars = _msvc_script_argument_toolset_vcvars(msvc, toolset_version, vc_dir) + # toolset may not be installed for host/target argpair = (SortOrder.TOOLSET, '-vcvars_ver={}'.format(toolset_vcvars)) arglist.append(argpair) @@ -956,8 +971,38 @@ 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) + + toolset_vcvars = _msvc_script_argument_toolset_vcvars(msvc, toolset_version, vc_dir) + + return toolset_vcvars + +def _msvc_toolset_versions_internal(msvc_version, vc_dir, full=True, sxs=False): + + msvc = _msvc_version(msvc_version) + + if len(msvc.vs_def.vc_buildtools_all) <= 1: + return None + + toolset_versions = [] + + toolsets_sxs, toolsets_full = _msvc_version_toolsets(msvc, vc_dir) + + if sxs: + sxs_versions = list(toolsets_sxs.keys()) + sxs_versions.sort(reverse=True) + toolset_versions.extend(sxs_versions) + + if full: + toolset_versions.extend(toolsets_full) + + return toolset_versions + def reset(): debug('') + _reset_have140_cache() _reset_toolset_cache() def verify(): diff --git a/SCons/Tool/MSCommon/MSVC/Util.py b/SCons/Tool/MSCommon/MSVC/Util.py index f1983ba..11b80ec 100644 --- a/SCons/Tool/MSCommon/MSVC/Util.py +++ b/SCons/Tool/MSCommon/MSVC/Util.py @@ -28,6 +28,10 @@ Helper functions for Microsoft Visual C/C++. import os import re +from collections import ( + namedtuple, +) + def listdir_dirs(p): """ Return a list of tuples for each subdirectory of the given directory path. @@ -67,7 +71,7 @@ def process_path(p): p = os.path.normcase(p) return p -re_version_prefix = re.compile(r'^(?P[0-9.]+).*') +re_version_prefix = re.compile(r'^(?P[0-9.]+).*$') def get_version_prefix(version): """ @@ -88,7 +92,7 @@ def get_version_prefix(version): rval = '' return rval -re_msvc_version_prefix = re.compile(r'^(?P[1-9][0-9]?[.][0-9]).*') +re_msvc_version_prefix = re.compile(r'^(?P[1-9][0-9]?[.][0-9]).*$') def get_msvc_version_prefix(version): """ @@ -109,3 +113,41 @@ def get_msvc_version_prefix(version): rval = '' return rval +VERSION_ELEMENTS_DEFINITION = namedtuple('VersionElements', [ + 'vc_version_numstr', # msvc version numeric string ('14.1') + 'vc_toolset_numstr', # toolset version numeric string ('14.16.27023') + 'vc_version_suffix', # component type ('Exp') + 'msvc_version', # msvc version ('14.1Exp') +]) + +re_version_elements = re.compile(r'^(?P(?P[1-9][0-9]?[.][0-9])[0-9.]*)(?P.*)$') + +def get_version_elements(version): + """ + Get the version elements from an msvc version or toolset version. + + Args: + version: str + version specification + + Returns: + None or VersionElements namedtuple: + """ + + m = re_version_elements.match(version) + if not m: + return None + + vc_version_numstr = m.group('msvc_version') + vc_toolset_numstr = m.group('version') + vc_version_suffix = m.group('suffix') + + version_elements_def = VERSION_ELEMENTS_DEFINITION( + vc_version_numstr = vc_version_numstr, + vc_toolset_numstr = vc_toolset_numstr, + vc_version_suffix = vc_version_suffix, + msvc_version = vc_version_numstr + vc_version_suffix, + ) + + return version_elements_def + diff --git a/SCons/Tool/MSCommon/MSVC/Warnings.py b/SCons/Tool/MSCommon/MSVC/Warnings.py new file mode 100644 index 0000000..9f20972 --- /dev/null +++ b/SCons/Tool/MSCommon/MSVC/Warnings.py @@ -0,0 +1,44 @@ +# 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. + +""" +Warnings for Microsoft Visual C/C++. +""" + +__all__ = [ + 'VisualCWarning', + 'MSVCScriptExecutionWarning', + 'MSVCVersionNotFoundWarning', +] + +import SCons.Warnings + +class VisualCWarning(SCons.Warnings.WarningOnByDefault): + pass + +class MSVCScriptExecutionWarning(VisualCWarning): + pass + +class MSVCVersionNotFoundWarning(VisualCWarning): + pass + diff --git a/SCons/Tool/MSCommon/MSVC/__init__.py b/SCons/Tool/MSCommon/MSVC/__init__.py index b0ef5dd..d73a7e1 100644 --- a/SCons/Tool/MSCommon/MSVC/__init__.py +++ b/SCons/Tool/MSCommon/MSVC/__init__.py @@ -24,15 +24,15 @@ """ Functions for Microsoft Visual C/C++. -The reset method is used to restore MSVC module data structures to their +The _reset method is used to restore MSVC module data structures to their initial state for testing purposes. -The verify method is used as a sanity check that MSVC module data structures +The _verify method is used as a sanity check that MSVC module data structures are internally consistent. Currently: -* reset is invoked from reset_installed_vcs in the vc module. -* verify is invoked from the last line in the vc module. +* _reset is invoked from reset_installed_vcs in the vc module. +* _verify is invoked from the last line in the vc module. """ from . import Exceptions # noqa: F401 @@ -41,15 +41,17 @@ from . import Util # noqa: F401 from . import Config # noqa: F401 from . import Registry # noqa: F401 from . import SetupEnvDefault # noqa: F401 -from . import NotFound # noqa: F401 +from . import Policy # noqa: F401 from . import WinSDK # noqa: F401 from . import ScriptArguments # noqa: F401 +from . import API # noqa: F401 + from . import Dispatcher as _Dispatcher -def reset(): +def _reset(): _Dispatcher.reset() -def verify(): +def _verify(): _Dispatcher.verify() diff --git a/SCons/Tool/MSCommon/vc.py b/SCons/Tool/MSCommon/vc.py index 502a71f..4834b12 100644 --- a/SCons/Tool/MSCommon/vc.py +++ b/SCons/Tool/MSCommon/vc.py @@ -62,13 +62,7 @@ from .sdk import get_installed_sdks from . import MSVC -from .MSVC.Exceptions import ( - VisualCException -) - -# msvc test(s) expect notfound policy available via vc -from .MSVC.NotFound import set_msvc_notfound_policy # noqa: F401 -from .MSVC.NotFound import get_msvc_notfound_policy # noqa: F401 +from .MSVC.API import * class UnsupportedVersion(VisualCException): pass @@ -94,9 +88,6 @@ class MSVCScriptNotFound(VisualCException): class MSVCUseSettingsError(VisualCException): pass -#class BatchFileExecutionWarning(SCons.Warnings.WarningOnByDefault): -# pass - # Dict to 'canonalize' the arch _ARCH_TO_CANONICAL = { @@ -972,7 +963,7 @@ def reset_installed_vcs(): """Make it try again to find VC. This is just for the tests.""" global __INSTALLED_VCS_RUN __INSTALLED_VCS_RUN = None - MSVC.reset() + MSVC._reset() def get_default_installed_msvc(env=None): vcs = get_installed_vcs(env) @@ -1000,7 +991,7 @@ def get_default_installed_msvc(env=None): script_env_cache = None -def script_env(script, args=None): +def script_env(env, script, args=None): global script_env_cache if script_env_cache is None: @@ -1028,8 +1019,8 @@ def script_env(script, args=None): stdout = common.get_output(script, args) cache_data = common.parse_output(stdout) + # debug(stdout) olines = stdout.splitlines() - #debug(olines) # process stdout: batch file errors (not necessarily first line) script_errlog = [] @@ -1041,21 +1032,23 @@ def script_env(script, args=None): if script_errlog: script_errmsg = '\n'.join(script_errlog) + have_cl = False if cache_data and 'PATH' in cache_data: for p in cache_data['PATH']: if os.path.exists(os.path.join(p, _CL_EXE_NAME)): have_cl = True break + + debug( + 'script=%s args=%s have_cl=%s, errors=%s', + repr(script), repr(args), repr(have_cl), script_errmsg + ) + MSVC.Policy.msvc_scripterror_handler(env, script_errmsg) + if not have_cl: # detected errors, cl.exe not on path raise BatchFileExecutionError(script_errmsg) - else: - # detected errors, cl.exe on path - debug('script=%s args=%s errors=%s', repr(script), repr(args), script_errmsg) - # This may be a bad idea (scons environment != vs cmdline environment) - #SCons.Warnings.warn(BatchFileExecutionWarning, script_errmsg) - # TODO: errlog/errstr should be added to cache and warning moved to call site # once we updated cache, give a chance to write out if user wanted script_env_cache[cache_key] = cache_data @@ -1111,7 +1104,7 @@ def msvc_setup_env_once(env, tool=None): msg = "No versions of the MSVC compiler were found.\n" \ " Visual Studio C/C++ compilers may not be set correctly.\n" \ " Requested tool(s) are: {}".format(req_tools) - MSVC.NotFound.policy_handler(env, msg) + MSVC.Policy.msvc_notfound_handler(env, msg) def msvc_find_valid_batch_script(env, version): """Find and execute appropriate batch script to set up build env. @@ -1155,7 +1148,7 @@ def msvc_find_valid_batch_script(env, version): if vc_script: arg = MSVC.ScriptArguments.msvc_script_arguments(env, version, vc_dir, arg) try: - d = script_env(vc_script, args=arg) + d = script_env(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) @@ -1164,7 +1157,7 @@ def msvc_find_valid_batch_script(env, version): if not vc_script and sdk_script: debug('use_script 4: trying sdk script: %s', sdk_script) try: - d = script_env(sdk_script) + d = script_env(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) @@ -1198,7 +1191,7 @@ def msvc_find_valid_batch_script(env, version): " 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) + MSVC.Policy.msvc_notfound_handler(env, msg) return d @@ -1250,7 +1243,7 @@ def msvc_setup_env(env): 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) + d = script_env(env, use_script, args) elif use_script: d = msvc_find_valid_batch_script(env,version) debug('use_script 2 %s', d) @@ -1336,7 +1329,7 @@ def msvc_setup_env_tool(env=None, version=None, tool=None): rval = True return rval -def get_msvc_sdk_versions(msvc_version=None, msvc_uwp_app=False): +def msvc_sdk_versions(msvc_version=None, msvc_uwp_app=False): debug('msvc_version=%s, msvc_uwp_app=%s', repr(msvc_version), repr(msvc_uwp_app)) rval = [] @@ -1351,6 +1344,185 @@ def get_msvc_sdk_versions(msvc_version=None, msvc_uwp_app=False): rval = MSVC.WinSDK.get_msvc_sdk_version_list(msvc_version, msvc_uwp_app) return rval +def msvc_toolset_versions(msvc_version=None, full=True, sxs=False): + debug('msvc_version=%s, full=%s, sxs=%s', repr(msvc_version), repr(full), repr(sxs)) + + env = None + rval = [] + + if not msvc_version: + msvc_version = get_default_installed_msvc() + + if not msvc_version: + debug('no msvc versions detected') + return rval + + if msvc_version not in _VCVER: + msg = 'Unsupported msvc version {}'.format(repr(msvc_version)) + 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)) + return + + rval = MSVC.ScriptArguments._msvc_toolset_versions_internal(msvc_version, vc_dir, full=full, sxs=sxs) + return rval + +def find_msvc_version_toolset(version, prefer_newest=True): + """ + Returns an msvc version and a toolset version given a version + specification. + + This an EXPERIMENTAL proxy for using a toolset version to perform + msvc instance selection. This function will be removed when + toolset version is taken into account during msvc instance selection. + + Search for an installed Visual Studio instance that supports the + specified version. + + When the specified version contains a component suffix (e.g., Exp), + the msvc version is returned and the toolset version is None. No + search if performed. + + When the specified version does not contain a component suffix, the + version is treated as a toolset version specification. A search is + performed for the first msvc instance that contains the toolset + version. + + Only Visual Studio 2017 and later support toolset arguments. For + Visual Studio 2015 and earlier, the msvc version is returned and + the toolset version is None. + + Args: + + version: str + The version specification may be an msvc version or a toolset + version. + + prefer_newest: bool + True: prefer newer Visual Studio instances. + False: prefer the "native" Visual Studio instance first. If + the native Visual Studio instance is not detected, prefer + newer Visual Studio instances. + + Returns: + tuple: A tuple containing the msvc version and the msvc toolset version. + The msvc toolset version may be None. + + Raises: + MSVCToolsetVersionNotFound: when the specified version is not found. + MSVCArgumentError: when argument validation fails. + """ + debug('version=%s, prefer_newest=%s', repr(version), repr(prefer_newest)) + + env = None + msvc_version = version + msvc_toolset_version = None + + if not version: + debug( + 'ignore: msvc_version=%s, msvc_toolset_version=%s', + repr(msvc_version), repr(msvc_toolset_version) + ) + return msvc_version, msvc_toolset_version + + version_elements_def = MSVC.Util.get_version_elements(version) + if not version_elements_def: + msg = 'Unsupported version format {}'.format(repr(version)) + raise MSVCArgumentError(msg) + + if version_elements_def.msvc_version not in _VCVER: + msg = 'Unsupported msvc version {}'.format(repr(version)) + raise MSVCArgumentError(msg) + + if version_elements_def.vc_version_suffix: + if version_elements_def.vc_version_numstr != version_elements_def.vc_toolset_numstr: + # toolset version with component suffix + msg = 'Unsupported toolset version {}'.format(repr(version)) + raise MSVCArgumentError(msg) + + if float(version_elements_def.vc_version_numstr) > 14.0: + # VS2017 and later + force_toolset_msvc_version = False + else: + # VS2015 and earlier + force_toolset_msvc_version = True + extended_version = version_elements_def.vc_version_numstr + '0.00000' + if not extended_version.startswith(version_elements_def.vc_toolset_numstr): + # toolset not equivalent to msvc version + msg = 'Unsupported toolset version {} (expected {})'.format( + repr(version), repr(extended_version) + ) + raise MSVCArgumentError(msg) + + msvc_version = version_elements_def.msvc_version + + if msvc_version not in MSVC.Config.MSVC_VERSION_TOOLSET_SEARCH_MAP: + # VS2013 and earlier + debug( + 'ignore: msvc_version=%s, msvc_toolset_version=%s', + repr(msvc_version), repr(msvc_toolset_version) + ) + return msvc_version, msvc_toolset_version + + if force_toolset_msvc_version: + msvc_toolset_version = version_elements_def.vc_version_numstr + else: + msvc_toolset_version = version_elements_def.vc_toolset_numstr + + if prefer_newest: + query_version_list = MSVC.Config.MSVC_VERSION_TOOLSET_SEARCH_MAP[msvc_version] + else: + query_version_list = MSVC.Config.MSVC_VERSION_TOOLSET_DEFAULTS_MAP[msvc_version] + \ + MSVC.Config.MSVC_VERSION_TOOLSET_SEARCH_MAP[msvc_version] + + seen_msvc_version = set() + for query_msvc_version in query_version_list: + + if query_msvc_version in seen_msvc_version: + continue + seen_msvc_version.add(query_msvc_version) + + vc_dir = find_vc_pdir(env, query_msvc_version) + if not vc_dir: + continue + + if query_msvc_version.startswith('14.0'): + # VS2015 does not support toolset version argument + msvc_toolset_version = None + debug( + 'found: msvc_version=%s, msvc_toolset_version=%s', + repr(query_msvc_version), repr(msvc_toolset_version) + ) + return query_msvc_version, msvc_toolset_version + + try: + toolset_vcvars = MSVC.ScriptArguments._msvc_toolset_internal(query_msvc_version, msvc_toolset_version, vc_dir) + if toolset_vcvars: + debug( + 'found: msvc_version=%s, msvc_toolset_version=%s', + repr(query_msvc_version), repr(msvc_toolset_version) + ) + return query_msvc_version, msvc_toolset_version + + except MSVCToolsetVersionNotFound: + pass + + debug( + 'not found: msvc_version=%s, msvc_toolset_version=%s', + repr(msvc_version), repr(msvc_toolset_version) + ) + + if version_elements_def.vc_version_numstr == msvc_toolset_version: + msg = 'MSVC version {} was not found'.format(repr(version)) + MSVC.Policy.msvc_notfound_handler(None, msg) + return msvc_version, msvc_toolset_version + + msg = 'MSVC toolset version {} not found'.format(repr(version)) + raise MSVCToolsetVersionNotFound(msg) + + # internal consistency check (should be last) -MSVC.verify() +MSVC._verify() diff --git a/SCons/Tool/msvc.xml b/SCons/Tool/msvc.xml index 9297100..c582bb4 100644 --- a/SCons/Tool/msvc.xml +++ b/SCons/Tool/msvc.xml @@ -761,6 +761,131 @@ subject to the conditions listed above. The default &scons; behavior may change + + + +Specify the &scons; behavior when Microsoft Visual C/C++ batch file errors are detected. + + + +The &cv-MSVC_SCRIPTERROR_POLICY; specifies the &scons; behavior when msvc batch file errors are +detected. +When &cv-MSVC_SCRIPTERROR_POLICY; is not specified, the default &scons; behavior is to suppress +msvc batch file error messages. + + +The root cause of msvc build failures may be difficult to diagnose. In these situations, setting +the &scons; behavior to issue a warning when msvc batch file errors are detected may +produce additional diagnostic information. + + + +The valid values for &cv-MSVC_SCRIPTERROR_POLICY; and the corresponding &scons; behavior are: + + + + + +'Error' or 'Exception' + + +Raise an exception when msvc batch file errors are detected. + + + + + +'Warning' or 'Warn' + + +Issue a warning when msvc batch file errors are detected. + + + + + +'Ignore' or 'Suppress' + + +Suppress msvc batch file error messages. + + + + + + + +Note: in addition to the camel case values shown above, lower case and upper case values are accepted as well. + + + +Example 1 - A Visual Studio 2022 build with user-defined script arguments: + +env = environment(MSVC_VERSION='14.3', MSVC_SCRIPT_ARGS=['8.1', 'store', '-vcvars_ver=14.1']) +env.Program('hello', ['hello.c'], CCFLAGS='/MD', LIBS=['kernel32', 'user32', 'runtimeobject']) + + + + +Example 1 - Output fragment: + +... +link /nologo /OUT:_build001\hello.exe kernel32.lib user32.lib runtimeobject.lib _build001\hello.obj +LINK : fatal error LNK1104: cannot open file 'MSVCRT.lib' +... + + + + +Example 2 - A Visual Studio 2022 build with user-defined script arguments and the script error policy set +to issue a warning when msvc batch file errors are detected: + +env = environment(MSVC_VERSION='14.3', MSVC_SCRIPT_ARGS=['8.1', 'store', '-vcvars_ver=14.1'], MSVC_SCRIPTERROR_POLICY='warn') +env.Program('hello', ['hello.c'], CCFLAGS='/MD', LIBS=['kernel32', 'user32', 'runtimeobject']) + + + + +Example 2 - Output fragment: + +... +scons: warning: vc script errors detected: +[ERROR:vcvars.bat] The UWP Application Platform requires a Windows 10 SDK. +[ERROR:vcvars.bat] WindowsSdkDir = "C:\Program Files (x86)\Windows Kits\8.1\" +[ERROR:vcvars.bat] host/target architecture is not supported : { x64 , x64 } +... +link /nologo /OUT:_build001\hello.exe kernel32.lib user32.lib runtimeobject.lib _build001\hello.obj +LINK : fatal error LNK1104: cannot open file 'MSVCRT.lib' + + + + +Important usage details: + + + +&cv-MSVC_SCRIPTERROR_POLICY; must be passed as an argument to the &f-link-Environment; +constructor when an msvc tool (e.g., &t-link-msvc;, &t-link-msvs;, etc.) is +loaded via the default tools list or via a tools list passed to the +&f-link-Environment; constructor. +Otherwise, &cv-MSVC_SCRIPTERROR_POLICY; must be set before the first msvc tool is +loaded into the environment. + + + +Due to &scons; implementation details, not all Windows system environment variables are propagated +to the environment in which the msvc batch file is executed. Depending on Visual Studio version +and installation options, non-fatal msvc batch file error messages may be generated for ancillary +tools which may not affect builds with the msvc compiler. For this reason, caution is recommended +when setting the script error policy to raise an exception (e.g., 'Error'). + + + + + + + + diff --git a/doc/generated/variables.gen b/doc/generated/variables.gen index 80d5b18..533ea22 100644 --- a/doc/generated/variables.gen +++ b/doc/generated/variables.gen @@ -4939,6 +4939,132 @@ and compatible with the version of msvc selected. + + + MSVC_SCRIPTERROR_POLICY + + +Specify the &scons; behavior when Microsoft Visual C/C++ batch file errors are detected. + + + +The &cv-MSVC_SCRIPTERROR_POLICY; specifies the &scons; behavior when msvc batch file errors are +detected. +When &cv-MSVC_SCRIPTERROR_POLICY; is not specified, the default &scons; behavior is to suppress +msvc batch file error messages. + + +The root cause of msvc build failures may be difficult to diagnose. In these situations, setting +the &scons; behavior to issue a warning when msvc batch file errors are detected may +produce additional diagnostic information. + + + +The valid values for &cv-MSVC_SCRIPTERROR_POLICY; and the corresponding &scons; behavior are: + + + + + +'Error' or 'Exception' + + +Raise an exception when msvc batch file errors are detected. + + + + + +'Warning' or 'Warn' + + +Issue a warning when msvc batch file errors are detected. + + + + + +'Ignore' or 'Suppress' + + +Suppress msvc batch file error messages. + + + + + + + +Note: in addition to the camel case values shown above, lower case and upper case values are accepted as well. + + + +Example 1 - A Visual Studio 2022 build with user-defined script arguments: + +env = environment(MSVC_VERSION='14.3', MSVC_SCRIPT_ARGS=['8.1', 'store', '-vcvars_ver=14.1']) +env.Program('hello', ['hello.c'], CCFLAGS='/MD', LIBS=['kernel32', 'user32', 'runtimeobject']) + + + + +Example 1 - Output fragment: + +... +link /nologo /OUT:_build001\hello.exe kernel32.lib user32.lib runtimeobject.lib _build001\hello.obj +LINK : fatal error LNK1104: cannot open file 'MSVCRT.lib' +... + + + + +Example 2 - A Visual Studio 2022 build with user-defined script arguments and the script error policy set +to issue a warning when msvc batch file errors are detected: + +env = environment(MSVC_VERSION='14.3', MSVC_SCRIPT_ARGS=['8.1', 'store', '-vcvars_ver=14.1'], MSVC_SCRIPTERROR_POLICY='warn') +env.Program('hello', ['hello.c'], CCFLAGS='/MD', LIBS=['kernel32', 'user32', 'runtimeobject']) + + + + +Example 2 - Output fragment: + +... +scons: warning: vc script errors detected: +[ERROR:vcvars.bat] The UWP Application Platform requires a Windows 10 SDK. +[ERROR:vcvars.bat] WindowsSdkDir = "C:\Program Files (x86)\Windows Kits\8.1\" +[ERROR:vcvars.bat] host/target architecture is not supported : { x64 , x64 } +... +link /nologo /OUT:_build001\hello.exe kernel32.lib user32.lib runtimeobject.lib _build001\hello.obj +LINK : fatal error LNK1104: cannot open file 'MSVCRT.lib' + + + + +Important usage details: + + + +&cv-MSVC_SCRIPTERROR_POLICY; must be passed as an argument to the &f-link-Environment; +constructor when an msvc tool (e.g., &t-link-msvc;, &t-link-msvs;, etc.) is +loaded via the default tools list or via a tools list passed to the +&f-link-Environment; constructor. +Otherwise, &cv-MSVC_SCRIPTERROR_POLICY; must be set before the first msvc tool is +loaded into the environment. + + + +Due to &scons; implementation details, not all Windows system environment variables are propagated +to the environment in which the msvc batch file is executed. Depending on Visual Studio version +and installation options, non-fatal msvc batch file error messages may be generated for ancillary +tools which may not affect builds with the msvc compiler. For this reason, caution is recommended +when setting the script error policy to raise an exception (e.g., 'Error'). + + + + + + + MSVC_SDK_VERSION diff --git a/doc/generated/variables.mod b/doc/generated/variables.mod index cc51043..5d89887 100644 --- a/doc/generated/variables.mod +++ b/doc/generated/variables.mod @@ -323,6 +323,7 @@ THIS IS AN AUTOMATICALLY-GENERATED FILE. DO NOT EDIT. $MSVC_BATCH"> $MSVC_NOTFOUND_POLICY"> $MSVC_SCRIPT_ARGS"> +$MSVC_SCRIPTERROR_POLICY"> $MSVC_SDK_VERSION"> $MSVC_SPECTRE_LIBS"> $MSVC_TOOLSET_VERSION"> @@ -992,6 +993,7 @@ THIS IS AN AUTOMATICALLY-GENERATED FILE. DO NOT EDIT. $MSVC_BATCH"> $MSVC_NOTFOUND_POLICY"> $MSVC_SCRIPT_ARGS"> +$MSVC_SCRIPTERROR_POLICY"> $MSVC_SDK_VERSION"> $MSVC_SPECTRE_LIBS"> $MSVC_TOOLSET_VERSION"> -- cgit v0.12