codekingpro/portable-devtools
114k
1"""2Improved support for Microsoft Visual C++ compilers.3 4Known supported compilers:5--------------------------6Microsoft Visual C++ 14.X:7 Microsoft Visual C++ Build Tools 2015 (x86, x64, arm)8 Microsoft Visual Studio Build Tools 2017 (x86, x64, arm, arm64)9 Microsoft Visual Studio Build Tools 2019 (x86, x64, arm, arm64)10 11This may also support compilers shipped with compatible Visual Studio versions.12"""13 14import json15from os import listdir, pathsep16from os.path import join, isfile, isdir, dirname17from subprocess import CalledProcessError18import contextlib19import platform20import itertools21import subprocess22import distutils.errors23from typing import Dict, TYPE_CHECKING24from setuptools.extern.more_itertools import unique_everseen25 26# https://github.com/python/mypy/issues/816627if not TYPE_CHECKING and platform.system() == 'Windows':28 import winreg29 from os import environ30else:31 # Mock winreg and environ so the module can be imported on this platform.32 33 class winreg:34 HKEY_USERS = None35 HKEY_CURRENT_USER = None36 HKEY_LOCAL_MACHINE = None37 HKEY_CLASSES_ROOT = None38 39 environ: Dict[str, str] = dict()40 41 42def _msvc14_find_vc2015():43 """Python 3.8 "distutils/_msvccompiler.py" backport"""44 try:45 key = winreg.OpenKey(46 winreg.HKEY_LOCAL_MACHINE,47 r"Software\Microsoft\VisualStudio\SxS\VC7",48 0,49 winreg.KEY_READ | winreg.KEY_WOW64_32KEY,50 )51 except OSError:52 return None, None53 54 best_version = 055 best_dir = None56 with key:57 for i in itertools.count():58 try:59 v, vc_dir, vt = winreg.EnumValue(key, i)60 except OSError:61 break62 if v and vt == winreg.REG_SZ and isdir(vc_dir):63 try:64 version = int(float(v))65 except (ValueError, TypeError):66 continue67 if version >= 14 and version > best_version:68 best_version, best_dir = version, vc_dir69 return best_version, best_dir70 71 72def _msvc14_find_vc2017():73 """Python 3.8 "distutils/_msvccompiler.py" backport74 75 Returns "15, path" based on the result of invoking vswhere.exe76 If no install is found, returns "None, None"77 78 The version is returned to avoid unnecessarily changing the function79 result. It may be ignored when the path is not None.80 81 If vswhere.exe is not available, by definition, VS 2017 is not82 installed.83 """84 root = environ.get("ProgramFiles(x86)") or environ.get("ProgramFiles")85 if not root:86 return None, None87 88 suitable_components = (89 "Microsoft.VisualStudio.Component.VC.Tools.x86.x64",90 "Microsoft.VisualStudio.Workload.WDExpress",91 )92 93 for component in suitable_components:94 # Workaround for `-requiresAny` (only available on VS 2017 > 15.6)95 with contextlib.suppress(CalledProcessError, OSError, UnicodeDecodeError):96 path = (97 subprocess.check_output([98 join(root, "Microsoft Visual Studio", "Installer", "vswhere.exe"),99 "-latest",100 "-prerelease",101 "-requires",102 component,103 "-property",104 "installationPath",105 "-products",106 "*",107 ])108 .decode(encoding="mbcs", errors="strict")109 .strip()110 )111 112 path = join(path, "VC", "Auxiliary", "Build")113 if isdir(path):114 return 15, path115 116 return None, None # no suitable component found117 118 119PLAT_SPEC_TO_RUNTIME = {120 'x86': 'x86',121 'x86_amd64': 'x64',122 'x86_arm': 'arm',123 'x86_arm64': 'arm64',124}125 126 127def _msvc14_find_vcvarsall(plat_spec):128 """Python 3.8 "distutils/_msvccompiler.py" backport"""129 _, best_dir = _msvc14_find_vc2017()130 vcruntime = None131 132 if plat_spec in PLAT_SPEC_TO_RUNTIME:133 vcruntime_plat = PLAT_SPEC_TO_RUNTIME[plat_spec]134 else:135 vcruntime_plat = 'x64' if 'amd64' in plat_spec else 'x86'136 137 if best_dir:138 vcredist = join(139 best_dir,140 "..",141 "..",142 "redist",143 "MSVC",144 "**",145 vcruntime_plat,146 "Microsoft.VC14*.CRT",147 "vcruntime140.dll",148 )149 try:150 import glob151 152 vcruntime = glob.glob(vcredist, recursive=True)[-1]153 except (ImportError, OSError, LookupError):154 vcruntime = None155 156 if not best_dir:157 best_version, best_dir = _msvc14_find_vc2015()158 if best_version:159 vcruntime = join(160 best_dir,161 'redist',162 vcruntime_plat,163 "Microsoft.VC140.CRT",164 "vcruntime140.dll",165 )166 167 if not best_dir:168 return None, None169 170 vcvarsall = join(best_dir, "vcvarsall.bat")171 if not isfile(vcvarsall):172 return None, None173 174 if not vcruntime or not isfile(vcruntime):175 vcruntime = None176 177 return vcvarsall, vcruntime178 179 180def _msvc14_get_vc_env(plat_spec):181 """Python 3.8 "distutils/_msvccompiler.py" backport"""182 if "DISTUTILS_USE_SDK" in environ:183 return {key.lower(): value for key, value in environ.items()}184 185 vcvarsall, vcruntime = _msvc14_find_vcvarsall(plat_spec)186 if not vcvarsall:187 raise distutils.errors.DistutilsPlatformError("Unable to find vcvarsall.bat")188 189 try:190 out = subprocess.check_output(191 'cmd /u /c "{}" {} && set'.format(vcvarsall, plat_spec),192 stderr=subprocess.STDOUT,193 ).decode('utf-16le', errors='replace')194 except subprocess.CalledProcessError as exc:195 raise distutils.errors.DistutilsPlatformError(196 "Error executing {}".format(exc.cmd)197 ) from exc198 199 env = {200 key.lower(): value201 for key, _, value in (line.partition('=') for line in out.splitlines())202 if key and value203 }204 205 if vcruntime:206 env['py_vcruntime_redist'] = vcruntime207 return env208 209 210def msvc14_get_vc_env(plat_spec):211 """212 Patched "distutils._msvccompiler._get_vc_env" for support extra213 Microsoft Visual C++ 14.X compilers.214 215 Set environment without use of "vcvarsall.bat".216 217 Parameters218 ----------219 plat_spec: str220 Target architecture.221 222 Return223 ------224 dict225 environment226 """227 228 # Always use backport from CPython 3.8229 try:230 return _msvc14_get_vc_env(plat_spec)231 except distutils.errors.DistutilsPlatformError as exc:232 _augment_exception(exc, 14.0)233 raise234 235 236def _augment_exception(exc, version, arch=''):237 """238 Add details to the exception message to help guide the user239 as to what action will resolve it.240 """241 # Error if MSVC++ directory not found or environment not set242 message = exc.args[0]243 244 if "vcvarsall" in message.lower() or "visual c" in message.lower():245 # Special error message if MSVC++ not installed246 tmpl = 'Microsoft Visual C++ {version:0.1f} or greater is required.'247 message = tmpl.format(**locals())248 msdownload = 'www.microsoft.com/download/details.aspx?id=%d'249 if version == 9.0:250 if arch.lower().find('ia64') > -1:251 # For VC++ 9.0, if IA64 support is needed, redirect user252 # to Windows SDK 7.0.253 # Note: No download link available from Microsoft.254 message += ' Get it with "Microsoft Windows SDK 7.0"'255 else:256 # For VC++ 9.0 redirect user to Vc++ for Python 2.7 :257 # This redirection link is maintained by Microsoft.258 # Contact vspython@microsoft.com if it needs updating.259 message += ' Get it from http://aka.ms/vcpython27'260 elif version == 10.0:261 # For VC++ 10.0 Redirect user to Windows SDK 7.1262 message += ' Get it with "Microsoft Windows SDK 7.1": '263 message += msdownload % 8279264 elif version >= 14.0:265 # For VC++ 14.X Redirect user to latest Visual C++ Build Tools266 message += (267 ' Get it with "Microsoft C++ Build Tools": '268 r'https://visualstudio.microsoft.com'269 r'/visual-cpp-build-tools/'270 )271 272 exc.args = (message,)273 274 275class PlatformInfo:276 """277 Current and Target Architectures information.278 279 Parameters280 ----------281 arch: str282 Target architecture.283 """284 285 current_cpu = environ.get('processor_architecture', '').lower()286 287 def __init__(self, arch):288 self.arch = arch.lower().replace('x64', 'amd64')289 290 @property291 def target_cpu(self):292 """293 Return Target CPU architecture.294 295 Return296 ------297 str298 Target CPU299 """300 return self.arch[self.arch.find('_') + 1 :]301 302 def target_is_x86(self):303 """304 Return True if target CPU is x86 32 bits..305 306 Return307 ------308 bool309 CPU is x86 32 bits310 """311 return self.target_cpu == 'x86'312 313 def current_is_x86(self):314 """315 Return True if current CPU is x86 32 bits..316 317 Return318 ------319 bool320 CPU is x86 32 bits321 """322 return self.current_cpu == 'x86'323 324 def current_dir(self, hidex86=False, x64=False):325 """326 Current platform specific subfolder.327 328 Parameters329 ----------330 hidex86: bool331 return '' and not '\x86' if architecture is x86.332 x64: bool333 return '\x64' and not '\amd64' if architecture is amd64.334 335 Return336 ------337 str338 subfolder: '\target', or '' (see hidex86 parameter)339 """340 return (341 ''342 if (self.current_cpu == 'x86' and hidex86)343 else r'\x64'344 if (self.current_cpu == 'amd64' and x64)345 else r'\%s' % self.current_cpu346 )347 348 def target_dir(self, hidex86=False, x64=False):349 r"""350 Target platform specific subfolder.351 352 Parameters353 ----------354 hidex86: bool355 return '' and not '\x86' if architecture is x86.356 x64: bool357 return '\x64' and not '\amd64' if architecture is amd64.358 359 Return360 ------361 str362 subfolder: '\current', or '' (see hidex86 parameter)363 """364 return (365 ''366 if (self.target_cpu == 'x86' and hidex86)367 else r'\x64'368 if (self.target_cpu == 'amd64' and x64)369 else r'\%s' % self.target_cpu370 )371 372 def cross_dir(self, forcex86=False):373 r"""374 Cross platform specific subfolder.375 376 Parameters377 ----------378 forcex86: bool379 Use 'x86' as current architecture even if current architecture is380 not x86.381 382 Return383 ------384 str385 subfolder: '' if target architecture is current architecture,386 '\current_target' if not.387 """388 current = 'x86' if forcex86 else self.current_cpu389 return (390 ''391 if self.target_cpu == current392 else self.target_dir().replace('\\', '\\%s_' % current)393 )394 395 396class RegistryInfo:397 """398 Microsoft Visual Studio related registry information.399 400 Parameters401 ----------402 platform_info: PlatformInfo403 "PlatformInfo" instance.404 """405 406 HKEYS = (407 winreg.HKEY_USERS,408 winreg.HKEY_CURRENT_USER,409 winreg.HKEY_LOCAL_MACHINE,410 winreg.HKEY_CLASSES_ROOT,411 )412 413 def __init__(self, platform_info):414 self.pi = platform_info415 416 @property417 def visualstudio(self):418 """419 Microsoft Visual Studio root registry key.420 421 Return422 ------423 str424 Registry key425 """426 return 'VisualStudio'427 428 @property429 def sxs(self):430 """431 Microsoft Visual Studio SxS registry key.432 433 Return434 ------435 str436 Registry key437 """438 return join(self.visualstudio, 'SxS')439 440 @property441 def vc(self):442 """443 Microsoft Visual C++ VC7 registry key.444 445 Return446 ------447 str448 Registry key449 """450 return join(self.sxs, 'VC7')451 452 @property453 def vs(self):454 """455 Microsoft Visual Studio VS7 registry key.456 457 Return458 ------459 str460 Registry key461 """462 return join(self.sxs, 'VS7')463 464 @property465 def vc_for_python(self):466 """467 Microsoft Visual C++ for Python registry key.468 469 Return470 ------471 str472 Registry key473 """474 return r'DevDiv\VCForPython'475 476 @property477 def microsoft_sdk(self):478 """479 Microsoft SDK registry key.480 481 Return482 ------483 str484 Registry key485 """486 return 'Microsoft SDKs'487 488 @property489 def windows_sdk(self):490 """491 Microsoft Windows/Platform SDK registry key.492 493 Return494 ------495 str496 Registry key497 """498 return join(self.microsoft_sdk, 'Windows')499 500 @property501 def netfx_sdk(self):502 """503 Microsoft .NET Framework SDK registry key.504 505 Return506 ------507 str508 Registry key509 """510 return join(self.microsoft_sdk, 'NETFXSDK')511 512 @property513 def windows_kits_roots(self):514 """515 Microsoft Windows Kits Roots registry key.516 517 Return518 ------519 str520 Registry key521 """522 return r'Windows Kits\Installed Roots'523 524 def microsoft(self, key, x86=False):525 """526 Return key in Microsoft software registry.527 528 Parameters529 ----------530 key: str531 Registry key path where look.532 x86: str533 Force x86 software registry.534 535 Return536 ------537 str538 Registry key539 """540 node64 = '' if self.pi.current_is_x86() or x86 else 'Wow6432Node'541 return join('Software', node64, 'Microsoft', key)542 543 def lookup(self, key, name):544 """545 Look for values in registry in Microsoft software registry.546 547 Parameters548 ----------549 key: str550 Registry key path where look.551 name: str552 Value name to find.553 554 Return555 ------556 str557 value558 """559 key_read = winreg.KEY_READ560 openkey = winreg.OpenKey561 closekey = winreg.CloseKey562 ms = self.microsoft563 for hkey in self.HKEYS:564 bkey = None565 try:566 bkey = openkey(hkey, ms(key), 0, key_read)567 except OSError:568 if not self.pi.current_is_x86():569 try:570 bkey = openkey(hkey, ms(key, True), 0, key_read)571 except OSError:572 continue573 else:574 continue575 try:576 return winreg.QueryValueEx(bkey, name)[0]577 except OSError:578 pass579 finally:580 if bkey:581 closekey(bkey)582 return None583 584 585class SystemInfo:586 """587 Microsoft Windows and Visual Studio related system information.588 589 Parameters590 ----------591 registry_info: RegistryInfo592 "RegistryInfo" instance.593 vc_ver: float594 Required Microsoft Visual C++ version.595 """596 597 # Variables and properties in this class use originals CamelCase variables598 # names from Microsoft source files for more easy comparison.599 WinDir = environ.get('WinDir', '')600 ProgramFiles = environ.get('ProgramFiles', '')601 ProgramFilesx86 = environ.get('ProgramFiles(x86)', ProgramFiles)602 603 def __init__(self, registry_info, vc_ver=None):604 self.ri = registry_info605 self.pi = self.ri.pi606 607 self.known_vs_paths = self.find_programdata_vs_vers()608 609 # Except for VS15+, VC version is aligned with VS version610 self.vs_ver = self.vc_ver = vc_ver or self._find_latest_available_vs_ver()611 612 def _find_latest_available_vs_ver(self):613 """614 Find the latest VC version615 616 Return617 ------618 float619 version620 """621 reg_vc_vers = self.find_reg_vs_vers()622 623 if not (reg_vc_vers or self.known_vs_paths):624 raise distutils.errors.DistutilsPlatformError(625 'No Microsoft Visual C++ version found'626 )627 628 vc_vers = set(reg_vc_vers)629 vc_vers.update(self.known_vs_paths)630 return sorted(vc_vers)[-1]631 632 def find_reg_vs_vers(self):633 """634 Find Microsoft Visual Studio versions available in registry.635 636 Return637 ------638 list of float639 Versions640 """641 ms = self.ri.microsoft642 vckeys = (self.ri.vc, self.ri.vc_for_python, self.ri.vs)643 vs_vers = []644 for hkey, key in itertools.product(self.ri.HKEYS, vckeys):645 try:646 bkey = winreg.OpenKey(hkey, ms(key), 0, winreg.KEY_READ)647 except OSError:648 continue649 with bkey:650 subkeys, values, _ = winreg.QueryInfoKey(bkey)651 for i in range(values):652 with contextlib.suppress(ValueError):653 ver = float(winreg.EnumValue(bkey, i)[0])654 if ver not in vs_vers:655 vs_vers.append(ver)656 for i in range(subkeys):657 with contextlib.suppress(ValueError):658 ver = float(winreg.EnumKey(bkey, i))659 if ver not in vs_vers:660 vs_vers.append(ver)661 return sorted(vs_vers)662 663 def find_programdata_vs_vers(self):664 r"""665 Find Visual studio 2017+ versions from information in666 "C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances".667 668 Return669 ------670 dict671 float version as key, path as value.672 """673 vs_versions = {}674 instances_dir = r'C:\ProgramData\Microsoft\VisualStudio\Packages\_Instances'675 676 try:677 hashed_names = listdir(instances_dir)678 679 except OSError:680 # Directory not exists with all Visual Studio versions681 return vs_versions682 683 for name in hashed_names:684 try:685 # Get VS installation path from "state.json" file686 state_path = join(instances_dir, name, 'state.json')687 with open(state_path, 'rt', encoding='utf-8') as state_file:688 state = json.load(state_file)689 vs_path = state['installationPath']690 691 # Raises OSError if this VS installation does not contain VC692 listdir(join(vs_path, r'VC\Tools\MSVC'))693 694 # Store version and path695 vs_versions[self._as_float_version(state['installationVersion'])] = (696 vs_path697 )698 699 except (OSError, KeyError):700 # Skip if "state.json" file is missing or bad format701 continue702 703 return vs_versions704 705 @staticmethod706 def _as_float_version(version):707 """708 Return a string version as a simplified float version (major.minor)709 710 Parameters711 ----------712 version: str713 Version.714 715 Return716 ------717 float718 version719 """720 return float('.'.join(version.split('.')[:2]))721 722 @property723 def VSInstallDir(self):724 """725 Microsoft Visual Studio directory.726 727 Return728 ------729 str730 path731 """732 # Default path733 default = join(734 self.ProgramFilesx86, 'Microsoft Visual Studio %0.1f' % self.vs_ver735 )736 737 # Try to get path from registry, if fail use default path738 return self.ri.lookup(self.ri.vs, '%0.1f' % self.vs_ver) or default739 740 @property741 def VCInstallDir(self):742 """743 Microsoft Visual C++ directory.744 745 Return746 ------747 str748 path749 """750 path = self._guess_vc() or self._guess_vc_legacy()751 752 if not isdir(path):753 msg = 'Microsoft Visual C++ directory not found'754 raise distutils.errors.DistutilsPlatformError(msg)755 756 return path757 758 def _guess_vc(self):759 """760 Locate Visual C++ for VS2017+.761 762 Return763 ------764 str765 path766 """767 if self.vs_ver <= 14.0:768 return ''769 770 try:771 # First search in known VS paths772 vs_dir = self.known_vs_paths[self.vs_ver]773 except KeyError:774 # Else, search with path from registry775 vs_dir = self.VSInstallDir776 777 guess_vc = join(vs_dir, r'VC\Tools\MSVC')778 779 # Subdir with VC exact version as name780 try:781 # Update the VC version with real one instead of VS version782 vc_ver = listdir(guess_vc)[-1]783 self.vc_ver = self._as_float_version(vc_ver)784 return join(guess_vc, vc_ver)785 except (OSError, IndexError):786 return ''787 788 def _guess_vc_legacy(self):789 """790 Locate Visual C++ for versions prior to 2017.791 792 Return793 ------794 str795 path796 """797 default = join(798 self.ProgramFilesx86, r'Microsoft Visual Studio %0.1f\VC' % self.vs_ver799 )800 801 # Try to get "VC++ for Python" path from registry as default path802 reg_path = join(self.ri.vc_for_python, '%0.1f' % self.vs_ver)803 python_vc = self.ri.lookup(reg_path, 'installdir')804 default_vc = join(python_vc, 'VC') if python_vc else default805 806 # Try to get path from registry, if fail use default path807 return self.ri.lookup(self.ri.vc, '%0.1f' % self.vs_ver) or default_vc808 809 @property810 def WindowsSdkVersion(self):811 """812 Microsoft Windows SDK versions for specified MSVC++ version.813 814 Return815 ------816 tuple of str817 versions818 """819 if self.vs_ver <= 9.0:820 return '7.0', '6.1', '6.0a'821 elif self.vs_ver == 10.0:822 return '7.1', '7.0a'823 elif self.vs_ver == 11.0:824 return '8.0', '8.0a'825 elif self.vs_ver == 12.0:826 return '8.1', '8.1a'827 elif self.vs_ver >= 14.0:828 return '10.0', '8.1'829 return None830 831 @property832 def WindowsSdkLastVersion(self):833 """834 Microsoft Windows SDK last version.835 836 Return837 ------838 str839 version840 """841 return self._use_last_dir_name(join(self.WindowsSdkDir, 'lib'))842 843 @property # noqa: C901844 def WindowsSdkDir(self): # noqa: C901 # is too complex (12) # FIXME845 """846 Microsoft Windows SDK directory.847 848 Return849 ------850 str851 path852 """853 sdkdir = ''854 for ver in self.WindowsSdkVersion:855 # Try to get it from registry856 loc = join(self.ri.windows_sdk, 'v%s' % ver)857 sdkdir = self.ri.lookup(loc, 'installationfolder')858 if sdkdir:859 break860 if not sdkdir or not isdir(sdkdir):861 # Try to get "VC++ for Python" version from registry862 path = join(self.ri.vc_for_python, '%0.1f' % self.vc_ver)863 install_base = self.ri.lookup(path, 'installdir')864 if install_base:865 sdkdir = join(install_base, 'WinSDK')866 if not sdkdir or not isdir(sdkdir):867 # If fail, use default new path868 for ver in self.WindowsSdkVersion:869 intver = ver[: ver.rfind('.')]870 path = r'Microsoft SDKs\Windows Kits\%s' % intver871 d = join(self.ProgramFiles, path)872 if isdir(d):873 sdkdir = d874 if not sdkdir or not isdir(sdkdir):875 # If fail, use default old path876 for ver in self.WindowsSdkVersion:877 path = r'Microsoft SDKs\Windows\v%s' % ver878 d = join(self.ProgramFiles, path)879 if isdir(d):880 sdkdir = d881 if not sdkdir:882 # If fail, use Platform SDK883 sdkdir = join(self.VCInstallDir, 'PlatformSDK')884 return sdkdir885 886 @property887 def WindowsSDKExecutablePath(self):888 """889 Microsoft Windows SDK executable directory.890 891 Return892 ------893 str894 path895 """896 # Find WinSDK NetFx Tools registry dir name897 if self.vs_ver <= 11.0:898 netfxver = 35899 arch = ''900 else:901 netfxver = 40902 hidex86 = True if self.vs_ver <= 12.0 else False903 arch = self.pi.current_dir(x64=True, hidex86=hidex86)904 fx = 'WinSDK-NetFx%dTools%s' % (netfxver, arch.replace('\\', '-'))905 906 # list all possibles registry paths907 regpaths = []908 if self.vs_ver >= 14.0:909 for ver in self.NetFxSdkVersion:910 regpaths += [join(self.ri.netfx_sdk, ver, fx)]911 912 for ver in self.WindowsSdkVersion:913 regpaths += [join(self.ri.windows_sdk, 'v%sA' % ver, fx)]914 915 # Return installation folder from the more recent path916 for path in regpaths:917 execpath = self.ri.lookup(path, 'installationfolder')918 if execpath:919 return execpath920 921 return None922 923 @property924 def FSharpInstallDir(self):925 """926 Microsoft Visual F# directory.927 928 Return929 ------930 str931 path932 """933 path = join(self.ri.visualstudio, r'%0.1f\Setup\F#' % self.vs_ver)934 return self.ri.lookup(path, 'productdir') or ''935 936 @property937 def UniversalCRTSdkDir(self):938 """939 Microsoft Universal CRT SDK directory.940 941 Return942 ------943 str944 path945 """946 # Set Kit Roots versions for specified MSVC++ version947 vers = ('10', '81') if self.vs_ver >= 14.0 else ()948 949 # Find path of the more recent Kit950 for ver in vers:951 sdkdir = self.ri.lookup(self.ri.windows_kits_roots, 'kitsroot%s' % ver)952 if sdkdir:953 return sdkdir or ''954 955 return None956 957 @property958 def UniversalCRTSdkLastVersion(self):959 """960 Microsoft Universal C Runtime SDK last version.961 962 Return963 ------964 str965 version966 """967 return self._use_last_dir_name(join(self.UniversalCRTSdkDir, 'lib'))968 969 @property970 def NetFxSdkVersion(self):971 """972 Microsoft .NET Framework SDK versions.973 974 Return975 ------976 tuple of str977 versions978 """979 # Set FxSdk versions for specified VS version980 return (981 ('4.7.2', '4.7.1', '4.7', '4.6.2', '4.6.1', '4.6', '4.5.2', '4.5.1', '4.5')982 if self.vs_ver >= 14.0983 else ()984 )985 986 @property987 def NetFxSdkDir(self):988 """989 Microsoft .NET Framework SDK directory.990 991 Return992 ------993 str994 path995 """996 sdkdir = ''997 for ver in self.NetFxSdkVersion:998 loc = join(self.ri.netfx_sdk, ver)999 sdkdir = self.ri.lookup(loc, 'kitsinstallationfolder')1000 if sdkdir:1001 break1002 return sdkdir1003 1004 @property1005 def FrameworkDir32(self):1006 """1007 Microsoft .NET Framework 32bit directory.1008 1009 Return1010 ------1011 str1012 path1013 """1014 # Default path1015 guess_fw = join(self.WinDir, r'Microsoft.NET\Framework')1016 1017 # Try to get path from registry, if fail use default path1018 return self.ri.lookup(self.ri.vc, 'frameworkdir32') or guess_fw1019 1020 @property1021 def FrameworkDir64(self):1022 """1023 Microsoft .NET Framework 64bit directory.1024 1025 Return1026 ------1027 str1028 path1029 """1030 # Default path1031 guess_fw = join(self.WinDir, r'Microsoft.NET\Framework64')1032 1033 # Try to get path from registry, if fail use default path1034 return self.ri.lookup(self.ri.vc, 'frameworkdir64') or guess_fw1035 1036 @property1037 def FrameworkVersion32(self):1038 """1039 Microsoft .NET Framework 32bit versions.1040 1041 Return1042 ------1043 tuple of str1044 versions1045 """1046 return self._find_dot_net_versions(32)1047 1048 @property1049 def FrameworkVersion64(self):1050 """1051 Microsoft .NET Framework 64bit versions.1052 1053 Return1054 ------1055 tuple of str1056 versions1057 """1058 return self._find_dot_net_versions(64)1059 1060 def _find_dot_net_versions(self, bits):1061 """1062 Find Microsoft .NET Framework versions.1063 1064 Parameters1065 ----------1066 bits: int1067 Platform number of bits: 32 or 64.1068 1069 Return1070 ------1071 tuple of str1072 versions1073 """1074 # Find actual .NET version in registry1075 reg_ver = self.ri.lookup(self.ri.vc, 'frameworkver%d' % bits)1076 dot_net_dir = getattr(self, 'FrameworkDir%d' % bits)1077 ver = reg_ver or self._use_last_dir_name(dot_net_dir, 'v') or ''1078 1079 # Set .NET versions for specified MSVC++ version1080 if self.vs_ver >= 12.0:1081 return ver, 'v4.0'1082 elif self.vs_ver >= 10.0:1083 return 'v4.0.30319' if ver.lower()[:2] != 'v4' else ver, 'v3.5'1084 elif self.vs_ver == 9.0:1085 return 'v3.5', 'v2.0.50727'1086 elif self.vs_ver == 8.0:1087 return 'v3.0', 'v2.0.50727'1088 1089 @staticmethod1090 def _use_last_dir_name(path, prefix=''):1091 """1092 Return name of the last dir in path or '' if no dir found.1093 1094 Parameters1095 ----------1096 path: str1097 Use dirs in this path1098 prefix: str1099 Use only dirs starting by this prefix1100 1101 Return1102 ------1103 str1104 name1105 """1106 matching_dirs = (1107 dir_name1108 for dir_name in reversed(listdir(path))1109 if isdir(join(path, dir_name)) and dir_name.startswith(prefix)1110 )1111 return next(matching_dirs, None) or ''1112 1113 1114class EnvironmentInfo:1115 """1116 Return environment variables for specified Microsoft Visual C++ version1117 and platform : Lib, Include, Path and libpath.1118 1119 This function is compatible with Microsoft Visual C++ 9.0 to 14.X.1120 1121 Script created by analysing Microsoft environment configuration files like1122 "vcvars[...].bat", "SetEnv.Cmd", "vcbuildtools.bat", ...1123 1124 Parameters1125 ----------1126 arch: str1127 Target architecture.1128 vc_ver: float1129 Required Microsoft Visual C++ version. If not set, autodetect the last1130 version.1131 vc_min_ver: float1132 Minimum Microsoft Visual C++ version.1133 """1134 1135 # Variables and properties in this class use originals CamelCase variables1136 # names from Microsoft source files for more easy comparison.1137 1138 def __init__(self, arch, vc_ver=None, vc_min_ver=0):1139 self.pi = PlatformInfo(arch)1140 self.ri = RegistryInfo(self.pi)1141 self.si = SystemInfo(self.ri, vc_ver)1142 1143 if self.vc_ver < vc_min_ver:1144 err = 'No suitable Microsoft Visual C++ version found'1145 raise distutils.errors.DistutilsPlatformError(err)1146 1147 @property1148 def vs_ver(self):1149 """1150 Microsoft Visual Studio.1151 1152 Return1153 ------1154 float1155 version1156 """1157 return self.si.vs_ver1158 1159 @property1160 def vc_ver(self):1161 """1162 Microsoft Visual C++ version.1163 1164 Return1165 ------1166 float1167 version1168 """1169 return self.si.vc_ver1170 1171 @property1172 def VSTools(self):1173 """1174 Microsoft Visual Studio Tools.1175 1176 Return1177 ------1178 list of str1179 paths1180 """1181 paths = [r'Common7\IDE', r'Common7\Tools']1182 1183 if self.vs_ver >= 14.0:1184 arch_subdir = self.pi.current_dir(hidex86=True, x64=True)1185 paths += [r'Common7\IDE\CommonExtensions\Microsoft\TestWindow']1186 paths += [r'Team Tools\Performance Tools']1187 paths += [r'Team Tools\Performance Tools%s' % arch_subdir]1188 1189 return [join(self.si.VSInstallDir, path) for path in paths]1190 1191 @property1192 def VCIncludes(self):1193 """1194 Microsoft Visual C++ & Microsoft Foundation Class Includes.1195 1196 Return1197 ------1198 list of str1199 paths1200 """