[pypy-commit] pypy msvc-discovery: simplify, use only setuptools.msvc to find msvc compilers

mattip pypy.commits at gmail.com
Sun Nov 10 18:16:37 EST 2019


Author: Matti Picus <matti.picus at gmail.com>
Branch: msvc-discovery
Changeset: r98012:f53664167f74
Date: 2019-11-08 21:55 +0200
http://bitbucket.org/pypy/pypy/changeset/f53664167f74/

Log:	simplify, use only setuptools.msvc to find msvc compilers

diff --git a/rpython/tool/setuptools_msvc.py b/rpython/tool/setuptools_msvc.py
--- a/rpython/tool/setuptools_msvc.py
+++ b/rpython/tool/setuptools_msvc.py
@@ -11,35 +11,39 @@
 Microsoft Visual C++ 10.0:
     Microsoft Windows SDK 7.1 (x86, x64, ia64)
 
-Microsoft Visual C++ 14.0:
+Microsoft Visual C++ 14.X:
     Microsoft Visual C++ Build Tools 2015 (x86, x64, arm)
-    Microsoft Visual Studio 2017 (x86, x64, arm, arm64)
     Microsoft Visual Studio Build Tools 2017 (x86, x64, arm, arm64)
+    Microsoft Visual Studio Build Tools 2019 (x86, x64, arm, arm64)
+
+This may also support compilers shipped with compatible Visual Studio versions.
 """
 
-# copied as-is from python3
-# modified to:
+# copied as-is from setuptools v41.6.0 setuptools/msvc.py
+# modified to (search for `"modified"):
 #    - commented out monkey patching
 #    - lookup() returns str not unicode
 
-import os
+import json
+from io import open
+from os import listdir, pathsep
+from os.path import join, isfile, isdir, dirname
 import sys
 import platform
 import itertools
 import distutils.errors
+from setuptools.extern.packaging.version import LegacyVersion
 
 from setuptools.extern.six.moves import filterfalse
 
-#from .monkey import get_unpatched
+# modified
+# from .monkey import get_unpatched
 
 if platform.system() == 'Windows':
     from setuptools.extern.six.moves import winreg
-    safe_env = os.environ
+    from os import environ
 else:
-    """
-    Mock winreg and environ so the module can be imported
-    on this platform.
-    """
+    # Mock winreg and environ so the module can be imported on this platform.
 
     class winreg:
         HKEY_USERS = None
@@ -47,12 +51,12 @@
         HKEY_LOCAL_MACHINE = None
         HKEY_CLASSES_ROOT = None
 
-    safe_env = dict()
+    environ = dict()
 
 _msvc9_suppress_errors = (
     # msvc9compiler isn't available on some platforms
     ImportError,
-    
+
     # msvc9compiler raises DistutilsPlatformError in some
     # environments. See #1118.
     distutils.errors.DistutilsPlatformError,
@@ -67,16 +71,14 @@
 def msvc9_find_vcvarsall(version):
     """
     Patched "distutils.msvc9compiler.find_vcvarsall" to use the standalone
-    compiler build for Python (VCForPython). Fall back to original behavior
-    when the standalone compiler is not available.
+    compiler build for Python
+    (VCForPython / Microsoft Visual C++ Compiler for Python 2.7).
+
+    Fall back to original behavior when the standalone compiler is not
+    available.
 
     Redirect the path of "vcvarsall.bat".
 
-    Known supported compilers
-    -------------------------
-    Microsoft Visual C++ 9.0:
-        Microsoft Visual C++ Compiler for Python 2.7 (x86, amd64)
-
     Parameters
     ----------
     version: float
@@ -84,24 +86,25 @@
 
     Return
     ------
-    vcvarsall.bat path: str
+    str
+        vcvarsall.bat path
     """
-    VC_BASE = r'Software\%sMicrosoft\DevDiv\VCForPython\%0.1f'
-    key = VC_BASE % ('', version)
+    vc_base = r'Software\%sMicrosoft\DevDiv\VCForPython\%0.1f'
+    key = vc_base % ('', version)
     try:
         # Per-user installs register the compiler path here
         productdir = Reg.get_value(key, "installdir")
     except KeyError:
         try:
             # All-user installs on a 64-bit system register here
-            key = VC_BASE % ('Wow6432Node\\', version)
+            key = vc_base % ('Wow6432Node\\', version)
             productdir = Reg.get_value(key, "installdir")
         except KeyError:
             productdir = None
 
     if productdir:
-        vcvarsall = os.path.os.path.join(productdir, "vcvarsall.bat")
-        if os.path.isfile(vcvarsall):
+        vcvarsall = join(productdir, "vcvarsall.bat")
+        if isfile(vcvarsall):
             return vcvarsall
 
     return get_unpatched(msvc9_find_vcvarsall)(version)
@@ -110,20 +113,10 @@
 def msvc9_query_vcvarsall(ver, arch='x86', *args, **kwargs):
     """
     Patched "distutils.msvc9compiler.query_vcvarsall" for support extra
-    compilers.
+    Microsoft Visual C++ 9.0 and 10.0 compilers.
 
     Set environment without use of "vcvarsall.bat".
 
-    Known supported compilers
-    -------------------------
-    Microsoft Visual C++ 9.0:
-        Microsoft Visual C++ Compiler for Python 2.7 (x86, amd64)
-        Microsoft Windows SDK 6.1 (x86, x64, ia64)
-        Microsoft Windows SDK 7.0 (x86, x64, ia64)
-
-    Microsoft Visual C++ 10.0:
-        Microsoft Windows SDK 7.1 (x86, x64, ia64)
-
     Parameters
     ----------
     ver: float
@@ -133,9 +126,10 @@
 
     Return
     ------
-    environment: dict
+    dict
+        environment
     """
-    # Try to get environement from vcvarsall.bat (Classical way)
+    # Try to get environment from vcvarsall.bat (Classical way)
     #try:
     #    orig = get_unpatched(msvc9_query_vcvarsall)
     #    return orig(ver, arch, *args, **kwargs)
@@ -157,17 +151,10 @@
 def msvc14_get_vc_env(plat_spec):
     """
     Patched "distutils._msvccompiler._get_vc_env" for support extra
-    compilers.
+    Microsoft Visual C++ 14.X compilers.
 
     Set environment without use of "vcvarsall.bat".
 
-    Known supported compilers
-    -------------------------
-    Microsoft Visual C++ 14.0:
-        Microsoft Visual C++ Build Tools 2015 (x86, x64, arm)
-        Microsoft Visual Studio 2017 (x86, x64, arm, arm64)
-        Microsoft Visual Studio Build Tools 2017 (x86, x64, arm, arm64)
-
     Parameters
     ----------
     plat_spec: str
@@ -175,7 +162,8 @@
 
     Return
     ------
-    environment: dict
+    dict
+        environment
     """
     # Try to get environment from vcvarsall.bat (Classical way)
     #try:
@@ -200,7 +188,6 @@
     """
     if "numpy.distutils" in sys.modules:
         import numpy as np
-        from pkg_resources.extern.packaging.version import LegacyVersion
         if LegacyVersion(np.__version__) < LegacyVersion('1.11.2'):
             return np.distutils.ccompiler.gen_lib_options(*args, **kwargs)
     return get_unpatched(msvc14_gen_lib_options)(*args, **kwargs)
@@ -222,9 +209,9 @@
         if version == 9.0:
             if arch.lower().find('ia64') > -1:
                 # For VC++ 9.0, if IA64 support is needed, redirect user
-                # to Windows SDK 7.0
-                message += ' Get it with "Microsoft Windows SDK 7.0": '
-                message += msdownload % 3138
+                # to Windows SDK 7.0.
+                # Note: No download link available from Microsoft.
+                message += ' Get it with "Microsoft Windows SDK 7.0"'
             else:
                 # For VC++ 9.0 redirect user to Vc++ for Python 2.7 :
                 # This redirection link is maintained by Microsoft.
@@ -235,36 +222,59 @@
             message += ' Get it with "Microsoft Windows SDK 7.1": '
             message += msdownload % 8279
         elif version >= 14.0:
-            # For VC++ 14.0 Redirect user to Visual C++ Build Tools
-            message += (' Get it with "Microsoft Visual C++ Build Tools": '
-                        r'http://landinghub.visualstudio.com/'
-                        'visual-cpp-build-tools')
+            # For VC++ 14.X Redirect user to latest Visual C++ Build Tools
+            message += (' Get it with "Build Tools for Visual Studio": '
+                        r'https://visualstudio.microsoft.com/downloads/')
 
     exc.args = (message, )
 
 
 class PlatformInfo:
     """
-    Current and Target Architectures informations.
+    Current and Target Architectures information.
 
     Parameters
     ----------
     arch: str
         Target architecture.
     """
-    current_cpu = safe_env.get('processor_architecture', '').lower()
+    current_cpu = environ.get('processor_architecture', '').lower()
 
     def __init__(self, arch):
         self.arch = arch.lower().replace('x64', 'amd64')
 
     @property
     def target_cpu(self):
+        """
+        Return Target CPU architecture.
+
+        Return
+        ------
+        str
+            Target CPU
+        """
         return self.arch[self.arch.find('_') + 1:]
 
     def target_is_x86(self):
+        """
+        Return True if target CPU is x86 32 bits..
+
+        Return
+        ------
+        bool
+            CPU is x86 32 bits
+        """
         return self.target_cpu == 'x86'
 
     def current_is_x86(self):
+        """
+        Return True if current CPU is x86 32 bits..
+
+        Return
+        ------
+        bool
+            CPU is x86 32 bits
+        """
         return self.current_cpu == 'x86'
 
     def current_dir(self, hidex86=False, x64=False):
@@ -280,8 +290,8 @@
 
         Return
         ------
