.. sectnum::

README - SCons.Tool.MSCommon
############################

.. contents:: **Table of Contents**
   :depth: 2
   :local:


Design Notes
============

* Public, user-callable functions and exception types are available via
  the ``SCons.Tool.MSCommon`` namespace.

* Some existing code has been moved from ``MSCommon/vc.py`` to the appropriate
  ``MSCommon/MSVC/<modulename>``.

* No functions from the MSVC module or its child modules are intended to be invoked directly.
  All functions of interest are made available via the ``SCons.Tool.MSCommon`` namespace.
  It is anticipated that more code may be moved in the future as new features are added.
  By exposing the public API through ``SCons.Tool.MSCommon`` there should not be a problem
  with code movement.

* Additional helper functions primarily used for the test suite were added to
  ``MSCommon/vc.py`` and are available via the ``SCons.Tool.MSCommon`` namespace.


Known Issues
============

The following issues are known to exist:

* Using ``MSVC_USE_SCRIPT`` and ``MSVC_USE_SCRIPT_ARGS`` to call older Microsoft SDK
  ``SetEnv.cmd`` batch files may result in build failures.  Some of these batch files
  require delayed expansion to be enabled which is not usually the Windows default.
  One solution would be to launch the MSVC batch file command in a new command interpreter
  instance with delayed expansion enabled via command-line options.

* The code to suppress the "No versions of the MSVC compiler were found" warning for
  the default environment was moved from ``MSCommon/vc.py`` to ``MSCommon/MSVC/SetupEnvDefault.py``.
  There very few, if any, existing unit tests. Now that the code is isolated in its own
  module with a limited API, unit tests may be easier to implement.


Experimental Features
=====================

msvc_query_version_toolset(version=None, prefer_newest=True)
------------------------------------------------------------

The experimental function ``msvc_query_version_toolset`` was added to ``MSCommon/vc.py``
and is available via the ``SCons.Tool.MSCommon`` namespace. This function takes a version
specification or a toolset version specification and a product preference as arguments and
returns the msvc version and the msvc toolset version for the corresponding version specification.

This is a proxy for using the toolset version for selection until that functionality can be added.

Example usage:
::
    for version in [
        '14.3',
        '14.2',
        '14.1',
        '14.0',
        '14.32',
        '14.31',
        '14.29',
        '14.16',
        '14.00',
        '14.28.29333', # only 14.2
        '14.20.29333', # fictitious for testing
    ]:

        for prefer_newest in (True, False):
            try:
                msvc_version, msvc_toolset_version = msvc_query_version_toolset(version, prefer_newest=prefer_newest)
                failed = False
            except MSVCToolsetVersionNotFound:
                failed = True
            if failed:
                msg = 'FAILED'
                newline = '\n'
            else:
                env = Environment(MSVC_VERSION=msvc_version, MSVC_TOOLSET_VERSION=msvc_toolset_version)
                msg = 'passed'
                newline = ''
            print('{}Query: {} version={}, prefer_newest={}'.format(newline, msg, version, prefer_newest))

Example output fragment
::
    Build: _build003 {'MSVC_VERSION': '14.3', 'MSVC_TOOLSET_VERSION': '14.29.30133'}
    Where: C:\Software\MSVS-2022-143-Com\VC\Tools\MSVC\14.29.30133\bin\HostX64\x64\cl.exe
    Where: C:\Software\MSVS-2022-143-Com\Common7\Tools\guidgen.exe
    Query: passed version=14.2, prefer_newest=True

    Build: _build004 {'MSVC_VERSION': '14.2', 'MSVC_TOOLSET_VERSION': '14.29.30133'}
    Where: C:\Software\MSVS-2019-142-Com\VC\Tools\MSVC\14.29.30133\bin\HostX64\x64\cl.exe
    Where: C:\Software\MSVS-2019-142-Com\Common7\Tools\guidgen.exe
    Query: passed version=14.2, prefer_newest=False


Undocumented Features
=====================

