From 461eec6eb3510d4995c6ef445f2555f35fcf18c1 Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Sun, 26 Jan 2020 12:15:37 -0700 Subject: Add support for VS2017 Express Needed some new logic to accomodate that tools are not in the expected place (if real host is amd64, Express still uses Hostx86 as the tools dir, not Hostx64). Also needed a hack to run vcvarsall.bat with the correct argument. We now derive the "argument" by finding the appropriate host_target vcvars script, which has the argument to vcvarsall embedded, which we then run instead of calling vcvarsall directly. This allows us to deal with the pseudo target that Express has always used - we derive this part of the script name by using an existing lookup table. arm targets were also added (untested). Added some pithy comments (oh, no, the Piths ran away!) Signed-off-by: Mats Wichmann --- src/CHANGES.txt | 3 ++ src/engine/SCons/Tool/MSCommon/vc.py | 99 +++++++++++++++++++++++++++--------- src/engine/SCons/Tool/MSCommon/vs.py | 18 ++++++- 3 files changed, 93 insertions(+), 27 deletions(-) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 99184ed..85a4c97 100755 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -44,6 +44,9 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER - Clean up some duplications in manpage. Clarify portion of manpage on Dir and File nodes. - Reduce needless list conversions. - Fixed regex in Python scanner. + - Accomodate VS 2017 Express - it's got a more liberal license then VS + Community, so some people prefer it (from 2019, no more Express) + vswhere call should also now work even if programs aren't on the C: drive. RELEASE 3.1.2 - Mon, 17 Dec 2019 02:06:27 +0000 diff --git a/src/engine/SCons/Tool/MSCommon/vc.py b/src/engine/SCons/Tool/MSCommon/vc.py index 6f9bd12..be1fd4d 100644 --- a/src/engine/SCons/Tool/MSCommon/vc.py +++ b/src/engine/SCons/Tool/MSCommon/vc.py @@ -95,6 +95,9 @@ _ARCH_TO_CANONICAL = { "aarch64" : "arm64", } +# Starting with 14.1 (aka VS2017), the tools are organized by host directory. +# with a target subdir. They are now in .../VC/Auxuiliary/Build. +# Special case: 2017 Express uses Hostx86 even if it's on 64-bit Windows. _HOST_TARGET_TO_CL_DIR_GREATER_THAN_14 = { ("amd64","amd64") : ("Hostx64","x64"), ("amd64","x86") : ("Hostx64","x86"), @@ -106,8 +109,9 @@ _HOST_TARGET_TO_CL_DIR_GREATER_THAN_14 = { ("x86","arm64") : ("Hostx86","arm64"), } -# get path to the cl.exe dir for older VS versions -# based off a tuple of (host, target) platforms +# For older compilers, the original x86 tools are in the tools dir, +# any others are in a subdir named by the host/target pair, +# or just a single word if host==target _HOST_TARGET_TO_CL_DIR = { ("amd64","amd64") : "amd64", ("amd64","x86") : "amd64_x86", @@ -117,23 +121,31 @@ _HOST_TARGET_TO_CL_DIR = { ("x86","x86") : "", ("x86","arm") : "x86_arm", ("x86","arm64") : "x86_arm64", + ("arm","arm") : "arm", } -# Given a (host, target) tuple, return the argument for the bat file. -# Both host and targets should be canonalized. +# Given a (host, target) tuple, return the argument for the bat file; +# For 14.1+ compilers we use this to compose the name of the bat file instead. +# Both host and targets should be canoncalized. +# If the target already looks like a pair, return it - these are +# pseudo targets (mainly used by Express versions) _HOST_TARGET_ARCH_TO_BAT_ARCH = { ("x86", "x86"): "x86", ("x86", "amd64"): "x86_amd64", ("x86", "x86_amd64"): "x86_amd64", - ("amd64", "x86_amd64"): "x86_amd64", # This is present in (at least) VS2012 express + ("amd64", "x86_amd64"): "x86_amd64", ("amd64", "amd64"): "amd64", ("amd64", "x86"): "x86", - ("x86", "ia64"): "x86_ia64", # gone since 14.0 - ("arm", "arm"): "arm", # since 14.0, maybe gone 14.1? - ("x86", "arm"): "x86_arm", # since 14.0 - ("x86", "arm64"): "x86_arm64", # since 14.1 - ("amd64", "arm"): "amd64_arm", # since 14.0 - ("amd64", "arm64"): "amd64_arm64", # since 14.1 + ("x86", "ia64"): "x86_ia64", # gone since 14.0 + ("arm", "arm"): "arm", # since 14.0, maybe gone 14.1? + ("x86", "arm"): "x86_arm", # since 14.0 + ("x86", "arm64"): "x86_arm64", # since 14.1 + ("x86", "x86_arm"): "x86_arm", # since 14.0 + ("x86", "x86_arm64"): "x86_arm64", # since 14.1 + ("amd64", "arm"): "amd64_arm", # since 14.0 + ("amd64", "arm64"): "amd64_arm64", # since 14.1 + ("amd64", "x86_arm"): "x86_arm", # since 14.0 + ("amd64", "x86_arm64"): "x86_arm64", # since 14.1 } _CL_EXE_NAME = 'cl.exe' @@ -403,6 +415,11 @@ def find_batch_file(env,msvc_version,host_arch,target_arch): """ Find the location of the batch script which should set up the compiler for any TARGET_ARCH whose compilers were installed by Visual Studio/VCExpress + + In newer (2017+) compilers, make use of the fact there are vcvars + scripts named with a host_target pair that calls vcvarsall.bat properly, + so use that and return an indication we don't need the argument + we would have computed to run the batch file. """ pdir = find_vc_pdir(msvc_version) if pdir is None: @@ -412,6 +429,7 @@ def find_batch_file(env,msvc_version,host_arch,target_arch): # filter out e.g. "Exp" from the version name msvc_ver_numeric = get_msvc_version_numeric(msvc_version) + use_arg = True vernum = float(msvc_ver_numeric) if 7 <= vernum < 8: pdir = os.path.join(pdir, os.pardir, "Common7", "Tools") @@ -422,7 +440,10 @@ def find_batch_file(env,msvc_version,host_arch,target_arch): elif 8 <= vernum <= 14: batfilename = os.path.join(pdir, "vcvarsall.bat") else: # vernum >= 14.1 VS2017 and above - batfilename = os.path.join(pdir, "Auxiliary", "Build", "vcvarsall.bat") + batfiledir = os.path.join(pdir, "Auxiliary", "Build") + targ = _HOST_TARGET_ARCH_TO_BAT_ARCH[(host_arch, target_arch)] + batfilename = os.path.join(batfiledir, "vcvars%s.bat" % targ) + use_arg = False if not os.path.exists(batfilename): debug("Not found: %s" % batfilename) @@ -437,8 +458,8 @@ def find_batch_file(env,msvc_version,host_arch,target_arch): sdk_bat_file_path = os.path.join(pdir,sdk_bat_file) if os.path.exists(sdk_bat_file_path): debug('find_batch_file() sdk_bat_file_path:%s'%sdk_bat_file_path) - return (batfilename, sdk_bat_file_path) - return (batfilename, None) + return (batfilename, use_arg, sdk_bat_file_path) + return (batfilename, use_arg, None) __INSTALLED_VCS_RUN = None @@ -484,7 +505,8 @@ def _check_cl_exists_in_vc_dir(env, vc_dir, msvc_version): # make sure the cl.exe exists meaning the tool is installed if ver_num > 14: - # 2017 and newer allowed multiple versions of the VC toolset to be installed at the same time. + # 2017 and newer allowed multiple versions of the VC toolset to be + # installed at the same time. This changes the layout. # Just get the default tool version for now #TODO: support setting a specific minor VC version default_toolset_file = os.path.join(vc_dir, _VC_TOOLS_VERSION_FILE) @@ -509,6 +531,20 @@ def _check_cl_exists_in_vc_dir(env, vc_dir, msvc_version): debug('_check_cl_exists_in_vc_dir(): found ' + _CL_EXE_NAME + '!') return True + elif host_platform == "amd64" and host_trgt_dir[0] == "Hostx64": + # Special case: fallback to Hostx86 if Hostx64 was tried + # and failed. We should key this off the "x86_amd64" and + # related pseudo targets, but we don't see them in this function. + # This is because VS 2017 Express running on amd64 will look + # to our probe like the host dir should be Hostx64, but it uses + # Hostx86 anyway. + host_trgt_dir = ("Hostx86", host_trgt_dir[1]) + cl_path = os.path.join(vc_dir, 'Tools','MSVC', vc_specific_version, 'bin', host_trgt_dir[0], host_trgt_dir[1], _CL_EXE_NAME) + debug('_check_cl_exists_in_vc_dir(): checking for ' + _CL_EXE_NAME + ' at ' + cl_path) + if os.path.exists(cl_path): + debug('_check_cl_exists_in_vc_dir(): found ' + _CL_EXE_NAME + '!') + return True + elif 14 >= ver_num >= 8: # Set default value to be -1 as "" which is the value for x86/x86 yields true when tested @@ -705,17 +741,27 @@ def msvc_find_valid_batch_script(env, version): host_platform, target_platform, req_target_platform = platforms try_target_archs = [target_platform] - # VS2012 has a "cross compile" environment to build 64 bit + # Express versions have a "cross compile" environment to build 64 bit # with x86_amd64 as the argument to the batch setup script + # For 2015/2017 Express, this could extend to arm and arm64 targets. if req_target_platform in ('amd64', 'x86_64'): try_target_archs.append('x86_amd64') - elif not req_target_platform and target_platform in ['amd64', 'x86_64']: - # There may not be "native" amd64, but maybe "cross" x86_amd64 tools - try_target_archs.append('x86_amd64') - # If the user hasn't specifically requested a TARGET_ARCH, and - # The TARGET_ARCH is amd64 then also try 32 bits if there are no viable - # 64 bit tools installed - try_target_archs.append('x86') + elif req_target_platform in ('arm',): + try_target_archs.append('x86_arm') + elif req_target_platform in ('arm64',): + try_target_archs.append('x86_arm64') + elif not req_target_platform: + if target_platform in ('amd64', 'x86_64'): + # There may not be "native" amd64, but maybe cross x86_amd64 tools + try_target_archs.append('x86_amd64') + # If the user hasn't specifically requested a TARGET_ARCH, + # and the TARGET_ARCH is amd64 then also try 32 bits + # if there are no viable 64 bit tools installed + try_target_archs.append('x86') + elif target_platform in ('arm',): + try_target_archs.append('x86_arm') + elif target_platform in ('arm64',): + try_target_archs.append('x86_arm64') debug("msvs_find_valid_batch_script(): host_platform: %s try_target_archs:%s"%(host_platform, try_target_archs)) @@ -742,7 +788,7 @@ def msvc_find_valid_batch_script(env, version): # Try to locate a batch file for this host/target platform combo try: - (vc_script, sdk_script) = find_batch_file(env, version, host_platform, tp) + (vc_script, use_arg, sdk_script) = find_batch_file(env, version, host_platform, tp) debug('msvc_find_valid_batch_script() vc_script:%s sdk_script:%s'%(vc_script,sdk_script)) except VisualCException as e: msg = str(e) @@ -758,6 +804,8 @@ def msvc_find_valid_batch_script(env, version): debug('msvc_find_valid_batch_script() use_script 2 %s, args:%s' % (repr(vc_script), arg)) found = None if vc_script: + if not use_arg: + arg = None try: d = script_env(vc_script, args=arg) found = vc_script @@ -823,12 +871,13 @@ def msvc_setup_env(env): return None for k, v in d.items(): - debug('msvc_setup_env() env:%s -> %s'%(k,v)) env.PrependENVPath(k, v, delete_existing=True) + debug("msvc_setup_env() env['ENV']['%s'] = %s" % (k, env['ENV'][k])) # final check to issue a warning if the compiler is not present msvc_cl = find_program_path(env, 'cl') if not msvc_cl: + debug("msvc_setup_env() did not find 'cl'") SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, "Could not find MSVC compiler 'cl', it may need to be installed separately with Visual Studio") diff --git a/src/engine/SCons/Tool/MSCommon/vs.py b/src/engine/SCons/Tool/MSCommon/vs.py index bac35d8..e13f52f 100644 --- a/src/engine/SCons/Tool/MSCommon/vs.py +++ b/src/engine/SCons/Tool/MSCommon/vs.py @@ -205,7 +205,8 @@ SupportedVSList = [ hkeys=[], common_tools_var='VS160COMNTOOLS', executable_path=r'Common7\IDE\devenv.com', - batch_file_path=r'VC\Auxiliary\Build\vsvars32.bat', + # should be a fallback, prefer use vswhere installationPath + batch_file_path=r'Common7\Tools\VsDevCmd.bat', supported_arch=['x86', 'amd64', "arm"], ), @@ -216,10 +217,23 @@ SupportedVSList = [ hkeys=[], common_tools_var='VS150COMNTOOLS', executable_path=r'Common7\IDE\devenv.com', - batch_file_path=r'VC\Auxiliary\Build\vsvars32.bat', + # should be a fallback, prefer use vswhere installationPath + batch_file_path=r'Common7\Tools\VsDevCmd.bat', supported_arch=['x86', 'amd64', "arm"], ), + # Visual C++ 2017 Express Edition (for Desktop) + VisualStudio('14.1Exp', + vc_version='14.1', + sdk_version='10.0A', + hkeys=[], + common_tools_var='VS150COMNTOOLS', + executable_path=r'Common7\IDE\WDExpress.exe', + # should be a fallback, prefer use vswhere installationPath + batch_file_path=r'Common7\Tools\VsDevCmd.bat', + supported_arch=['x86', 'amd64', "arm"], + ), + # Visual Studio 2015 VisualStudio('14.0', vc_version='14.0', -- cgit v0.12 From 1478929815f7934bd8163d511dbc31936bfb3eb7 Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Mon, 27 Jan 2020 07:55:18 -0700 Subject: [PR #3538] change way native->native found VS 14.1+ looks for a host/target batch file, except those didn't exist in that form if host=target. add the ability to find those (vcvars32.bat and vcvars64.bat) Signed-off-by: Mats Wichmann --- src/engine/SCons/Tool/MSCommon/vc.py | 65 ++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/src/engine/SCons/Tool/MSCommon/vc.py b/src/engine/SCons/Tool/MSCommon/vc.py index be1fd4d..2832db7 100644 --- a/src/engine/SCons/Tool/MSCommon/vc.py +++ b/src/engine/SCons/Tool/MSCommon/vc.py @@ -109,7 +109,7 @@ _HOST_TARGET_TO_CL_DIR_GREATER_THAN_14 = { ("x86","arm64") : ("Hostx86","arm64"), } -# For older compilers, the original x86 tools are in the tools dir, +# before 14.1 (VS2017): the original x86 tools are in the tools dir, # any others are in a subdir named by the host/target pair, # or just a single word if host==target _HOST_TARGET_TO_CL_DIR = { @@ -124,8 +124,30 @@ _HOST_TARGET_TO_CL_DIR = { ("arm","arm") : "arm", } +# 14.1 (VS2017) and later: +# Given a (host, target) tuple, return the batch file to look for. +# We can't return an arg for vcvarsall.bat, because that script +# runs anyway, even if given a host/targ pair that isn't installed. +# Targets that already looks like a pair are pseudo targets. +_HOST_TARGET_TO_BAT_ARCH_GT14 = { + ("amd64", "amd64"): "vcvars64.bat", + ("amd64", "x86"): "vcvarsamd64_x86.bat", + ("amd64", "x86_amd64"): "vcvarsx86_amd64.bat", + ("amd64", "arm"): "vcvarsamd64_arm.bat", + ("amd64", "x86_arm"): "vcvarsx86_arm.bat", + ("amd64", "arm64"): "vcvarsamd64_arm64.bat", + ("amd64", "x86_arm64"): "vcvarsx86_arm64.bat", + ("x86", "x86"): "vcvars32.bat", + ("x86", "amd64"): "vcvarsx86_amd64.bat", + ("x86", "x86_amd64"): "vcvarsx86_amd64.bat", + ("x86", "arm"): "vcvarsx86_arm.bat", + ("x86", "x86_arm"): "vcvarsx86_arm.bat", + ("x86", "arm64"): "vcvarsx86_arm64.bat", + ("x86", "x86_arm64"): "vcvarsx86_arm64.bat", +} + +# before 14.1 (VS2017): # Given a (host, target) tuple, return the argument for the bat file; -# For 14.1+ compilers we use this to compose the name of the bat file instead. # Both host and targets should be canoncalized. # If the target already looks like a pair, return it - these are # pseudo targets (mainly used by Express versions) @@ -133,17 +155,16 @@ _HOST_TARGET_ARCH_TO_BAT_ARCH = { ("x86", "x86"): "x86", ("x86", "amd64"): "x86_amd64", ("x86", "x86_amd64"): "x86_amd64", - ("amd64", "x86_amd64"): "x86_amd64", + ("amd64", "x86_amd64"): "x86_amd64", # This is present in (at least) VS2012 express ("amd64", "amd64"): "amd64", ("amd64", "x86"): "x86", - ("x86", "ia64"): "x86_ia64", # gone since 14.0 - ("arm", "arm"): "arm", # since 14.0, maybe gone 14.1? - ("x86", "arm"): "x86_arm", # since 14.0 - ("x86", "arm64"): "x86_arm64", # since 14.1 - ("x86", "x86_arm"): "x86_arm", # since 14.0 - ("x86", "x86_arm64"): "x86_arm64", # since 14.1 - ("amd64", "arm"): "amd64_arm", # since 14.0 - ("amd64", "arm64"): "amd64_arm64", # since 14.1 + ("x86", "ia64"): "x86_ia64", # gone since 14.0 + ("x86", "arm"): "x86_arm", # since 14.0 + ("x86", "arm64"): "x86_arm64", # since 14.1 + ("amd64", "arm"): "amd64_arm", # since 14.0 + ("amd64", "arm64"): "amd64_arm64", # since 14.1 + ("x86", "x86_arm"): "x86_arm", # since 14.0 + ("x86", "x86_arm64"): "x86_arm64", # since 14.1 ("amd64", "x86_arm"): "x86_arm", # since 14.0 ("amd64", "x86_arm64"): "x86_arm64", # since 14.1 } @@ -441,8 +462,8 @@ def find_batch_file(env,msvc_version,host_arch,target_arch): batfilename = os.path.join(pdir, "vcvarsall.bat") else: # vernum >= 14.1 VS2017 and above batfiledir = os.path.join(pdir, "Auxiliary", "Build") - targ = _HOST_TARGET_ARCH_TO_BAT_ARCH[(host_arch, target_arch)] - batfilename = os.path.join(batfiledir, "vcvars%s.bat" % targ) + targ = _HOST_TARGET_TO_BAT_ARCH_GT14[(host_arch, target_arch)] + batfilename = os.path.join(batfiledir, targ) use_arg = False if not os.path.exists(batfilename): @@ -778,14 +799,6 @@ def msvc_find_valid_batch_script(env, version): SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg) arg = _HOST_TARGET_ARCH_TO_BAT_ARCH[host_target] - # Get just version numbers - maj, min = msvc_version_to_maj_min(version) - # VS2015+ - if maj >= 14: - if env.get('MSVC_UWP_APP') == '1': - # Initialize environment variables with store/universal paths - arg += ' store' - # Try to locate a batch file for this host/target platform combo try: (vc_script, use_arg, sdk_script) = find_batch_file(env, version, host_platform, tp) @@ -805,7 +818,15 @@ def msvc_find_valid_batch_script(env, version): found = None if vc_script: if not use_arg: - arg = None + arg = '' # bat file will supply platform type + # Get just version numbers + maj, min = msvc_version_to_maj_min(version) + # VS2015+ + if maj >= 14: + if env.get('MSVC_UWP_APP') == '1': + # Initialize environment variables with store/UWP paths + arg = (arg + ' store').lstrip() + try: d = script_env(vc_script, args=arg) found = vc_script -- cgit v0.12 From 384e93561fad65b4bf75771648174593a515528c Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Mon, 27 Jan 2020 10:12:07 -0700 Subject: [PR #3538] fix CHANGES.txt typo/sider complaint [appveyor skip] Signed-off-by: Mats Wichmann --- src/CHANGES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 85a4c97..72e20fb 100755 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -44,7 +44,7 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER - Clean up some duplications in manpage. Clarify portion of manpage on Dir and File nodes. - Reduce needless list conversions. - Fixed regex in Python scanner. - - Accomodate VS 2017 Express - it's got a more liberal license then VS + - Accommodate VS 2017 Express - it's got a more liberal license then VS Community, so some people prefer it (from 2019, no more Express) vswhere call should also now work even if programs aren't on the C: drive. -- cgit v0.12 From 3485896f915dc39e7e385fb72ec7e0321f5e5c58 Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Fri, 31 Jan 2020 10:32:22 -0700 Subject: [PR #3538] detect vswhere even if not in C: Some changes in vswhere detection - not specific to 2017 Express, but came up in testing so fixing at same time: use the Windows variables for where programs are installed, rather than hardcoding C:. [fixes #3542] Since py2 support is dropped, use the more modern subprocess.run to call vswhere instead of fidlling with a Popen object (change is minimal, but this eliminates the commented aside about Py2 not supporting a context manager on Popen instances, as run() is a complete implementation of setup-call-wait-teardown). Signed-off-by: Mats Wichmann --- src/engine/SCons/Tool/MSCommon/vc.py | 38 +++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/engine/SCons/Tool/MSCommon/vc.py b/src/engine/SCons/Tool/MSCommon/vc.py index 2832db7..105c499 100644 --- a/src/engine/SCons/Tool/MSCommon/vc.py +++ b/src/engine/SCons/Tool/MSCommon/vc.py @@ -41,7 +41,9 @@ import subprocess import os import platform import sys +from contextlib import suppress from string import digits as string_digits +#TODO: Python 2 cleanup if sys.version_info[0] == 2: import collections @@ -332,21 +334,24 @@ def find_vc_pdir_vswhere(msvc_version): debug("Unknown version of MSVC: %s" % msvc_version) raise UnsupportedVersion("Unknown version %s" % msvc_version) - # For bug 3333 - support default location of vswhere for both 64 and 32 bit windows - # installs. - for pf in ['Program Files (x86)', 'Program Files']: + # For bug 3333: support default location of vswhere for both + # 64 and 32 bit windows installs. + # For bug 3542: also accommodate not being on C: drive. + pfpaths = [os.environ["ProgramFiles"]] + with suppress(KeyError): + # 64-bit Windows only, try it first + pfpaths.insert(0, os.environ["ProgramFiles(x86)"]) + for pf in pfpaths: vswhere_path = os.path.join( - 'C:\\', pf, 'Microsoft Visual Studio', 'Installer', 'vswhere.exe' ) if os.path.exists(vswhere_path): - # If we found vswhere, then use it. break else: - # No vswhere on system, no install info available + # No vswhere on system, no install info available this way return None vswhere_cmd = [vswhere_path, @@ -354,22 +359,19 @@ def find_vc_pdir_vswhere(msvc_version): '-version', vswhere_version, '-property', 'installationPath'] - #TODO PY27 cannot use Popen as context manager - # try putting it back to the old way for now - sp = subprocess.Popen(vswhere_cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - vsdir, err = sp.communicate() - if vsdir: - vsdir = vsdir.decode("mbcs").splitlines() + cp = subprocess.run(vswhere_cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + if cp.stdout: + lines = cp.stdout.decode("mbcs").splitlines() # vswhere could easily return multiple lines # we could define a way to pick the one we prefer, but since # this data is currently only used to make a check for existence, # returning the first hit should be good enough for now. - vc_pdir = os.path.join(vsdir[0], 'VC') + vc_pdir = os.path.join(lines[0], 'VC') return vc_pdir else: - # No vswhere on system, no install info available + # We found vswhere, but no install info available for this version return None @@ -661,7 +663,7 @@ def reset_installed_vcs(): # within the same scons run. Windows builds on the CI system were split # into chunks to get around single-build time limits. # With VS2019 it got even slower and an optional persistent cache file -# was introduced. The cache now also stores only the parsed vars, +# was introduced. The cache now also stores only the parsed vars, # not the entire output of running the batch file - saves a bit # of time not parsing every time. @@ -703,7 +705,7 @@ def script_env(script, args=None): return data cache_data = convert(cache_data) - + return cache_data def get_default_version(env): -- cgit v0.12 From 4b410b9b4ea9eb750c66562db49297dfc02a4b3b Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Fri, 31 Jan 2020 18:51:51 -0700 Subject: [PR #3538] detect msvc case where target arch is set For Express 2017, no 64-bit tools. When that was fixed to retry with 32-bit tools, the case where a 64-bit target was specified failed. Signed-off-by: Mats Wichmann --- src/CHANGES.txt | 2 +- src/engine/SCons/Tool/MSCommon/vc.py | 43 ++++++++++++++++++++++++------------ 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 72e20fb..047bde1 100755 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -46,7 +46,7 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER - Fixed regex in Python scanner. - Accommodate VS 2017 Express - it's got a more liberal license then VS Community, so some people prefer it (from 2019, no more Express) - vswhere call should also now work even if programs aren't on the C: drive. + - vswhere call should also now work even if programs aren't on the C: drive. RELEASE 3.1.2 - Mon, 17 Dec 2019 02:06:27 +0000 diff --git a/src/engine/SCons/Tool/MSCommon/vc.py b/src/engine/SCons/Tool/MSCommon/vc.py index 105c499..f0731b3 100644 --- a/src/engine/SCons/Tool/MSCommon/vc.py +++ b/src/engine/SCons/Tool/MSCommon/vc.py @@ -135,6 +135,7 @@ _HOST_TARGET_TO_BAT_ARCH_GT14 = { ("amd64", "amd64"): "vcvars64.bat", ("amd64", "x86"): "vcvarsamd64_x86.bat", ("amd64", "x86_amd64"): "vcvarsx86_amd64.bat", + ("amd64", "x86_x86"): "vcvars32.bat", ("amd64", "arm"): "vcvarsamd64_arm.bat", ("amd64", "x86_arm"): "vcvarsx86_arm.bat", ("amd64", "arm64"): "vcvarsamd64_arm64.bat", @@ -160,6 +161,7 @@ _HOST_TARGET_ARCH_TO_BAT_ARCH = { ("amd64", "x86_amd64"): "x86_amd64", # This is present in (at least) VS2012 express ("amd64", "amd64"): "amd64", ("amd64", "x86"): "x86", + ("amd64", "x86_x86"): "x86", ("x86", "ia64"): "x86_ia64", # gone since 14.0 ("x86", "arm"): "x86_arm", # since 14.0 ("x86", "arm64"): "x86_arm64", # since 14.1 @@ -755,43 +757,56 @@ def msvc_setup_env_once(env): env["MSVC_SETUP_RUN"] = True def msvc_find_valid_batch_script(env, version): - debug('msvc_find_valid_batch_script()') - # Find the host platform, target platform, and if present the requested - # target platform + """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 if present the requested target: platforms = get_host_target(env) debug(" msvs_find_valid_batch_script(): host_platform %s, target_platform %s req_target_platform:%s" % platforms) - host_platform, target_platform, req_target_platform = platforms - try_target_archs = [target_platform] - # Express versions have a "cross compile" environment to build 64 bit - # with x86_amd64 as the argument to the batch setup script - # For 2015/2017 Express, this could extend to arm and arm64 targets. + # Most combinations of host + target are straightforward. + # While all MSVC / Visual Studio tools are pysically 32-bit, they + # make it look like there are 64-bit tools if the host is 64-bit, + # so you can invoke the environment batch script to set up to build, + # say, amd64 host -> x86 target. Express versions are an exception: + # they always look 32-bit, so the batch scripts with 64-bit + # host parts are absent. We try to fix that up in a couple of ways. + # One is here: we make a table of "targets" to try, with the extra + # targets being tags that tell us to try a different "host" instead + # of the deduced host. + try_target_archs = [target_platform] if req_target_platform in ('amd64', 'x86_64'): try_target_archs.append('x86_amd64') + elif req_target_platform in ('x86',): + try_target_archs.append('x86_x86') elif req_target_platform in ('arm',): try_target_archs.append('x86_arm') elif req_target_platform in ('arm64',): try_target_archs.append('x86_arm64') elif not req_target_platform: if target_platform in ('amd64', 'x86_64'): - # There may not be "native" amd64, but maybe cross x86_amd64 tools try_target_archs.append('x86_amd64') # If the user hasn't specifically requested a TARGET_ARCH, # and the TARGET_ARCH is amd64 then also try 32 bits # if there are no viable 64 bit tools installed try_target_archs.append('x86') - elif target_platform in ('arm',): - try_target_archs.append('x86_arm') - elif target_platform in ('arm64',): - try_target_archs.append('x86_arm64') debug("msvs_find_valid_batch_script(): host_platform: %s try_target_archs:%s"%(host_platform, try_target_archs)) d = None for tp in try_target_archs: # Set to current arch. - env['TARGET_ARCH']=tp + env['TARGET_ARCH'] = tp debug("msvc_find_valid_batch_script() trying target_platform:%s"%tp) host_target = (host_platform, tp) -- cgit v0.12 From 4f44de0e7816213b181cb00794bb0fc2b79798b9 Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Sat, 1 Feb 2020 07:42:53 -0700 Subject: [PR #3538] unbreak msvc tests on non-Windows Make tests still pass when run from a non-Windows platform, was breaking in CI on Linux hosts. Fiddle some more with comments. Add 14.1Exp in more places, including docs. Signed-off-by: Mats Wichmann --- src/engine/SCons/Tool/MSCommon/vc.py | 88 +++++++++++++++++++----------------- src/engine/SCons/Tool/msvc.xml | 1 + 2 files changed, 48 insertions(+), 41 deletions(-) diff --git a/src/engine/SCons/Tool/MSCommon/vc.py b/src/engine/SCons/Tool/MSCommon/vc.py index f0731b3..d955668 100644 --- a/src/engine/SCons/Tool/MSCommon/vc.py +++ b/src/engine/SCons/Tool/MSCommon/vc.py @@ -98,8 +98,9 @@ _ARCH_TO_CANONICAL = { } # Starting with 14.1 (aka VS2017), the tools are organized by host directory. -# with a target subdir. They are now in .../VC/Auxuiliary/Build. -# Special case: 2017 Express uses Hostx86 even if it's on 64-bit Windows. +# subdirs for each target. They are now in .../VC/Auxuiliary/Build. +# Note 2017 Express uses Hostx86 even if it's on 64-bit Windows, +# not reflected in this table. _HOST_TARGET_TO_CL_DIR_GREATER_THAN_14 = { ("amd64","amd64") : ("Hostx64","x64"), ("amd64","x86") : ("Hostx64","x86"), @@ -128,9 +129,10 @@ _HOST_TARGET_TO_CL_DIR = { # 14.1 (VS2017) and later: # Given a (host, target) tuple, return the batch file to look for. -# We can't return an arg for vcvarsall.bat, because that script -# runs anyway, even if given a host/targ pair that isn't installed. -# Targets that already looks like a pair are pseudo targets. +# We can't rely on returning an arg to use for vcvarsall.bat, +# because that script will run even if given a pair that isn't installed. +# Targets that already look like a pair are pseudo targets that +# effectively mean to skip whatever the host was specified as. _HOST_TARGET_TO_BAT_ARCH_GT14 = { ("amd64", "amd64"): "vcvars64.bat", ("amd64", "x86"): "vcvarsamd64_x86.bat", @@ -151,7 +153,7 @@ _HOST_TARGET_TO_BAT_ARCH_GT14 = { # before 14.1 (VS2017): # Given a (host, target) tuple, return the argument for the bat file; -# Both host and targets should be canoncalized. +# Both host and target should be canoncalized. # If the target already looks like a pair, return it - these are # pseudo targets (mainly used by Express versions) _HOST_TARGET_ARCH_TO_BAT_ARCH = { @@ -231,7 +233,7 @@ def get_host_target(env): # If you update this, update SupportedVSList in Tool/MSCommon/vs.py, and the # MSVC_VERSION documentation in Tool/msvc.xml. -_VCVER = ["14.2", "14.1", "14.0", "14.0Exp", "12.0", "12.0Exp", "11.0", "11.0Exp", "10.0", "10.0Exp", "9.0", "9.0Exp","8.0", "8.0Exp","7.1", "7.0", "6.0"] +_VCVER = ["14.2", "14.1", "14.1Exp", "14.0", "14.0Exp", "12.0", "12.0Exp", "11.0", "11.0Exp", "10.0", "10.0Exp", "9.0", "9.0Exp","8.0", "8.0Exp","7.1", "7.0", "6.0"] # if using vswhere, a further mapping is needed _VCVER_TO_VSWHERE_VER = { @@ -241,9 +243,11 @@ _VCVER_TO_VSWHERE_VER = { _VCVER_TO_PRODUCT_DIR = { '14.2' : [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'')], # VS 2019 doesn't set this key + (SCons.Util.HKEY_LOCAL_MACHINE, r'')], # not set by this version '14.1' : [ - (SCons.Util.HKEY_LOCAL_MACHINE, r'')], # VS 2017 doesn't set this key + (SCons.Util.HKEY_LOCAL_MACHINE, r'')], # not set by this version + '14.1Exp' : [ + (SCons.Util.HKEY_LOCAL_MACHINE, r'')], # not set by this version '14.0' : [ (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\14.0\Setup\VC\ProductDir')], '14.0Exp' : [ @@ -339,16 +343,17 @@ def find_vc_pdir_vswhere(msvc_version): # For bug 3333: support default location of vswhere for both # 64 and 32 bit windows installs. # For bug 3542: also accommodate not being on C: drive. - pfpaths = [os.environ["ProgramFiles"]] + # NB: this gets called from testsuite on non-Windows platforms. + # Whether that makes sense or not, don't break it for those. + pfpaths = [] with suppress(KeyError): # 64-bit Windows only, try it first - pfpaths.insert(0, os.environ["ProgramFiles(x86)"]) + pfpaths.append(os.environ["ProgramFiles(x86)"]) + with suppress(KeyError): + pfpaths.append(os.environ["ProgramFiles"]) for pf in pfpaths: vswhere_path = os.path.join( - pf, - 'Microsoft Visual Studio', - 'Installer', - 'vswhere.exe' + pf, "Microsoft Visual Studio", "Installer", "vswhere.exe" ) if os.path.exists(vswhere_path): break @@ -356,22 +361,22 @@ def find_vc_pdir_vswhere(msvc_version): # No vswhere on system, no install info available this way return None - vswhere_cmd = [vswhere_path, - '-products', '*', - '-version', vswhere_version, - '-property', 'installationPath'] + vswhere_cmd = [ + vswhere_path, + "-products", "*", + "-version", '[15.0, 16.0)', # vswhere_version, + "-property", "installationPath", + ] - cp = subprocess.run(vswhere_cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + cp = subprocess.run(vswhere_cmd, capture_output=True) if cp.stdout: - lines = cp.stdout.decode("mbcs").splitlines() - # vswhere could easily return multiple lines - # we could define a way to pick the one we prefer, but since + # vswhere could return multiple lines, e.g. if Build Tools + # and {Community,Professional,Enterprise} are both installed. + # We could define a way to pick the one we prefer, but since # this data is currently only used to make a check for existence, - # returning the first hit should be good enough for now. - vc_pdir = os.path.join(lines[0], 'VC') - return vc_pdir + # returning the first hit should be good enough. + lines = cp.stdout.decode("mbcs").splitlines() + return os.path.join(lines[0], 'VC') else: # We found vswhere, but no install info available for this version return None @@ -444,12 +449,11 @@ def find_batch_file(env,msvc_version,host_arch,target_arch): In newer (2017+) compilers, make use of the fact there are vcvars scripts named with a host_target pair that calls vcvarsall.bat properly, so use that and return an indication we don't need the argument - we would have computed to run the batch file. + we would have computed to run vcvarsall.bat. """ pdir = find_vc_pdir(msvc_version) if pdir is None: raise NoVersionFound("No version of Visual Studio found") - debug('find_batch_file() in {}'.format(pdir)) # filter out e.g. "Exp" from the version name @@ -492,10 +496,12 @@ _VC_TOOLS_VERSION_FILE_PATH = ['Auxiliary', 'Build', 'Microsoft.VCToolsVersion.d _VC_TOOLS_VERSION_FILE = os.sep.join(_VC_TOOLS_VERSION_FILE_PATH) def _check_cl_exists_in_vc_dir(env, vc_dir, msvc_version): - """Find the cl.exe on the filesystem in the vc_dir depending on - TARGET_ARCH, HOST_ARCH and the msvc version. TARGET_ARCH and - HOST_ARCH can be extracted from the passed env, unless its None, - which then the native platform is assumed the host and target. + """Return status of finding a cl.exe to use. + + Locates cl in the vc_dir depending on TARGET_ARCH, HOST_ARCH and the + msvc version. TARGET_ARCH and HOST_ARCH can be extracted from the + passed env, unless it is None, in which case the native platform is + assumed for both host and target. Args: env: Environment @@ -558,11 +564,11 @@ def _check_cl_exists_in_vc_dir(env, vc_dir, msvc_version): elif host_platform == "amd64" and host_trgt_dir[0] == "Hostx64": # Special case: fallback to Hostx86 if Hostx64 was tried - # and failed. We should key this off the "x86_amd64" and - # related pseudo targets, but we don't see them in this function. - # This is because VS 2017 Express running on amd64 will look - # to our probe like the host dir should be Hostx64, but it uses - # Hostx86 anyway. + # and failed. This is because VS 2017 Express running on amd64 + # will look to our probe like the host dir should be Hostx64, + # but Express uses Hostx86 anyway. + # We should key this off the "x86_amd64" and related pseudo + # targets, but we don't see those in this function. host_trgt_dir = ("Hostx86", host_trgt_dir[1]) cl_path = os.path.join(vc_dir, 'Tools','MSVC', vc_specific_version, 'bin', host_trgt_dir[0], host_trgt_dir[1], _CL_EXE_NAME) debug('_check_cl_exists_in_vc_dir(): checking for ' + _CL_EXE_NAME + ' at ' + cl_path) @@ -572,8 +578,8 @@ def _check_cl_exists_in_vc_dir(env, vc_dir, msvc_version): elif 14 >= ver_num >= 8: - # Set default value to be -1 as "" which is the value for x86/x86 yields true when tested - # if not host_trgt_dir + # Set default value to be -1 as "" which is the value for x86/x86 + # yields true when tested if not host_trgt_dir host_trgt_dir = _HOST_TARGET_TO_CL_DIR.get((host_platform, target_platform), None) if host_trgt_dir is None: debug('_check_cl_exists_in_vc_dir(): unsupported host/target platform combo') diff --git a/src/engine/SCons/Tool/msvc.xml b/src/engine/SCons/Tool/msvc.xml index dcac6cc..100c84c 100644 --- a/src/engine/SCons/Tool/msvc.xml +++ b/src/engine/SCons/Tool/msvc.xml @@ -355,6 +355,7 @@ constructor; setting it later has no effect. Valid values for Windows are 14.2, 14.1, +14.1Exp, 14.0, 14.0Exp, 12.0, -- cgit v0.12 From 98e032097907d9c21fc5080456b5870892ac971b Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Sat, 1 Feb 2020 16:42:44 -0700 Subject: [PR #3538] roll back use of a too-new Py3 feature subprocess.run kw arg capture_output actually doesn't appear until Python 3.7; drop back to individually setting stdout/stderr. Signed-off-by: Mats Wichmann --- src/engine/SCons/Tool/MSCommon/vc.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/engine/SCons/Tool/MSCommon/vc.py b/src/engine/SCons/Tool/MSCommon/vc.py index d955668..2d6fad1 100644 --- a/src/engine/SCons/Tool/MSCommon/vc.py +++ b/src/engine/SCons/Tool/MSCommon/vc.py @@ -43,6 +43,7 @@ import platform import sys from contextlib import suppress from string import digits as string_digits +from subprocess import PIPE #TODO: Python 2 cleanup if sys.version_info[0] == 2: import collections @@ -368,7 +369,9 @@ def find_vc_pdir_vswhere(msvc_version): "-property", "installationPath", ] - cp = subprocess.run(vswhere_cmd, capture_output=True) + #cp = subprocess.run(vswhere_cmd, capture_output=True) # 3.7+ only + cp = subprocess.run(vswhere_cmd, stdout=PIPE, stderr=PIPE) + if cp.stdout: # vswhere could return multiple lines, e.g. if Build Tools # and {Community,Professional,Enterprise} are both installed. -- cgit v0.12 From 2961f860b169052f6ce780404b2f7161987767a5 Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Sun, 2 Feb 2020 10:18:58 -0700 Subject: [PR #3538] fix wshwere invoke error During debugging, the call to vswhere which uses a derived version from the table was replaced by a fixed range, and this was unfortunately committed to git. Restoring. Signed-off-by: Mats Wichmann --- src/engine/SCons/Tool/MSCommon/vc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/SCons/Tool/MSCommon/vc.py b/src/engine/SCons/Tool/MSCommon/vc.py index 2d6fad1..d22aa9b 100644 --- a/src/engine/SCons/Tool/MSCommon/vc.py +++ b/src/engine/SCons/Tool/MSCommon/vc.py @@ -365,7 +365,7 @@ def find_vc_pdir_vswhere(msvc_version): vswhere_cmd = [ vswhere_path, "-products", "*", - "-version", '[15.0, 16.0)', # vswhere_version, + "-version", vswhere_version, "-property", "installationPath", ] -- cgit v0.12 From c23b5ad29c14e686aff08da4d76992e06ecfa8c5 Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Sun, 2 Feb 2020 11:03:11 -0700 Subject: [PR #3538] MSVC UWP test was never updated for 14.2 Has been reporting skipped because not supported due to lookup logic in the testcase. Adding. Signed-off-by: Mats Wichmann --- test/MSVC/MSVC_UWP_APP.py | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/test/MSVC/MSVC_UWP_APP.py b/test/MSVC/MSVC_UWP_APP.py index 861edcd..0f33dda 100644 --- a/test/MSVC/MSVC_UWP_APP.py +++ b/test/MSVC/MSVC_UWP_APP.py @@ -83,23 +83,23 @@ test = TestSCons.TestSCons() test.skip_if_not_msvc() installed_msvc_versions = msvc.cached_get_installed_vcs() -# MSVC guaranteed to be at least one version on the system or else skip_if_not_msvc() function -# would have skipped the test +# MSVC guaranteed to be at least one version on the system or else +# skip_if_not_msvc() function would have skipped the test msvc_140 = '14.0' in installed_msvc_versions msvc_141 = '14.1' in installed_msvc_versions +msvc_142 = '14.2' in installed_msvc_versions -if not (msvc_140 or msvc_141): - test.skip_test("Available MSVC doesn't support App store") +if not any((msvc_140, msvc_141, msvc_142)): + test.skip_test("Available MSVC doesn't support App store\n") if msvc_140: - - test.write('SConstruct', """ + test.write('SConstruct', """\ if ARGUMENTS.get('MSVC_UWP_APP'): help_vars = Variables() help_vars.Add(EnumVariable( 'MSVC_UWP_APP', - 'Build for a Universal Windows Platform (UWP) Application', + 'Build a Universal Windows Platform (UWP) Application', '0', allowed_values=('0', '1'))) else: @@ -128,14 +128,31 @@ print('env[MSVC_VERSION]=%s' % env.get('MSVC_VERSION')) test.fail_test((vclibstore_path_present is True) or (vclibstorerefs_path_present is True), message='VC Store LIBPATHs present when MSVC_UWP_APP not set (msvc_version=%s)' % msvc_version) -if msvc_141: - - test.write('SConstruct', """ +if msvc_141 or msvc_142: + if msvc_142: + test.write('SConstruct', """\ +if ARGUMENTS.get('MSVC_UWP_APP'): + help_vars = Variables() + help_vars.Add(EnumVariable( + 'MSVC_UWP_APP', + 'Build a Universal Windows Platform (UWP) Application', + '0', + allowed_values=('0', '1'))) +else: + help_vars = None +env = Environment(tools=['default', 'msvc'], variables=help_vars, MSVC_VERSION='14.2') +# Print the ENV LIBPATH to stdout +print('env[ENV][LIBPATH]=%s' % env.get('ENV').get('LIBPATH')) +print('env[MSVC_VERSION]=%s' % env.get('MSVC_VERSION')) +print('env[ENV][VSCMD_ARG_app_plat]=%s' % env.get('ENV').get('VSCMD_ARG_app_plat')) +""") + elif msvc_141: + test.write('SConstruct', """\ if ARGUMENTS.get('MSVC_UWP_APP'): help_vars = Variables() help_vars.Add(EnumVariable( 'MSVC_UWP_APP', - 'Build for a Universal Windows Platform (UWP) Application', + 'Build a Universal Windows Platform (UWP) Application', '0', allowed_values=('0', '1'))) else: -- cgit v0.12 From ae586e9d850b030cad7c2a5bad9ed79547d7f707 Mon Sep 17 00:00:00 2001 From: Mats Wichmann Date: Mon, 3 Feb 2020 16:51:03 -0700 Subject: [PR #3538] vswhere lookup: add choco path Chocolatey install path is now checked. The paths to try use a different method of getting the Windows vars - os.path.expandvars, that avoids the use of a try block or os.environ.get. Signed-off-by: Mats Wichmann --- src/engine/SCons/Tool/MSCommon/vc.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/engine/SCons/Tool/MSCommon/vc.py b/src/engine/SCons/Tool/MSCommon/vc.py index d22aa9b..a18a1a7 100644 --- a/src/engine/SCons/Tool/MSCommon/vc.py +++ b/src/engine/SCons/Tool/MSCommon/vc.py @@ -346,16 +346,15 @@ def find_vc_pdir_vswhere(msvc_version): # For bug 3542: also accommodate not being on C: drive. # NB: this gets called from testsuite on non-Windows platforms. # Whether that makes sense or not, don't break it for those. - pfpaths = [] - with suppress(KeyError): - # 64-bit Windows only, try it first - pfpaths.append(os.environ["ProgramFiles(x86)"]) - with suppress(KeyError): - pfpaths.append(os.environ["ProgramFiles"]) + # TODO: requested to add a user-specified path to vswhere + # and have this routine set the same var if it finds it. + pfpaths = [ + os.path.expandvars(r"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer"), + os.path.expandvars(r"%ProgramFiles%\Microsoft Visual Studio\Installer"), + os.path.expandvars(r"%ChocolateyInstall%\bin"), + ] for pf in pfpaths: - vswhere_path = os.path.join( - pf, "Microsoft Visual Studio", "Installer", "vswhere.exe" - ) + vswhere_path = os.path.join(pf, "vswhere.exe") if os.path.exists(vswhere_path): break else: -- cgit v0.12