-        subfolder: str
-            '\target', or '' (see hidex86 parameter)
+        str
+            subfolder: '\target', or '' (see hidex86 parameter)
         """
         return (
             '' if (self.current_cpu == 'x86' and hidex86) else
@@ -302,8 +312,8 @@
 
         Return
         ------
-        subfolder: str
-            '\current', or '' (see hidex86 parameter)
+        str
+            subfolder: '\current', or '' (see hidex86 parameter)
         """
         return (
             '' if (self.target_cpu == 'x86' and hidex86) else
@@ -318,13 +328,13 @@
         Parameters
         ----------
         forcex86: bool
-            Use 'x86' as current architecture even if current acritecture is
+            Use 'x86' as current architecture even if current architecture is
             not x86.
 
         Return
         ------
-        subfolder: str
-            '' if target architecture is current architecture,
+        str
+            subfolder: '' if target architecture is current architecture,
             '\current_target' if not.
         """
         current = 'x86' if forcex86 else self.current_cpu
@@ -336,7 +346,7 @@
 
 class RegistryInfo:
     """
-    Microsoft Visual Studio related registry informations.
+    Microsoft Visual Studio related registry information.
 
     Parameters
     ----------
@@ -355,6 +365,11 @@
     def visualstudio(self):
         """
         Microsoft Visual Studio root registry key.
+
+        Return
+        ------
+        str
+            Registry key
         """
         return 'VisualStudio'
 
@@ -362,27 +377,47 @@
     def sxs(self):
         """
         Microsoft Visual Studio SxS registry key.
+
+        Return
+        ------
+        str
+            Registry key
         """
-        return os.path.join(self.visualstudio, 'SxS')
+        return join(self.visualstudio, 'SxS')
 
     @property
     def vc(self):
         """
         Microsoft Visual C++ VC7 registry key.
+
+        Return
+        ------
+        str
+            Registry key
         """
-        return os.path.join(self.sxs, 'VC7')
+        return join(self.sxs, 'VC7')
 
     @property
     def vs(self):
         """
         Microsoft Visual Studio VS7 registry key.
+
+        Return
+        ------
+        str
+            Registry key
         """
-        return os.path.join(self.sxs, 'VS7')
+        return join(self.sxs, 'VS7')
 
     @property
     def vc_for_python(self):
         """
         Microsoft Visual C++ for Python registry key.
+
+        Return
+        ------
+        str
+            Registry key
         """
         return r'DevDiv\VCForPython'
 
@@ -390,6 +425,11 @@
     def microsoft_sdk(self):
         """
         Microsoft SDK registry key.
+
+        Return
+        ------
+        str
+            Registry key
         """
         return 'Microsoft SDKs'
 
@@ -397,20 +437,35 @@
     def windows_sdk(self):
         """
         Microsoft Windows/Platform SDK registry key.
+
+        Return
+        ------
+        str
+            Registry key
         """
-        return os.path.join(self.microsoft_sdk, 'Windows')
+        return join(self.microsoft_sdk, 'Windows')
 
     @property
     def netfx_sdk(self):
         """
         Microsoft .NET Framework SDK registry key.
+
+        Return
+        ------
+        str
+            Registry key
         """
-        return os.path.join(self.microsoft_sdk, 'NETFXSDK')
+        return join(self.microsoft_sdk, 'NETFXSDK')
 
     @property
     def windows_kits_roots(self):
         """
         Microsoft Windows Kits Roots registry key.
+
+        Return
+        ------
+        str
+            Registry key
         """
         return r'Windows Kits\Installed Roots'
 
@@ -427,10 +482,11 @@
 
         Return
         ------
-        str: value
+        str
+            Registry key
         """
         node64 = '' if self.pi.current_is_x86() or x86 else 'Wow6432Node'
-        return os.path.join('Software', node64, 'Microsoft', key)
+        return join('Software', node64, 'Microsoft', key)
 
     def lookup(self, key, name):
         """
@@ -445,18 +501,19 @@
 
         Return
         ------
-        str: value
+        str
+            value
         """
-        KEY_READ = winreg.KEY_READ
+        key_read = winreg.KEY_READ
         openkey = winreg.OpenKey
         ms = self.microsoft
         for hkey in self.HKEYS:
             try:
-                bkey = openkey(hkey, ms(key), 0, KEY_READ)
+                bkey = openkey(hkey, ms(key), 0, key_read)
             except (OSError, IOError):
                 if not self.pi.current_is_x86():
                     try:
-                        bkey = openkey(hkey, ms(key, True), 0, KEY_READ)
+                        bkey = openkey(hkey, ms(key, True), 0, key_read)
                     except (OSError, IOError):
                         continue
                 else:
@@ -470,7 +527,7 @@
 
 class SystemInfo:
     """
-    Microsoft Windows and Visual Studio related system inormations.
+    Microsoft Windows and Visual Studio related system information.
 
     Parameters
     ----------
@@ -481,30 +538,52 @@
     """
 
     # Variables and properties in this class use originals CamelCase variables
-    # names from Microsoft source files for more easy comparaison.
-    WinDir = safe_env.get('WinDir', '')
-    ProgramFiles = safe_env.get('ProgramFiles', '')
-    ProgramFilesx86 = safe_env.get('ProgramFiles(x86)', ProgramFiles)
+    # names from Microsoft source files for more easy comparison.
+    WinDir = environ.get('WinDir', '')
+    ProgramFiles = environ.get('ProgramFiles', '')
+    ProgramFilesx86 = environ.get('ProgramFiles(x86)', ProgramFiles)
 
     def __init__(self, registry_info, vc_ver=None):
         self.ri = registry_info
         self.pi = self.ri.pi
-        self.vc_ver = vc_ver or self._find_latest_available_vc_ver()
 
-    def _find_latest_available_vc_ver(self):
-        try:
-            return self.find_available_vc_vers()[-1]
-        except IndexError:
-            err = 'No Microsoft Visual C++ version found'
-            raise distutils.errors.DistutilsPlatformError(err)
+        self.known_vs_paths = self.find_programdata_vs_vers()
 
-    def find_available_vc_vers(self):
+        # Except for VS15+, VC version is aligned with VS version
+        self.vs_ver = self.vc_ver = (
+                vc_ver or self._find_latest_available_vs_ver())
+
+    def _find_latest_available_vs_ver(self):
         """
-        Find all available Microsoft Visual C++ versions.
+        Find the latest VC version
+
+        Return
+        ------
+        float
+            version
+        """
+        reg_vc_vers = self.find_reg_vs_vers()
+
+        if not (reg_vc_vers or self.known_vs_paths):
+            raise distutils.errors.DistutilsPlatformError(
+                'No Microsoft Visual C++ version found')
+
+        vc_vers = set(reg_vc_vers)
+        vc_vers.update(self.known_vs_paths)
+        return sorted(vc_vers)[-1]
+
+    def find_reg_vs_vers(self):
+        """
+        Find Microsoft Visual Studio versions available in registry.
+
+        Return
+        ------
+        list of float
+            Versions
         """
         ms = self.ri.microsoft
         vckeys = (self.ri.vc, self.ri.vc_for_python, self.ri.vs)
-        vc_vers = []
+        vs_vers = []
         for hkey in self.ri.HKEYS:
             for key in vckeys:
                 try:
@@ -515,49 +594,109 @@
                 for i in range(values):
                     try:
                         ver = float(winreg.EnumValue(bkey, i)[0])
-                        if ver not in vc_vers:
-                            vc_vers.append(ver)
+                        if ver not in vs_vers:
+                            vs_vers.append(ver)
                     except ValueError:
                         pass
                 for i in range(subkeys):
                     try:
                         ver = float(winreg.EnumKey(bkey, i))
-                        if ver not in vc_vers:
-                            vc_vers.append(ver)
+                        if ver not in vs_vers:
+                            vs_vers.append(ver)
                     except ValueError:
                         pass
-        return sorted(vc_vers)
+        return sorted(vs_vers)
+
+    def find_programdata_vs_vers(self):
+        r"""
+        Find Visual studio 2017+ versions from information in
+        "C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances".
+
+        Return
+        ------
+        dict
+            float version as key, path as value.
+        """
+        vs_versions = {}
+        instances_dir = \
+            r'C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances'
+
+        try:
+            hashed_names = listdir(instances_dir)
+
+        except (OSError, IOError):
+            # Directory not exists with all Visual Studio versions
+            return vs_versions
+
+        for name in hashed_names:
+            try:
+                # Get VS installation path from "state.json" file
+                state_path = join(instances_dir, name, 'state.json')
+                with open(state_path, 'rt', encoding='utf-8') as state_file:
+                    state = json.load(state_file)
+                vs_path = state['installationPath']
+
+                # Raises OSError if this VS installation does not contain VC
+                listdir(join(vs_path, r'VC\Tools\MSVC'))
+
+                # Store version and path
+                # modified for python2 to encode paths
+                vs_versions[self._as_float_version(
+                    state['installationVersion'])] = vs_path.encode('utf-8')
+
+            except (OSError, IOError, KeyError):
+                # Skip if "state.json" file is missing or bad format
+                continue
+
+        return vs_versions
+
+    @staticmethod
+    def _as_float_version(version):
+        """
+        Return a string version as a simplified float version (major.minor)
+
+        Parameters
+        ----------
+        version: str
+            Version.
+
+        Return
+        ------
+        float
+            version
+        """
+        return float('.'.join(version.split('.')[:2]))
 
     @property
     def VSInstallDir(self):
         """
         Microsoft Visual Studio directory.
+
+        Return
+        ------
+        str
+            path
         """
         # Default path
-        name = 'Microsoft Visual Studio %0.1f' % self.vc_ver
-        default = os.path.join(self.ProgramFilesx86, name)
+        default = join(self.ProgramFilesx86,
+                       'Microsoft Visual Studio %0.1f' % self.vs_ver)
 
         # Try to get path from registry, if fail use default path
-        return self.ri.lookup(self.ri.vs, '%0.1f' % self.vc_ver) or default
+        return self.ri.lookup(self.ri.vs, '%0.1f' % self.vs_ver) or default
 
     @property
     def VCInstallDir(self):
         """
         Microsoft Visual C++ directory.
+
+        Return
+        ------
+        str
+            path
         """
-        self.VSInstallDir
+        path = self._guess_vc() or self._guess_vc_legacy()
 
-        guess_vc = self._guess_vc() or self._guess_vc_legacy()
-
-        # Try to get "VC++ for Python" path from registry as default path
-        reg_path = os.path.join(self.ri.vc_for_python, '%0.1f' % self.vc_ver)
-        python_vc = self.ri.lookup(reg_path, 'installdir')
-        default_vc = os.path.join(python_vc, 'VC') if python_vc else guess_vc
-
-        # Try to get path from registry, if fail use default path
-        path = self.ri.lookup(self.ri.vc, '%0.1f' % self.vc_ver) or default_vc
-
-        if not os.path.isdir(path):
+        if not isdir(path):
             msg = 'Microsoft Visual C++ directory not found'
             raise distutils.errors.DistutilsPlatformError(msg)
 
@@ -565,186 +704,256 @@
 
     def _guess_vc(self):
         """
-        Locate Visual C for 2017
+        Locate Visual C++ for VS2017+.
+
+        Return
+        ------
+        str
+            path
         """
-        if self.vc_ver <= 14.0:
-            return
+        if self.vs_ver <= 14.0:
+            return ''
 
-        default = r'VC\Tools\MSVC'
-        guess_vc = os.path.join(self.VSInstallDir, default)
+        try:
+            # First search in known VS paths
+            vs_dir = self.known_vs_paths[self.vs_ver]
+        except KeyError:
+            # Else, search with path from registry
+            vs_dir = self.VSInstallDir
+
+        guess_vc = join(vs_dir, r'VC\Tools\MSVC')
+
         # Subdir with VC exact version as name
         try:
-            vc_exact_ver = os.listdir(guess_vc)[-1]
-            return os.path.join(guess_vc, vc_exact_ver)
+            # Update the VC version with real one instead of VS version
+            vc_ver = listdir(guess_vc)[-1]
+            self.vc_ver = self._as_float_version(vc_ver)
+            return join(guess_vc, vc_ver)
         except (OSError, IOError, IndexError):
-            pass
+            return ''
 
     def _guess_vc_legacy(self):
         """
-        Locate Visual C for versions prior to 2017
+        Locate Visual C++ for versions prior to 2017.
+
+        Return
+        ------
+        str
+            path
         """
-        default = r'Microsoft Visual Studio %0.1f\VC' % self.vc_ver
-        return os.path.join(self.ProgramFilesx86, default)
+        default = join(self.ProgramFilesx86,
+                       r'Microsoft Visual Studio %0.1f\VC' % self.vs_ver)
+
+        # Try to get "VC++ for Python" path from registry as default path
+        reg_path = join(self.ri.vc_for_python, '%0.1f' % self.vs_ver)
+        python_vc = self.ri.lookup(reg_path, 'installdir')
+        default_vc = join(python_vc, 'VC') if python_vc else default
+
+        # Try to get path from registry, if fail use default path
+        return self.ri.lookup(self.ri.vc, '%0.1f' % self.vs_ver) or default_vc
 
     @property
     def WindowsSdkVersion(self):
         """
         Microsoft Windows SDK versions for specified MSVC++ version.
+
+        Return
+        ------
+        tuple of str
+            versions
         """
-        if self.vc_ver <= 9.0:
-            return ('7.0', '6.1', '6.0a')
-        elif self.vc_ver == 10.0:
-            return ('7.1', '7.0a')
-        elif self.vc_ver == 11.0:
-            return ('8.0', '8.0a')
-        elif self.vc_ver == 12.0:
-            return ('8.1', '8.1a')
-        elif self.vc_ver >= 14.0:
-            return ('10.0', '8.1')
+        if self.vs_ver <= 9.0:
+            return '7.0', '6.1', '6.0a'
+        elif self.vs_ver == 10.0:
+            return '7.1', '7.0a'
+        elif self.vs_ver == 11.0:
+            return '8.0', '8.0a'
+        elif self.vs_ver == 12.0:
+            return '8.1', '8.1a'
+        elif self.vs_ver >= 14.0:
+            return '10.0', '8.1'
 
     @property
     def WindowsSdkLastVersion(self):
         """
-        Microsoft Windows SDK last version
+        Microsoft Windows SDK last version.
+
+        Return
+        ------
+        str
+            version
         """
-        return self._use_last_dir_name(os.path.join(
-            self.WindowsSdkDir, 'lib'))
+        return self._use_last_dir_name(join(self.WindowsSdkDir, 'lib'))
 
     @property
     def WindowsSdkDir(self):
         """
         Microsoft Windows SDK directory.
+
+        Return
+        ------
+        str
+            path
         """
         sdkdir = ''
         for ver in self.WindowsSdkVersion:
             # Try to get it from registry
-            loc = os.path.join(self.ri.windows_sdk, 'v%s' % ver)
+            loc = join(self.ri.windows_sdk, 'v%s' % ver)
             sdkdir = self.ri.lookup(loc, 'installationfolder')
             if sdkdir:
                 break
-        if not sdkdir or not os.path.isdir(sdkdir):
+        if not sdkdir or not isdir(sdkdir):
             # Try to get "VC++ for Python" version from registry
-            path = os.path.join(self.ri.vc_for_python, '%0.1f' % self.vc_ver)
+            path = join(self.ri.vc_for_python, '%0.1f' % self.vc_ver)
             install_base = self.ri.lookup(path, 'installdir')
             if install_base:
-                sdkdir = os.path.join(install_base, 'WinSDK')
-        if not sdkdir or not os.path.isdir(sdkdir):
+                sdkdir = join(install_base, 'WinSDK')
+        if not sdkdir or not isdir(sdkdir):
             # If fail, use default new path
             for ver in self.WindowsSdkVersion:
                 intver = ver[:ver.rfind('.')]
-                path = r'Microsoft SDKs\Windows Kits\%s' % (intver)
-                d = os.path.join(self.ProgramFiles, path)
-                if os.path.isdir(d):
+                path = r'Microsoft SDKs\Windows Kits\%s' % intver
+                d = join(self.ProgramFiles, path)
+                if isdir(d):
                     sdkdir = d
-        if not sdkdir or not os.path.isdir(sdkdir):
+        if not sdkdir or not isdir(sdkdir):
             # If fail, use default old path
             for ver in self.WindowsSdkVersion:
                 path = r'Microsoft SDKs\Windows\v%s' % ver
-                d = os.path.join(self.ProgramFiles, path)
-                if os.path.isdir(d):
+                d = join(self.ProgramFiles, path)
+                if isdir(d):
                     sdkdir = d
         if not sdkdir:
             # If fail, use Platform SDK
-            sdkdir = os.path.join(self.VCInstallDir, 'PlatformSDK')
+            sdkdir = join(self.VCInstallDir, 'PlatformSDK')
         return sdkdir
 
     @property
     def WindowsSDKExecutablePath(self):
         """
         Microsoft Windows SDK executable directory.
+
+        Return
+        ------
+        str
+            path
         """
         # Find WinSDK NetFx Tools registry dir name
-        if self.vc_ver <= 11.0:
+        if self.vs_ver <= 11.0:
             netfxver = 35
             arch = ''
         else:
             netfxver = 40
-            hidex86 = True if self.vc_ver <= 12.0 else False
+            hidex86 = True if self.vs_ver <= 12.0 else False
             arch = self.pi.current_dir(x64=True, hidex86=hidex86)
         fx = 'WinSDK-NetFx%dTools%s' % (netfxver, arch.replace('\\', '-'))
 
-        # liste all possibles registry paths
+        # list all possibles registry paths
         regpaths = []
-        if self.vc_ver >= 14.0:
+        if self.vs_ver >= 14.0:
             for ver in self.NetFxSdkVersion:
-                regpaths += [os.path.join(self.ri.netfx_sdk, ver, fx)]
+                regpaths += [join(self.ri.netfx_sdk, ver, fx)]
 
         for ver in self.WindowsSdkVersion:
-            regpaths += [os.path.join(self.ri.windows_sdk, 'v%sA' % ver, fx)]
+            regpaths += [join(self.ri.windows_sdk, 'v%sA' % ver, fx)]
 
         # Return installation folder from the more recent path
         for path in regpaths:
             execpath = self.ri.lookup(path, 'installationfolder')
             if execpath:
-                break
-        return execpath
+                return execpath
 
     @property
     def FSharpInstallDir(self):
         """
         Microsoft Visual F# directory.
+
+        Return
+        ------
+        str
+            path
         """
-        path = r'%0.1f\Setup\F#' % self.vc_ver
-        path = os.path.join(self.ri.visualstudio, path)
+        path = join(self.ri.visualstudio, r'%0.1f\Setup\F#' % self.vs_ver)
         return self.ri.lookup(path, 'productdir') or ''
 
     @property
     def UniversalCRTSdkDir(self):
         """
         Microsoft Universal CRT SDK directory.
+
+        Return
+        ------
+        str
+            path
         """
         # Set Kit Roots versions for specified MSVC++ version
-        if self.vc_ver >= 14.0:
-            vers = ('10', '81')
-        else:
-            vers = ()
+        vers = ('10', '81') if self.vs_ver >= 14.0 else ()
 
         # Find path of the more recent Kit
         for ver in vers:
             sdkdir = self.ri.lookup(self.ri.windows_kits_roots,
                                     'kitsroot%s' % ver)
             if sdkdir:
-                break
-        return sdkdir or ''
+                return sdkdir or ''
 
     @property
     def UniversalCRTSdkLastVersion(self):
         """
-        Microsoft Universal C Runtime SDK last version
+        Microsoft Universal C Runtime SDK last version.
+
+        Return
+        ------
+        str
+            version
         """
-        return self._use_last_dir_name(os.path.join(
-            self.UniversalCRTSdkDir, 'lib'))
+        return self._use_last_dir_name(join(self.UniversalCRTSdkDir, 'lib'))
 
     @property
     def NetFxSdkVersion(self):
         """
         Microsoft .NET Framework SDK versions.
+
+        Return
+        ------
+        tuple of str
+            versions
         """
-        # Set FxSdk versions for specified MSVC++ version
-        if self.vc_ver >= 14.0:
-            return ('4.6.1', '4.6')
-        else:
-            return ()
+        # Set FxSdk versions for specified VS version
+        return (('4.7.2', '4.7.1', '4.7',
+                 '4.6.2', '4.6.1', '4.6',
+                 '4.5.2', '4.5.1', '4.5')
+                if self.vs_ver >= 14.0 else ())
 
     @property
     def NetFxSdkDir(self):
         """
         Microsoft .NET Framework SDK directory.
+
+        Return
+        ------
+        str
+            path
         """
+        sdkdir = ''
         for ver in self.NetFxSdkVersion:
-            loc = os.path.join(self.ri.netfx_sdk, ver)
+            loc = join(self.ri.netfx_sdk, ver)
             sdkdir = self.ri.lookup(loc, 'kitsinstallationfolder')
             if sdkdir:
                 break
-        return sdkdir or ''
+        return sdkdir
 
     @property
     def FrameworkDir32(self):
         """
         Microsoft .NET Framework 32bit directory.
+
+        Return
+        ------
+        str
+            path
         """
         # Default path
-        guess_fw = os.path.join(self.WinDir, r'Microsoft.NET\Framework')
+        guess_fw = join(self.WinDir, r'Microsoft.NET\Framework')
 
         # Try to get path from registry, if fail use default path
         return self.ri.lookup(self.ri.vc, 'frameworkdir32') or guess_fw
@@ -753,9 +962,14 @@
     def FrameworkDir64(self):
         """
         Microsoft .NET Framework 64bit directory.
+
+        Return
+        ------
+        str
+            path
         """
         # Default path
-        guess_fw = os.path.join(self.WinDir, r'Microsoft.NET\Framework64')
+        guess_fw = join(self.WinDir, r'Microsoft.NET\Framework64')
 
         # Try to get path from registry, if fail use default path
         return self.ri.lookup(self.ri.vc, 'frameworkdir64') or guess_fw
@@ -764,6 +978,11 @@
     def FrameworkVersion32(self):
         """
         Microsoft .NET Framework 32bit versions.
+
+        Return
+        ------
+        tuple of str
+            versions
         """
         return self._find_dot_net_versions(32)
 
@@ -771,6 +990,11 @@
     def FrameworkVersion64(self):
         """
         Microsoft .NET Framework 64bit versions.
+
+        Return
+        ------
+        tuple of str
+            versions
         """
         return self._find_dot_net_versions(64)
 
@@ -782,6 +1006,11 @@
         ----------
         bits: int
             Platform number of bits: 32 or 64.
+
+        Return
+        ------
+        tuple of str
+            versions
         """
         # Find actual .NET version in registry
         reg_ver = self.ri.lookup(self.ri.vc, 'frameworkver%d' % bits)
@@ -789,18 +1018,17 @@
         ver = reg_ver or self._use_last_dir_name(dot_net_dir, 'v') or ''
 
         # Set .NET versions for specified MSVC++ version
-        if self.vc_ver >= 12.0:
-            frameworkver = (ver, 'v4.0')
-        elif self.vc_ver >= 10.0:
-            frameworkver = ('v4.0.30319' if ver.lower()[:2] != 'v4' else ver,
-                            'v3.5')
-        elif self.vc_ver == 9.0:
-            frameworkver = ('v3.5', 'v2.0.50727')
-        if self.vc_ver == 8.0:
-            frameworkver = ('v3.0', 'v2.0.50727')
-        return frameworkver
+        if self.vs_ver >= 12.0:
+            return ver, 'v4.0'
+        elif self.vs_ver >= 10.0:
+            return 'v4.0.30319' if ver.lower()[:2] != 'v4' else ver, 'v3.5'
+        elif self.vs_ver == 9.0:
+            return 'v3.5', 'v2.0.50727'
+        elif self.vs_ver == 8.0:
+            return 'v3.0', 'v2.0.50727'
 
-    def _use_last_dir_name(self, path, prefix=''):
+    @staticmethod
+    def _use_last_dir_name(path, prefix=''):
         """
         Return name of the last dir in path or '' if no dir found.
 
@@ -809,12 +1037,17 @@
         path: str
             Use dirs in this path
         prefix: str
-            Use only dirs startings by this prefix
+            Use only dirs starting by this prefix
+
+        Return
+        ------
+        str
+            name
         """
         matching_dirs = (
             dir_name
-            for dir_name in reversed(os.listdir(path))
-            if os.path.isdir(os.path.join(path, dir_name)) and
+            for dir_name in reversed(listdir(path))
+            if isdir(join(path, dir_name)) and
             dir_name.startswith(prefix)
         )
         return next(matching_dirs, None) or ''
@@ -825,7 +1058,7 @@
     Return environment variables for specified Microsoft Visual C++ version
     and platform : Lib, Include, Path and libpath.
 
-    This function is compatible with Microsoft Visual C++ 9.0 to 14.0.
+    This function is compatible with Microsoft Visual C++ 9.0 to 14.X.
 
     Script created by analysing Microsoft environment configuration files like
     "vcvars[...].bat", "SetEnv.Cmd", "vcbuildtools.bat", ...
@@ -842,7 +1075,7 @@
     """
 
     # Variables and properties in this class use originals CamelCase variables
-    # names from Microsoft source files for more easy comparaison.
+    # names from Microsoft source files for more easy comparison.
 
     def __init__(self, arch, vc_ver=None, vc_min_ver=0):
         self.pi = PlatformInfo(arch)
@@ -854,204 +1087,254 @@
             raise distutils.errors.DistutilsPlatformError(err)
 
     @property
+    def vs_ver(self):
+        """
+        Microsoft Visual Studio.
+
+        Return
+        ------
+        float
+            version
+        """
+        return self.si.vs_ver
+
+    @property
     def vc_ver(self):
         """
         Microsoft Visual C++ version.
+
+        Return
+        ------
+        float
+            version
         """
         return self.si.vc_ver
 
     @property
     def VSTools(self):
         """
-        Microsoft Visual Studio Tools
+        Microsoft Visual Studio Tools.
+
+        Return
+        ------
+        list of str
+            paths
         """
         paths = [r'Common7\IDE', r'Common7\Tools']
 
-        if self.vc_ver >= 14.0:
+        if self.vs_ver >= 14.0:
             arch_subdir = self.pi.current_dir(hidex86=True, x64=True)
             paths += [r'Common7\IDE\CommonExtensions\Microsoft\TestWindow']
             paths += [r'Team Tools\Performance Tools']
             paths += [r'Team Tools\Performance Tools%s' % arch_subdir]
 
-        return [os.path.join(self.si.VSInstallDir, path) for path in paths]
+        return [join(self.si.VSInstallDir, path) for path in paths]
 
     @property
     def VCIncludes(self):
         """
-        Microsoft Visual C++ & Microsoft Foundation Class Includes
+        Microsoft Visual C++ & Microsoft Foundation Class Includes.
+
+        Return
+        ------
+        list of str
+            paths
         """
-        return [os.path.join(self.si.VCInstallDir, 'Include'),
-                os.path.join(self.si.VCInstallDir, r'ATLMFC\Include')]
+        return [join(self.si.VCInstallDir, 'Include'),
+                join(self.si.VCInstallDir, r'ATLMFC\Include')]
 
     @property
     def VCLibraries(self):
         """
-        Microsoft Visual C++ & Microsoft Foundation Class Libraries
+        Microsoft Visual C++ & Microsoft Foundation Class Libraries.
+
+        Return
+        ------
+        list of str
+            paths
         """
-        if self.vc_ver >= 15.0:
+        if self.vs_ver >= 15.0:
             arch_subdir = self.pi.target_dir(x64=True)
         else:
             arch_subdir = self.pi.target_dir(hidex86=True)
         paths = ['Lib%s' % arch_subdir, r'ATLMFC\Lib%s' % arch_subdir]
 
-        if self.vc_ver >= 14.0:
+        if self.vs_ver >= 14.0:
             paths += [r'Lib\store%s' % arch_subdir]
 
-        return [os.path.join(self.si.VCInstallDir, path) for path in paths]
+        return [join(self.si.VCInstallDir, path) for path in paths]
 
     @property
     def VCStoreRefs(self):
         """
-        Microsoft Visual C++ store references Libraries
+        Microsoft Visual C++ store references Libraries.
+
+        Return
+        ------
+        list of str
+            paths
         """
-        if self.vc_ver < 14.0:
+        if self.vs_ver < 14.0:
             return []
-        return [os.path.join(self.si.VCInstallDir, r'Lib\store\references')]
+        return [join(self.si.VCInstallDir, r'Lib\store\references')]
 
     @property
     def VCTools(self):
         """
-        Microsoft Visual C++ Tools
+        Microsoft Visual C++ Tools.
+
+        Return
+        ------
+        list of str
+            paths
         """
         si = self.si
-        tools = [os.path.join(si.VCInstallDir, 'VCPackages')]
+        tools = [join(si.VCInstallDir, 'VCPackages')]
 
-        forcex86 = True if self.vc_ver <= 10.0 else False
+        forcex86 = True if self.vs_ver <= 10.0 else False
         arch_subdir = self.pi.cross_dir(forcex86)
         if arch_subdir:
-            tools += [os.path.join(si.VCInstallDir, 'Bin%s' % arch_subdir)]
+            tools += [join(si.VCInstallDir, 'Bin%s' % arch_subdir)]
 
-        if self.vc_ver == 14.0:
+        if self.vs_ver == 14.0:
             path = 'Bin%s' % self.pi.current_dir(hidex86=True)
-            tools += [os.path.join(si.VCInstallDir, path)]
+            tools += [join(si.VCInstallDir, path)]
 
-        elif self.vc_ver >= 15.0:
+        elif self.vs_ver >= 15.0:
             host_dir = (r'bin\HostX86%s' if self.pi.current_is_x86() else
                         r'bin\HostX64%s')
-            tools += [os.path.join(
+            tools += [join(
                 si.VCInstallDir, host_dir % self.pi.target_dir(x64=True))]
 
             if self.pi.current_cpu != self.pi.target_cpu:
-                tools += [os.path.join(
+                tools += [join(
                     si.VCInstallDir, host_dir % self.pi.current_dir(x64=True))]
 
         else:
-            tools += [os.path.join(si.VCInstallDir, 'Bin')]
+            tools += [join(si.VCInstallDir, 'Bin')]
 
         return tools
 
     @property
     def OSLibraries(self):
         """
-        Microsoft Windows SDK Libraries
+        Microsoft Windows SDK Libraries.
+
+        Return
+        ------
+        list of str
+            paths
         """
-        if self.vc_ver <= 10.0:
+        if self.vs_ver <= 10.0:
             arch_subdir = self.pi.target_dir(hidex86=True, x64=True)
-            return [os.path.join(self.si.WindowsSdkDir, 'Lib%s' % arch_subdir)]
+            return [join(self.si.WindowsSdkDir, 'Lib%s' % arch_subdir)]
 
         else:
             arch_subdir = self.pi.target_dir(x64=True)
-            lib = os.path.join(self.si.WindowsSdkDir, 'lib')
+            lib = join(self.si.WindowsSdkDir, 'lib')
             libver = self._sdk_subdir
-            return [os.path.join(lib, '%sum%s' % (libver , arch_subdir))]
+            return [join(lib, '%sum%s' % (libver , arch_subdir))]
 
     @property
     def OSIncludes(self):
         """
-        Microsoft Windows SDK Include
+        Microsoft Windows SDK Include.
+
+        Return
+        ------
+        list of str
+            paths
         """
-        include = os.path.join(self.si.WindowsSdkDir, 'include')
+        include = join(self.si.WindowsSdkDir, 'include')
 
-        if self.vc_ver <= 10.0:
-            return [include, os.path.join(include, 'gl')]
+        if self.vs_ver <= 10.0:
+            return [include, join(include, 'gl')]
 
         else:
-            if self.vc_ver >= 14.0:
+            if self.vs_ver >= 14.0:
                 sdkver = self._sdk_subdir
             else:
                 sdkver = ''
-            return [os.path.join(include, '%sshared' % sdkver),
-                    os.path.join(include, '%sum' % sdkver),
-                    os.path.join(include, '%swinrt' % sdkver)]
+            return [join(include, '%sshared' % sdkver),
+                    join(include, '%sum' % sdkver),
+                    join(include, '%swinrt' % sdkver)]
 
     @property
     def OSLibpath(self):
         """
-        Microsoft Windows SDK Libraries Paths
+        Microsoft Windows SDK Libraries Paths.
+
+        Return
+        ------
+        list of str
+            paths
         """
-        ref = os.path.join(self.si.WindowsSdkDir, 'References')
+        ref = join(self.si.WindowsSdkDir, 'References')
         libpath = []
 
-        if self.vc_ver <= 9.0:
+        if self.vs_ver <= 9.0:
             libpath += self.OSLibraries
 
-        if self.vc_ver >= 11.0:
-            libpath += [os.path.join(ref, r'CommonConfiguration\Neutral')]
+        if self.vs_ver >= 11.0:
+            libpath += [join(ref, r'CommonConfiguration\Neutral')]
 
-        if self.vc_ver >= 14.0:
+        if self.vs_ver >= 14.0:
             libpath += [
                 ref,
-                os.path.join(self.si.WindowsSdkDir, 'UnionMetadata'),
-                os.path.join(
-                    ref,
-                    'Windows.Foundation.UniversalApiContract',
-                    '1.0.0.0',
-                ),
-                os.path.join(
-                    ref,
-                    'Windows.Foundation.FoundationContract',
-                    '1.0.0.0',
-                ),
-                os.path.join(
-                    ref,
-                    'Windows.Networking.Connectivity.WwanContract',
-                    '1.0.0.0',
-                ),
-                os.path.join(
-                    self.si.WindowsSdkDir,
-                    'ExtensionSDKs',
-                    'Microsoft.VCLibs',
-                    '%0.1f' % self.vc_ver,
-                    'References',
-                    'CommonConfiguration',
-                    'neutral',
-                ),
+                join(self.si.WindowsSdkDir, 'UnionMetadata'),
+                join(ref, 'Windows.Foundation.UniversalApiContract', '1.0.0.0'),
+                join(ref, 'Windows.Foundation.FoundationContract', '1.0.0.0'),
+                join(ref,'Windows.Networking.Connectivity.WwanContract',
+                     '1.0.0.0'),
+                join(self.si.WindowsSdkDir, 'ExtensionSDKs', 'Microsoft.VCLibs',
+                     '%0.1f' % self.vs_ver, 'References', 'CommonConfiguration',
+                     'neutral'),
             ]
         return libpath
 
     @property
     def SdkTools(self):
         """
-        Microsoft Windows SDK Tools
+        Microsoft Windows SDK Tools.
+
+        Return
+        ------
+        list of str
+            paths
         """
         return list(self._sdk_tools())
 
     def _sdk_tools(self):
         """
-        Microsoft Windows SDK Tools paths generator
+        Microsoft Windows SDK Tools paths generator.
+
+        Return
+        ------
+        generator of str
+            paths
         """
-        if self.vc_ver < 15.0:
-            bin_dir = 'Bin' if self.vc_ver <= 11.0 else r'Bin\x86'
-            yield os.path.join(self.si.WindowsSdkDir, bin_dir)
+        if self.vs_ver < 15.0:
+            bin_dir = 'Bin' if self.vs_ver <= 11.0 else r'Bin\x86'
+            yield join(self.si.WindowsSdkDir, bin_dir)
 
         if not self.pi.current_is_x86():
             arch_subdir = self.pi.current_dir(x64=True)
             path = 'Bin%s' % arch_subdir
-            yield os.path.join(self.si.WindowsSdkDir, path)
+            yield join(self.si.WindowsSdkDir, path)
 
-        if self.vc_ver == 10.0 or self.vc_ver == 11.0:
+        if self.vs_ver in (10.0, 11.0):
             if self.pi.target_is_x86():
                 arch_subdir = ''
             else:
                 arch_subdir = self.pi.current_dir(hidex86=True, x64=True)
             path = r'Bin\NETFX 4.0 Tools%s' % arch_subdir
-            yield os.path.join(self.si.WindowsSdkDir, path)
+            yield join(self.si.WindowsSdkDir, path)
 
-        elif self.vc_ver >= 15.0:
-            path = os.path.join(self.si.WindowsSdkDir, 'Bin')
+        elif self.vs_ver >= 15.0:
+            path = join(self.si.WindowsSdkDir, 'Bin')
             arch_subdir = self.pi.current_dir(x64=True)
             sdkver = self.si.WindowsSdkLastVersion
-            yield os.path.join(path, '%s%s' % (sdkver, arch_subdir))
+            yield join(path, '%s%s' % (sdkver, arch_subdir))
 
         if self.si.WindowsSDKExecutablePath:
             yield self.si.WindowsSDKExecutablePath
@@ -1059,7 +1342,12 @@
     @property
     def _sdk_subdir(self):
         """
-        Microsoft Windows SDK version subdir
+        Microsoft Windows SDK version subdir.
+
+        Return
+        ------
+        str
+            subdir
         """
         ucrtver = self.si.WindowsSdkLastVersion
         return ('%s\\' % ucrtver) if ucrtver else ''
@@ -1067,22 +1355,32 @@
     @property
     def SdkSetup(self):
         """
-        Microsoft Windows SDK Setup
+        Microsoft Windows SDK Setup.
+
+        Return
+        ------
+        list of str
+            paths
         """
-        if self.vc_ver > 9.0:
+        if self.vs_ver > 9.0:
             return []
 
-        return [os.path.join(self.si.WindowsSdkDir, 'Setup')]
+        return [join(self.si.WindowsSdkDir, 'Setup')]
 
     @property
     def FxTools(self):
         """
-        Microsoft .NET Framework Tools
+        Microsoft .NET Framework Tools.
+
+        Return
+        ------
+        list of str
+            paths
         """
         pi = self.pi
         si = self.si
 
-        if self.vc_ver <= 10.0:
+        if self.vs_ver <= 10.0:
             include32 = True
             include64 = not pi.target_is_x86() and not pi.current_is_x86()
         else:
@@ -1091,102 +1389,142 @@
 
         tools = []
         if include32:
-            tools += [os.path.join(si.FrameworkDir32, ver)
+            tools += [join(si.FrameworkDir32, ver)
                       for ver in si.FrameworkVersion32]
         if include64:
-            tools += [os.path.join(si.FrameworkDir64, ver)
+            tools += [join(si.FrameworkDir64, ver)
                       for ver in si.FrameworkVersion64]
         return tools
 
     @property
     def NetFxSDKLibraries(self):
         """
-        Microsoft .Net Framework SDK Libraries
+        Microsoft .Net Framework SDK Libraries.
+
+        Return
+        ------
+        list of str
+            paths
         """
-        if self.vc_ver < 14.0 or not self.si.NetFxSdkDir:
+        if self.vs_ver < 14.0 or not self.si.NetFxSdkDir:
             return []
 
         arch_subdir = self.pi.target_dir(x64=True)
-        return [os.path.join(self.si.NetFxSdkDir, r'lib\um%s' % arch_subdir)]
+        return [join(self.si.NetFxSdkDir, r'lib\um%s' % arch_subdir)]
 
     @property
     def NetFxSDKIncludes(self):
         """
-        Microsoft .Net Framework SDK Includes
+        Microsoft .Net Framework SDK Includes.
+
+        Return
+        ------
+        list of str
+            paths
         """
-        if self.vc_ver < 14.0 or not self.si.NetFxSdkDir:
+        if self.vs_ver < 14.0 or not self.si.NetFxSdkDir:
             return []
 
-        return [os.path.join(self.si.NetFxSdkDir, r'include\um')]
+        return [join(self.si.NetFxSdkDir, r'include\um')]
 
     @property
     def VsTDb(self):
         """
-        Microsoft Visual Studio Team System Database
+        Microsoft Visual Studio Team System Database.
+
+        Return
+        ------
+        list of str
+            paths
         """
-        return [os.path.join(self.si.VSInstallDir, r'VSTSDB\Deploy')]
+        return [join(self.si.VSInstallDir, r'VSTSDB\Deploy')]
 
     @property
     def MSBuild(self):
         """
-        Microsoft Build Engine
+        Microsoft Build Engine.
+
+        Return
+        ------
+        list of str
+            paths
         """
-        if self.vc_ver < 12.0:
+        if self.vs_ver < 12.0:
             return []
-        elif self.vc_ver < 15.0:
+        elif self.vs_ver < 15.0:
             base_path = self.si.ProgramFilesx86
             arch_subdir = self.pi.current_dir(hidex86=True)
         else:
             base_path = self.si.VSInstallDir
             arch_subdir = ''
 
-        path = r'MSBuild\%0.1f\bin%s' % (self.vc_ver, arch_subdir)
-        build = [os.path.join(base_path, path)]
+        path = r'MSBuild\%0.1f\bin%s' % (self.vs_ver, arch_subdir)
+        build = [join(base_path, path)]
 
-        if self.vc_ver >= 15.0:
+        if self.vs_ver >= 15.0:
             # Add Roslyn C# & Visual Basic Compiler
-            build += [os.path.join(base_path, path, 'Roslyn')]
+            build += [join(base_path, path, 'Roslyn')]
 
         return build
 
     @property
     def HTMLHelpWorkshop(self):
         """
-        Microsoft HTML Help Workshop
+        Microsoft HTML Help Workshop.
+
+        Return
+        ------
+        list of str
+            paths
         """
-        if self.vc_ver < 11.0:
+        if self.vs_ver < 11.0:
             return []
 
-        return [os.path.join(self.si.ProgramFilesx86, 'HTML Help Workshop')]
+        return [join(self.si.ProgramFilesx86, 'HTML Help Workshop')]
 
     @property
     def UCRTLibraries(self):
         """
-        Microsoft Universal C Runtime SDK Libraries
+        Microsoft Universal C Runtime SDK Libraries.
+
+        Return
+        ------
+        list of str
+            paths
         """
-        if self.vc_ver < 14.0:
+        if self.vs_ver < 14.0:
             return []
 
         arch_subdir = self.pi.target_dir(x64=True)
-        lib = os.path.join(self.si.UniversalCRTSdkDir, 'lib')
+        lib = join(self.si.UniversalCRTSdkDir, 'lib')
         ucrtver = self._ucrt_subdir
-        return [os.path.join(lib, '%sucrt%s' % (ucrtver, arch_subdir))]
+        return [join(lib, '%sucrt%s' % (ucrtver, arch_subdir))]
 
     @property
     def UCRTIncludes(self):
         """
-        Microsoft Universal C Runtime SDK Include
+        Microsoft Universal C Runtime SDK Include.
+
+        Return
+        ------
+        list of str
+            paths
         """
-        if self.vc_ver < 14.0:
+        if self.vs_ver < 14.0:
             return []
 
-        include = os.path.join(self.si.UniversalCRTSdkDir, 'include')
-        return [os.path.join(include, '%sucrt' % self._ucrt_subdir)]
+        include = join(self.si.UniversalCRTSdkDir, 'include')
+        return [join(include, '%sucrt' % self._ucrt_subdir)]
 
     @property
     def _ucrt_subdir(self):
         """
-        Microsoft Universal C Runtime SDK version subdir
+        Microsoft Universal C Runtime SDK version subdir.
+
+        Return
+        ------
+        str
+            subdir
         """
         ucrtver = self.si.UniversalCRTSdkLastVersion
         return ('%s\\' % ucrtver) if ucrtver else ''
@@ -1194,31 +1532,52 @@
     @property
     def FSharp(self):
         """
-        Microsoft Visual F#
+        Microsoft Visual F#.
+
+        Return
+        ------
+        list of str
+            paths
         """
-        if self.vc_ver < 11.0 and self.vc_ver > 12.0:
+        if 11.0 > self.vs_ver > 12.0:
             return []
 
-        return self.si.FSharpInstallDir
+        return [self.si.FSharpInstallDir]
 
     @property
     def VCRuntimeRedist(self):
         """
-        Microsoft Visual C++ runtime redistribuable dll
+        Microsoft Visual C++ runtime redistributable dll.
+
+        Return
+        ------
+        str
+            path
         """
-        arch_subdir = self.pi.target_dir(x64=True)
-        if self.vc_ver < 15:
-            redist_path = self.si.VCInstallDir
-            vcruntime = 'redist%s\\Microsoft.VC%d0.CRT\\vcruntime%d0.dll'
-        else:
-            redist_path = self.si.VCInstallDir.replace('\\Tools', '\\Redist')
-            vcruntime = 'onecore%s\\Microsoft.VC%d0.CRT\\vcruntime%d0.dll'
+        vcruntime = 'vcruntime%d0.dll' % self.vc_ver
+        arch_subdir = self.pi.target_dir(x64=True).strip('\\')
 
-        # Visual Studio 2017  is still Visual C++ 14.0
-        dll_ver = 14.0 if self.vc_ver == 15 else self.vc_ver
+        # Installation prefixes candidates
+        prefixes = []
+        tools_path = self.si.VCInstallDir
+        redist_path = dirname(tools_path.replace(r'\Tools', r'\Redist'))
+        if isdir(redist_path):
+            # Redist version may not be exactly the same as tools
+            redist_path = join(redist_path, listdir(redist_path)[-1])
+            prefixes += [redist_path, join(redist_path, 'onecore')]
 
-        vcruntime = vcruntime % (arch_subdir, self.vc_ver, dll_ver)
-        return os.path.join(redist_path, vcruntime)
+        prefixes += [join(tools_path, 'redist')]  # VS14 legacy path
+
+        # CRT directory
+        crt_dirs = ('Microsoft.VC%d.CRT' % (self.vc_ver * 10),
+                    # Sometime store in directory with VS version instead of VC
+                    'Microsoft.VC%d.CRT' % (int(self.vs_ver) * 10))
+
+        # vcruntime path
+        for prefix, crt_dir in itertools.product(prefixes, crt_dirs):
+            path = join(prefix, arch_subdir, crt_dir, vcruntime)
+            if isfile(path):
+                return path
 
     def return_env(self, exists=True):
         """
@@ -1228,6 +1587,11 @@
         ----------
         exists: bool
             It True, only return existing paths.
+
+        Return
+        ------
+        dict
+            environment
         """
         env = dict(
             include=self._build_paths('include',
@@ -1261,7 +1625,7 @@
                                     self.FSharp],
                                    exists),
         )
-        if self.vc_ver >= 14 and os.path.isfile(self.VCRuntimeRedist):
+        if self.vs_ver >= 14 and isfile(self.VCRuntimeRedist):
             env['py_vcruntime_redist'] = self.VCRuntimeRedist
         return env
 
@@ -1272,20 +1636,35 @@
         unique, extant, directories from those paths and from
         the environment variable. Raise an error if no paths
         are resolved.
+
+        Parameters
+        ----------
+        name: str
+            Environment variable name
+        spec_path_lists: list of str
+            Paths
+        exists: bool
+            It True, only return existing paths.
+
+        Return
+        ------
+        str
+            Pathsep-separated paths
         """
         # flatten spec_path_lists
         spec_paths = itertools.chain.from_iterable(spec_path_lists)
-        env_paths = safe_env.get(name, '').split(os.pathsep)
+        env_paths = environ.get(name, '').split(pathsep)
         paths = itertools.chain(spec_paths, env_paths)
-        extant_paths = list(filter(os.path.isdir, paths)) if exists else paths
+        extant_paths = list(filter(isdir, paths)) if exists else paths
         if not extant_paths:
             msg = "%s environment variable is empty" % name.upper()
             raise distutils.errors.DistutilsPlatformError(msg)
         unique_paths = self._unique_everseen(extant_paths)
-        return os.pathsep.join(unique_paths)
+        return pathsep.join(unique_paths)
 
     # from Python docs
-    def _unique_everseen(self, iterable, key=None):
+    @staticmethod
+    def _unique_everseen(iterable, key=None):
         """
         List unique elements, preserving order.
         Remember all elements ever seen.
diff --git a/rpython/translator/platform/windows.py b/rpython/translator/platform/windows.py
--- a/rpython/translator/platform/windows.py
+++ b/rpython/translator/platform/windows.py
@@ -5,6 +5,7 @@
 from rpython.translator.platform import CompilationError
 from rpython.translator.platform import log, _run_subprocess
 from rpython.translator.platform import Platform, posix
+import rpython.tool.setuptools_msvc as msvc
 
 import rpython
 rpydir = str(py.path.local(rpython.__file__).join('..'))
@@ -37,96 +38,37 @@
     return _get_compiler_type(cc, True)
 
 def _find_vcvarsall(version, x64flag):
-    import rpython.tool.setuptools_msvc as msvc
     if x64flag:
         arch = 'x64'
     else:
         arch = 'x86'
-    if version >= 140:
-        return msvc.msvc14_get_vc_env(arch)
-    else:
-        return msvc.msvc9_query_vcvarsall(version / 10.0, arch)
+    return msvc.EnvironmentInfo(arch, vc_ver=version/10.0).return_env()
 
-def _get_msvc_env(vsver, x64flag):
+def _get_msvc_env(vcver, x64flag):
     vcdict = None
     toolsdir = None
+    # use setuptools from python3 to find tools
     try:
-        toolsdir = os.environ['VS%sCOMNTOOLS' % vsver]
-    except KeyError:
-        # use setuptools from python3 to find tools
-        try:
-            vcdict = _find_vcvarsall(vsver, x64flag)
-        except ImportError as e:
-            if 'setuptools' in str(e):
-                log.error('is setuptools installed (perhaps try %s -mensurepip)?' % sys.executable)
-            log.error('looking for compiler %s raised exception "%s' % (vsver, str(e)))
-        except Exception as e:
-            log.error('looking for compiler %s raised exception "%s' % (vsver, str(e)))
+        available = msvc.EnvironmentInfo('x86').si.find_reg_vs_vers()
+        if vcver / 10.0 not in available:
+            log.error('Could not find vcver %i' % vcver)
             return None
-    else:
-        if x64flag:
-            vsinstalldir = os.path.abspath(os.path.join(toolsdir, '..', '..'))
-            vcinstalldir = os.path.join(vsinstalldir, 'VC')
-            vcbindir = os.path.join(vcinstalldir, 'BIN')
-            vcvars = os.path.join(vcbindir, 'amd64', 'vcvarsamd64.bat')
-        else:
-            vcvars = os.path.join(toolsdir, 'vsvars32.bat')
-            if not os.path.exists(vcvars):
-                # even msdn does not know which to run
-                # see https://msdn.microsoft.com/en-us/library/1700bbwd(v=vs.90).aspx
-                # which names both
-                vcvars = os.path.join(toolsdir, 'vcvars32.bat')
-
-        import subprocess
-        try:
-            popen = subprocess.Popen('"%s" & set' % (vcvars,),
-                                 stdout=subprocess.PIPE,
-                                 stderr=subprocess.PIPE)
-
-            stdout, stderr = popen.communicate()
-            if popen.wait() != 0 or stdout[:5].lower() == 'error':
-                log.msg('Running "%s" errored: \n\nstdout:\n%s\n\nstderr:\n%s' % (
-                    vcvars, stdout.split()[0], stderr))
-                return None
-            else:
-                log.msg('Running "%s" succeeded' %(vcvars,))
-        except Exception as e:
-            log.msg('Running "%s" failed: "%s"' % (vcvars, str(e)))
-            return None
-
-        stdout = stdout.replace("\r\n", "\n")
-        vcdict = {}
-        for line in stdout.split("\n"):
-            if '=' not in line:
-                continue
-            key, value = line.split('=', 1)
-            vcdict[key] = value
-    env = {}
-    for key, value in vcdict.items():
-        if key.upper() in ['PATH', 'INCLUDE', 'LIB']:
-            env[key.upper()] = value
-    if 'PATH' not in env:
-        log.msg('Did not find "PATH" in stdout\n%s' %(stdout))
-    if not _find_executable('mt.exe', env['PATH']):
-        # For some reason the sdk bin path is missing?
-        # put it together from some other env variables that happened to exist
-        # on the buildbot where this occurred
-        if 'WindowsSDKVersion' in vcdict and 'WindowsSdkDir' in vcdict:
-            binpath = vcdict['WindowsSdkDir'] + '\\bin\\' + vcdict['WindowsSDKVersion'] + 'x86'
-            env['PATH'] += ';' + binpath
-        if not _find_executable('mt.exe', env['PATH']):
-            log.msg('Could not find mt.exe on path=%s' % env['PATH'])
-            log.msg('Running vsver %s set this env' % vsver)
-            for key, value in vcdict.items():
-                log.msg('%s=%s' %(key, value))
-    log.msg("Updated environment with vsver %d, using x64 %s" % (vsver, x64flag,))
-    return env
+        vcdict = _find_vcvarsall(vcver, x64flag)
+        keys = vcdict.keys()
+        for k in keys:
+            if k.upper != k:
+                vcdict[k.upper()] = vcdict[k]
+                del vcdict[k]
+        log.error('Found vcver %i' % vcver)
+        return vcdict
+    except Exception as e:
+        log.error('looking for compiler %s raised exception "%s' % (vcver, str(e)))
+        return None
 
 def find_msvc_env(x64flag=False, ver0=None):
-    vcvers = [140, 141, 150, 90, 100]
+    vcvers = [150, 140, 141, 90, 100]
     if ver0 in vcvers:
         vcvers.insert(0, ver0)
-    errs = []
     for vsver in vcvers:
         env = _get_msvc_env(vsver, x64flag)
         if env is not None:


More information about the pypy-commit mailing list