set SCONS_CACHE_MSVC_FORCE_DEFAULTS=1
-------------------------------------

The Windows system environment variable ``SCONS_CACHE_MSVC_FORCE_DEFAULTS`` was added.  This variable is only
evaluated when the msvc cache is enabled and accepts the values ``1``, ``true``, and ``True``.

When enabled, the default msvc toolset version and the default sdk version, if not otherwise specified, are
added to the batch file argument list.  This is intended to make the cache more resilient to Visual Studio
updates that may change the default toolset version and/or the default SDK version.

Example usage:
::

    @echo Enabling scons cache ...
    @set "SCONS_CACHE_MSVC_CONFIG=mycachefile.json"
    @set "SCONS_CACHE_MSVC_FORCE_DEFAULTS=True"


End-User Diagnostic Tools
=========================

Due to the proliferation of user-defined msvc batch file arguments, the likelihood of end-user build
failures has increased.

Some of the options that may be employed in diagnosing end-user msvc build failures are listed below.

msvc_set_scripterror_policy('Warning') and MSVC_SCRIPTERROR_POLICY='Warning'
----------------------------------------------------------------------------

Enabling warnings to be produced for detected msvc batch file errors may provide additional context
for build failures. Refer to the documentation for details.

Change the default policy:
::
    from SCons.Tool.MSCommon import msvc_set_scripterror_policy

    msvc_set_scripterror_policy('Warning')

Specify the policy per-environment:
::

    env = Environment(MSVC_VERSION='14.3', MSVC_SPECTRE_LIBS=True, MSVC_SCRIPTERROR_POLICY='Warning')


set SCONS_MSCOMMON_DEBUG=mydebugfile.txt
----------------------------------------

The traditional method of diagnosing end-user issues is to enable the internal msvc debug logging.


set SCONS_CACHE_MSVC_CONFIG=mycachefile.json
--------------------------------------------

On occasion, enabling the cache file can prove to be a useful diagnostic tool.  If nothing else,
issues with the msvc environment may be readily apparent.


vswhere.exe
-----------

On occasion, the raw vswhere output may prove useful especially if there are suspected issues with
detection of installed msvc instances.

Windows command-line sample invocations:
::
    @rem 64-Bit Windows
    "%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -all -sort -prerelease -products * -legacy -format json >MYVSWHEREOUTPUT.json

    @rem 32-Bit Windows:
    "%ProgramFiles%\Microsoft Visual Studio\Installer\vswhere.exe" -all -sort -prerelease -products * -legacy -format json >MYVSWHEREOUTPUT.json


Visual Studio Implementation Notes
==================================

Batch File Arguments
--------------------

Supported MSVC batch file arguments by product:

======= === === ======= =======
Product UWP SDK Toolset Spectre
======= === === ======= =======
VS2022   X   X     X       X
------- --- --- ------- -------
VS2019   X   X     X       X
------- --- --- ------- -------
VS2017   X   X     X       X
------- --- --- ------- -------
VS2015   X   X
======= === === ======= =======

Supported MSVC batch file arguments in SCons:

======== ====================================== ===================================================
Argument Construction Variable                  Script Argument Equivalent
======== ====================================== ===================================================
UWP      ``MSVC_UWP_APP=True``                  ``MSVC_SCRIPT_ARGS='store'``
-------- -------------------------------------- ---------------------------------------------------
SDK      ``MSVC_SDK_VERSION='10.0.20348.0'``    ``MSVC_SCRIPT_ARGS='10.0.20348.0'``
-------- -------------------------------------- ---------------------------------------------------
Toolset  ``MSVC_TOOLSET_VERSION='14.31.31103'`` ``MSVC_SCRIPT_ARGS='-vcvars_ver=14.31.31103'``
-------- -------------------------------------- ---------------------------------------------------
Spectre  ``MSVC_SPECTRE_LIBS=True``             ``MSVC_SCRIPT_ARGS='-vcvars_spectre_libs=spectre'``
======== ====================================== ===================================================

**MSVC_SCRIPT_ARGS contents are not validated.  Utilizing script arguments that have construction
variable equivalents is discouraged and may lead to difficult to diagnose build errors.**

Additional constraints:

* ``MSVC_SDK_VERSION='8.1'`` and ``MSVC_UWP_APP=True`` is supported only for the v140
  build tools (i.e., ``MSVC_VERSION='14.0'`` or ``MSVC_TOOLSET_VERSION='14.0'``).

* ``MSVC_SPECTRE_LIBS=True`` and ``MSVC_UWP_APP=True`` is not supported (i.e., there
  are no spectre mitigations libraries for UWP builds).

Default Toolset Version
-----------------------

Side-by-side toolset versions were introduced in Visual Studio 2017.
The examples shown below are for Visual Studio 2022.

The msvc default toolset version is dependent on the installation options
selected.  This means that the default toolset version may be different for
each machine given the same Visual Studio product.

The msvc default toolset is not necessarily the latest toolset installed.
This has implications when a toolset version is specified using only one minor
digit (e.g., ``MSVC_TOOLSET_VERSION='14.3'`` or ``MSVC_SCRIPT_ARGS='-vcvars_ver=14.3'``).

Explicitly defining ``MSVC_TOOLSET_VERSION=None`` will return the same toolset
that the msvc batch files would return.  When using ``MSVC_SCRIPT_ARGS``, the
toolset specification should be omitted entirely.

Local installation and summary test results:
::
    VS2022\VC\Auxiliary\Build\Microsoft.VCToolsVersion.v143.default.txt
        14.31.31103

    VS2022\VC\Auxiliary\Build\Microsoft.VCToolsVersion.default.txt
        14.32.31326

Toolset version summary:
::
    14.31.31103   Environment()
    14.31.31103   Environment(MSVC_TOOLSET_VERSION=None)

    14.32.31326*  Environment(MSVC_TOOLSET_VERSION='14.3')
    14.32.31326*  Environment(MSVC_SCRIPT_ARGS=['-vcvars_ver=14.3'])

    14.31.31103   Environment(MSVC_TOOLSET_VERSION='14.31')
    14.31.31103   Environment(MSVC_SCRIPT_ARGS=['-vcvars_ver=14.31'])

    14.32.31326   Environment(MSVC_TOOLSET_VERSION='14.32')
    14.32.31326   Environment(MSVC_SCRIPT_ARGS=['-vcvars_ver=14.32'])

VS2022\\Common7\\Tools\\vsdevcmd\\ext\\vcvars.bat usage fragment:
::
    @echo     -vcvars_ver=version : Version of VC++ Toolset to select
    @echo            ** [Default]   : If -vcvars_ver=version is NOT specified, the toolset specified by
    @echo                             [VSInstallDir]\VC\Auxiliary\Build\Microsoft.VCToolsVersion.v143.default.txt will be used.
    @echo            ** 14.0        : VS 2015 (v140) VC++ Toolset (installation of the v140 toolset is a prerequisite)
    @echo            ** 14.xx       : VS 2017 or VS 2019 VC++ Toolset, if that version is installed on the system under
    @echo                             [VSInstallDir]\VC\MSVC\Tools\[version].  Where '14.xx' specifies a partial
    @echo                             [version]. The latest [version] directory that matches the specified value will
    @echo                             be used.
    @echo            ** 14.xx.yyyyy : VS 2017 or VS 2019 VC++ Toolset, if that version is installed on the system under
    @echo                             [VSInstallDir]\VC\MSVC\Tools\[version]. Where '14.xx.yyyyy' specifies an
    @echo                             exact [version] directory to be used.
    @echo            ** 14.xx.VV.vv : VS 2019 C++ side-by-side toolset package identity alias, if the SxS toolset has been installed on the system.
    @echo                             Where '14.xx.VV.vv' corresponds to a SxS toolset
    @echo                                 VV = VS Update Major Version (e.g. "16" for VS 2019 v16.9)
    @echo                                 vv = VS Update Minor version (e.g. "9" for VS 2019 v16.9)
    @echo                             Please see [VSInstallDir]\VC\Auxiliary\Build\[version]\Microsoft.VCToolsVersion.[version].txt for mapping of
    @echo                             SxS toolset to [VSInstallDir]\VC\MSVC\Tools\ directory.

VS2022 batch file fragment to determine the default toolset version:
::
    @REM Add MSVC
    set "__VCVARS_DEFAULT_CONFIG_FILE=%VCINSTALLDIR%Auxiliary\Build\Microsoft.VCToolsVersion.default.txt"

    @REM We will "fallback" to Microsoft.VCToolsVersion.default.txt (latest) if Microsoft.VCToolsVersion.v143.default.txt does not exist.
    if EXIST "%VCINSTALLDIR%Auxiliary\Build\Microsoft.VCToolsVersion.v143.default.txt" (
        if "%VSCMD_DEBUG%" GEQ "2" @echo [DEBUG:ext\%~nx0] Microsoft.VCToolsVersion.v143.default.txt was found.
        set "__VCVARS_DEFAULT_CONFIG_FILE=%VCINSTALLDIR%Auxiliary\Build\Microsoft.VCToolsVersion.v143.default.txt"

    ) else (
        if "%VSCMD_DEBUG%" GEQ "1" @echo [DEBUG:ext\%~nx0] Microsoft.VCToolsVersion.v143.default.txt was not found. Defaulting to 'Microsoft.VCToolsVersion.default.txt'.
    )

Empirical evidence suggests that the default toolset version is different from the latest
toolset version when the toolset version immediately preceding the latest version is
installed.  For example, the ``14.31`` toolset version is installed when the ``14.32``
toolset version is the latest.


Visual Studio Version Notes
============================

SDK Versions
------------

==== ============
SDK  Format
==== ============
10.0 10.0.XXXXX.Y
---- ------------
8.1  8.1
==== ============

BuildTools Versions
-------------------

========== ===== ===== ========
BuildTools VCVER CLVER MSVCRT
========== ===== ===== ========
v143       14.3   19.3 140/ucrt
---------- ----- ----- --------
v142       14.2   19.2 140/ucrt
---------- ----- ----- --------
v141       14.1   19.1 140/ucrt
---------- ----- ----- --------
v140       14.0   19.0 140/ucrt
---------- ----- ----- --------
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
========== ===== ===== ========

Product Versions
----------------

======== ===== ========= ============
Product  VSVER SDK       BuildTools
======== ===== ========= ============
2022     17.0  10.0, 8.1 v143 .. v140
-------- ----- --------- ------------
2019     16.0  10.0, 8.1 v142 .. v140
-------- ----- --------- ------------
2017     15.0  10.0, 8.1 v141 .. v140
-------- ----- --------- ------------
2015     14.0  10.0, 8.1 v140
-------- ----- --------- ------------
2013     12.0            v120
-------- ----- --------- ------------
2012     11.0            v110
-------- ----- --------- ------------
2010     10.0            v100
-------- ----- --------- ------------
2008      9.0            v90
-------- ----- --------- ------------
2005      8.0            v80
-------- ----- --------- ------------
2003.NET  7.1            v71
-------- ----- --------- ------------
2002.NET  7.0            v70
-------- ----- --------- ------------
6.0       6.0            v60
======== ===== ========= ============


SCons Implementation Notes
==========================

Compiler Detection Logic
------------------------

**WARNING: the compiler detection logic documentation below is likely out-of-date.**

In the future, the compiler detection logic documentation will be updated and integrated
into the current document format as appropriate.

::

    This is the flow of the compiler detection logic:

    External to MSCommon:

      The Tool init modules, in their exists() routines, call -> msvc_exists(env)

    At the moment, those modules are:
      SCons/Tool/midl.py
      SCons/Tool/mslib.py
      SCons/Tool/mslink.py
      SCons/Tool/msvc.py
      SCons/Tool/msvs.py

    env may contain a version request in MSVC_VERSION, but this is not used
    in the detection that follows from msvc_exists(), only in the later
    batch that starts with a call to msvc_setup_env().

    Internal to MSCommon/vc.py:

    + MSCommon/vc.py:msvc_exists:
    | vcs = cached_get_installed_vcs(env)
    | returns True if vcs > 0
    |
    +-> MSCommon/vc.py:cached_get_installed_vcs:
      | checks global if we've run previously, if so return it
      | populate the global from -> get_installed_vcs(env)
      |
      +-> MSCommon/vc.py:get_installed_vcs:
        | loop through "known" versions of msvc, granularity is maj.min
        |   check for product dir -> find_vc_pdir(env, ver)
        |
        +-> MSCommon/vc.py:find_vc_pdir:
          | From the msvc-version to pdir mapping dict, get reg key base and value
          | If value is none -> find_vc_pdir_vswhere(ver, env)
          |
          +-> MSCommon/vc.py:find_vc_pdir_vswhere:
            | From the vc-version to VS-version mapping table get string
            | Figure out where vswhere is -> msvc_find_vswhere()
            | Use subprocess to call vswhere, return first line of match
            /
          | else get product directory from registry (<= 14.0)
          /
        | if we found one -> _check_cl_exists_in_vc_dir(env, pdir, ver)
        |
        +-> MSCommon/vc.py:_check_cl_exists_in_vc_dir:
          | Figure out host/target pair
          | if version > 14.0 get specific version by looking in
          |    pdir + Auxiliary/Build/Microsoft/VCToolsVersion/default.txt
          |    look for pdir + Tools/MSVC/{specver}/bin/host/target/cl.exe
          | if 14.0 or less, "do older stuff"

    All of this just got us a yes-no answer on whether /some/ msvc version
    exists, but does populate __INSTALLED_VCS_RUN with all of the top-level
    versions as noted for get_installed_vcs

    Externally:

      Once a module's exists() has been called (or, in the case of
      clang/clangxx, after the compiler has been detected by other means -
      those still expect the rest of the msvc chain but not cl.exe)
      the module's generate() function calls -> msvc_setup_env_once(env)

    Internally:

    + MSCommon/vc.py:msvc_setup_env_once:
    | checks for environment flag MSVC_SETUP_RUN
    | if not, -> msvc_setup_env(env) and set flag
    |
    +-+ MSCommon/vc.py:msvc_setup_env:
      | set ver from -> get_default_version(env)
      |
      +-+ MSCommon/vc.py:get_default_version:
        | if no version specified in env.MSVC_VERSION:
        |   return first entry from -> cached_get_installed_vcs(env)
        | else return requested version
        /
      | get script from MSVC_USE_SCRIPT if set to a filename
      | -> script_env(script)
      |
      +-+ MSCommon/vc.py:script_env:
        | return (possibly cached) script variables matching script arg
        /
      | else -> msvc_find_valid_batch_script(env, version)
      |
      +-+ MSCommon/vc.py:msvc_find_valid_batch_script:
        | Build a list of plausible target values, and loop through
        |   look for host + target -> find_batch_file(env, ver, host, target)
        |
        +-+ MSCommon/vc.py:find_batch_file:
          | call -> find_vc_pdir (see above)
          | use the return to construct a version-biased batfile path, check
          /
        | if not found, try sdk scripts (unknown if this is still useful)


    Problems:
    - For VS >= 2017, VS and VS are not 1:1, there can be many VC for one VS
    - For vswhere-ready versions, detection does not proceed beyond the
      product level ("2019") into individual "features" (individual msvc)
    - As documented for MSVC_VERSION, compilers can only be requested if versions
      are from the set in _VCVER, so 14.1 but not 14.16 or 14.16.27023
    - Information found in the first pass (msvs_exists) isn't really
      available anywhere except the cached version list, since we just
      return true/false.
    - Since msvc_exists chain of calls does not look at version, we
      can proceed to compiler setup if *any* msvc was found, even if the
      one requested wasn't found.

