1""" 2Improved support for Microsoft Visual C++ compilers. 3 4Known supported compilers: 5-------------------------- 6Microsoft Visual C++ 9.0: 7 Microsoft Visual C++ Compiler for Python 2.7 (x86, amd64) 8 Microsoft Windows SDK 6.1 (x86, x64, ia64) 9 Microsoft Windows SDK 7.0 (x86, x64, ia64) 10 11Microsoft Visual C++ 10.0: 12 Microsoft Windows SDK 7.1 (x86, x64, ia64) 13 14Microsoft Visual C++ 14.0: 15 Microsoft Visual C++ Build Tools 2015 (x86, x64, arm) 16 Microsoft Visual Studio 2017 (x86, x64, arm, arm64) 17 Microsoft Visual Studio Build Tools 2017 (x86, x64, arm, arm64) 18""" 19 20import os 21import sys 22import platform 23import itertools 24import distutils.errors 25from setuptools.extern.packaging.version import LegacyVersion 26 27from setuptools.extern.six.moves import filterfalse 28 29from .monkey import get_unpatched 30 31if platform.system() == 'Windows': 32 from setuptools.extern.six.moves import winreg 33 safe_env = os.environ 34else: 35 """ 36 Mock winreg and environ so the module can be imported 37 on this platform. 38 """ 39 40 class winreg: 41 HKEY_USERS = None 42 HKEY_CURRENT_USER = None 43 HKEY_LOCAL_MACHINE = None 44 HKEY_CLASSES_ROOT = None 45 46 safe_env = dict() 47 48_msvc9_suppress_errors = ( 49 # msvc9compiler isn't available on some platforms 50 ImportError, 51 52 # msvc9compiler raises DistutilsPlatformError in some 53 # environments. See #1118. 54 distutils.errors.DistutilsPlatformError, 55) 56 57try: 58 from distutils.msvc9compiler import Reg 59except _msvc9_suppress_errors: 60 pass 61 62 63def msvc9_find_vcvarsall(version): 64 """ 65 Patched "distutils.msvc9compiler.find_vcvarsall" to use the standalone 66 compiler build for Python (VCForPython). Fall back to original behavior 67 when the standalone compiler is not available. 68 69 Redirect the path of "vcvarsall.bat". 70 71 Known supported compilers 72 ------------------------- 73 Microsoft Visual C++ 9.0: 74 Microsoft Visual C++ Compiler for Python 2.7 (x86, amd64) 75 76 Parameters 77 ---------- 78 version: float 79 Required Microsoft Visual C++ version. 80 81 Return 82 ------ 83 vcvarsall.bat path: str 84 """ 85 VC_BASE = r'Software\%sMicrosoft\DevDiv\VCForPython\%0.1f' 86 key = VC_BASE % ('', version) 87 try: 88 # Per-user installs register the compiler path here 89 productdir = Reg.get_value(key, "installdir") 90 except KeyError: 91 try: 92 # All-user installs on a 64-bit system register here 93 key = VC_BASE % ('Wow6432Node\\', version) 94 productdir = Reg.get_value(key, "installdir") 95 except KeyError: 96 productdir = None 97 98 if productdir: 99 vcvarsall = os.path.os.path.join(productdir, "vcvarsall.bat") 100 if os.path.isfile(vcvarsall): 101 return vcvarsall 102 103 return get_unpatched(msvc9_find_vcvarsall)(version) 104 105 106def msvc9_query_vcvarsall(ver, arch='x86', *args, **kwargs): 107 """ 108 Patched "distutils.msvc9compiler.query_vcvarsall" for support extra 109 compilers. 110 111 Set environment without use of "vcvarsall.bat". 112 113 Known supported compilers 114 ------------------------- 115 Microsoft Visual C++ 9.0: 116 Microsoft Visual C++ Compiler for Python 2.7 (x86, amd64) 117 Microsoft Windows SDK 6.1 (x86, x64, ia64) 118 Microsoft Windows SDK 7.0 (x86, x64, ia64) 119 120 Microsoft Visual C++ 10.0: 121 Microsoft Windows SDK 7.1 (x86, x64, ia64) 122 123 Parameters 124 ---------- 125 ver: float 126 Required Microsoft Visual C++ version. 127 arch: str 128 Target architecture. 129 130 Return 131 ------ 132 environment: dict 133 """ 134 # Try to get environement from vcvarsall.bat (Classical way) 135 try: 136 orig = get_unpatched(msvc9_query_vcvarsall) 137 return orig(ver, arch, *args, **kwargs) 138 except distutils.errors.DistutilsPlatformError: 139 # Pass error if Vcvarsall.bat is missing 140 pass 141 except ValueError: 142 # Pass error if environment not set after executing vcvarsall.bat 143 pass 144 145 # If error, try to set environment directly 146 try: 147 return EnvironmentInfo(arch, ver).return_env() 148 except distutils.errors.DistutilsPlatformError as exc: 149 _augment_exception(exc, ver, arch) 150 raise 151 152 153def msvc14_get_vc_env(plat_spec): 154 """ 155 Patched "distutils._msvccompiler._get_vc_env" for support extra 156 compilers. 157 158 Set environment without use of "vcvarsall.bat". 159 160 Known supported compilers 161 ------------------------- 162 Microsoft Visual C++ 14.0: 163 Microsoft Visual C++ Build Tools 2015 (x86, x64, arm) 164 Microsoft Visual Studio 2017 (x86, x64, arm, arm64) 165 Microsoft Visual Studio Build Tools 2017 (x86, x64, arm, arm64) 166 167 Parameters 168 ---------- 169 plat_spec: str 170 Target architecture. 171 172 Return 173 ------ 174 environment: dict 175 """ 176 # Try to get environment from vcvarsall.bat (Classical way) 177 try: 178 return get_unpatched(msvc14_get_vc_env)(plat_spec) 179 except distutils.errors.DistutilsPlatformError: 180 # Pass error Vcvarsall.bat is missing 181 pass 182 183 # If error, try to set environment directly 184 try: 185 return EnvironmentInfo(plat_spec, vc_min_ver=14.0).return_env() 186 except distutils.errors.DistutilsPlatformError as exc: 187 _augment_exception(exc, 14.0) 188 raise 189 190 191def msvc14_gen_lib_options(*args, **kwargs): 192 """ 193 Patched "distutils._msvccompiler.gen_lib_options" for fix 194 compatibility between "numpy.distutils" and "distutils._msvccompiler" 195 (for Numpy < 1.11.2) 196 """ 197 if "numpy.distutils" in sys.modules: 198 import numpy as np 199 if LegacyVersion(np.__version__) < LegacyVersion('1.11.2'): 200 return np.distutils.ccompiler.gen_lib_options(*args, **kwargs) 201 return get_unpatched(msvc14_gen_lib_options)(*args, **kwargs) 202 203 204def _augment_exception(exc, version, arch=''): 205 """ 206 Add details to the exception message to help guide the user 207 as to what action will resolve it. 208 """ 209 # Error if MSVC++ directory not found or environment not set 210 message = exc.args[0] 211 212 if "vcvarsall" in message.lower() or "visual c" in message.lower(): 213 # Special error message if MSVC++ not installed 214 tmpl = 'Microsoft Visual C++ {version:0.1f} is required.' 215 message = tmpl.format(**locals()) 216 msdownload = 'www.microsoft.com/download/details.aspx?id=%d' 217 if version == 9.0: 218 if arch.lower().find('ia64') > -1: 219 # For VC++ 9.0, if IA64 support is needed, redirect user 220 # to Windows SDK 7.0 221 message += ' Get it with "Microsoft Windows SDK 7.0": ' 222 message += msdownload % 3138 223 else: 224 # For VC++ 9.0 redirect user to Vc++ for Python 2.7 : 225 # This redirection link is maintained by Microsoft. 226 # Contact vspython@microsoft.com if it needs updating. 227 message += ' Get it from http://aka.ms/vcpython27' 228 elif version == 10.0: 229 # For VC++ 10.0 Redirect user to Windows SDK 7.1 230 message += ' Get it with "Microsoft Windows SDK 7.1": ' 231 message += msdownload % 8279 232 elif version >= 14.0: 233 # For VC++ 14.0 Redirect user to Visual C++ Build Tools 234 message += (' Get it with "Microsoft Visual C++ Build Tools": ' 235 r'http://landinghub.visualstudio.com/' 236 'visual-cpp-build-tools') 237 238 exc.args = (message, ) 239 240 241class PlatformInfo: 242 """ 243 Current and Target Architectures informations. 244 245 Parameters 246 ---------- 247 arch: str 248 Target architecture. 249 """ 250 current_cpu = safe_env.get('processor_architecture', '').lower() 251 252 def __init__(self, arch): 253 self.arch = arch.lower().replace('x64', 'amd64') 254 255 @property 256 def target_cpu(self): 257 return self.arch[self.arch.find('_') + 1:] 258 259 def target_is_x86(self): 260 return self.target_cpu == 'x86' 261 262 def current_is_x86(self): 263 return self.current_cpu == 'x86' 264 265 def current_dir(self, hidex86=False, x64=False): 266 """ 267 Current platform specific subfolder. 268 269 Parameters 270 ---------- 271 hidex86: bool 272 return '' and not '\x86' if architecture is x86. 273 x64: bool 274 return '\x64' and not '\amd64' if architecture is amd64. 275 276 Return 277 ------ 278 subfolder: str 279 '\target', or '' (see hidex86 parameter) 280 """ 281 return ( 282 '' if (self.current_cpu == 'x86' and hidex86) else 283 r'\x64' if (self.current_cpu == 'amd64' and x64) else 284 r'\%s' % self.current_cpu 285 ) 286 287 def target_dir(self, hidex86=False, x64=False): 288 r""" 289 Target platform specific subfolder. 290 291 Parameters 292 ---------- 293 hidex86: bool 294 return '' and not '\x86' if architecture is x86. 295 x64: bool 296 return '\x64' and not '\amd64' if architecture is amd64. 297 298 Return 299 ------ 300 subfolder: str 301 '\current', or '' (see hidex86 parameter) 302 """ 303 return ( 304 '' if (self.target_cpu == 'x86' and hidex86) else 305 r'\x64' if (self.target_cpu == 'amd64' and x64) else 306 r'\%s' % self.target_cpu 307 ) 308 309 def cross_dir(self, forcex86=False): 310 r""" 311 Cross platform specific subfolder. 312 313 Parameters 314 ---------- 315 forcex86: bool 316 Use 'x86' as current architecture even if current acritecture is 317 not x86. 318 319 Return 320 ------ 321 subfolder: str 322 '' if target architecture is current architecture, 323 '\current_target' if not. 324 """ 325 current = 'x86' if forcex86 else self.current_cpu 326 return ( 327 '' if self.target_cpu == current else 328 self.target_dir().replace('\\', '\\%s_' % current) 329 ) 330 331 332class RegistryInfo: 333 """ 334 Microsoft Visual Studio related registry informations. 335 336 Parameters 337 ---------- 338 platform_info: PlatformInfo 339 "PlatformInfo" instance. 340 """ 341 HKEYS = (winreg.HKEY_USERS, 342 winreg.HKEY_CURRENT_USER, 343 winreg.HKEY_LOCAL_MACHINE, 344 winreg.HKEY_CLASSES_ROOT) 345 346 def __init__(self, platform_info): 347 self.pi = platform_info 348 349 @property 350 def visualstudio(self): 351 """ 352 Microsoft Visual Studio root registry key. 353 """ 354 return 'VisualStudio' 355 356 @property 357 def sxs(self): 358 """ 359 Microsoft Visual Studio SxS registry key. 360 """ 361 return os.path.join(self.visualstudio, 'SxS') 362 363 @property 364 def vc(self): 365 """ 366 Microsoft Visual C++ VC7 registry key. 367 """ 368 return os.path.join(self.sxs, 'VC7') 369 370 @property 371 def vs(self): 372 """ 373 Microsoft Visual Studio VS7 registry key. 374 """ 375 return os.path.join(self.sxs, 'VS7') 376 377 @property 378 def vc_for_python(self): 379 """ 380 Microsoft Visual C++ for Python registry key. 381 """ 382 return r'DevDiv\VCForPython' 383 384 @property 385 def microsoft_sdk(self): 386 """ 387 Microsoft SDK registry key. 388 """ 389 return 'Microsoft SDKs' 390 391 @property 392 def windows_sdk(self): 393 """ 394 Microsoft Windows/Platform SDK registry key. 395 """ 396 return os.path.join(self.microsoft_sdk, 'Windows') 397 398 @property 399 def netfx_sdk(self): 400 """ 401 Microsoft .NET Framework SDK registry key. 402 """ 403 return os.path.join(self.microsoft_sdk, 'NETFXSDK') 404 405 @property 406 def windows_kits_roots(self): 407 """ 408 Microsoft Windows Kits Roots registry key. 409 """ 410 return r'Windows Kits\Installed Roots' 411 412 def microsoft(self, key, x86=False): 413 """ 414 Return key in Microsoft software registry. 415 416 Parameters 417 ---------- 418 key: str 419 Registry key path where look. 420 x86: str 421 Force x86 software registry. 422 423 Return 424 ------ 425 str: value 426 """ 427 node64 = '' if self.pi.current_is_x86() or x86 else 'Wow6432Node' 428 return os.path.join('Software', node64, 'Microsoft', key) 429 430 def lookup(self, key, name): 431 """ 432 Look for values in registry in Microsoft software registry. 433 434 Parameters 435 ---------- 436 key: str 437 Registry key path where look. 438 name: str 439 Value name to find. 440 441 Return 442 ------ 443 str: value 444 """ 445 KEY_READ = winreg.KEY_READ 446 openkey = winreg.OpenKey 447 ms = self.microsoft 448 for hkey in self.HKEYS: 449 try: 450 bkey = openkey(hkey, ms(key), 0, KEY_READ) 451 except (OSError, IOError): 452 if not self.pi.current_is_x86(): 453 try: 454 bkey = openkey(hkey, ms(key, True), 0, KEY_READ) 455 except (OSError, IOError): 456 continue 457 else: 458 continue 459 try: 460 return winreg.QueryValueEx(bkey, name)[0] 461 except (OSError, IOError): 462 pass 463 464 465class SystemInfo: 466 """ 467 Microsoft Windows and Visual Studio related system inormations. 468 469 Parameters 470 ---------- 471 registry_info: RegistryInfo 472 "RegistryInfo" instance. 473 vc_ver: float 474 Required Microsoft Visual C++ version. 475 """ 476 477 # Variables and properties in this class use originals CamelCase variables 478 # names from Microsoft source files for more easy comparaison. 479 WinDir = safe_env.get('WinDir', '') 480 ProgramFiles = safe_env.get('ProgramFiles', '') 481 ProgramFilesx86 = safe_env.get('ProgramFiles(x86)', ProgramFiles) 482 483 def __init__(self, registry_info, vc_ver=None): 484 self.ri = registry_info 485 self.pi = self.ri.pi 486 self.vc_ver = vc_ver or self._find_latest_available_vc_ver() 487 488 def _find_latest_available_vc_ver(self): 489 try: 490 return self.find_available_vc_vers()[-1] 491 except IndexError: 492 err = 'No Microsoft Visual C++ version found' 493 raise distutils.errors.DistutilsPlatformError(err) 494 495 def find_available_vc_vers(self): 496 """ 497 Find all available Microsoft Visual C++ versions. 498 """ 499 ms = self.ri.microsoft 500 vckeys = (self.ri.vc, self.ri.vc_for_python, self.ri.vs) 501 vc_vers = [] 502 for hkey in self.ri.HKEYS: 503 for key in vckeys: 504 try: 505 bkey = winreg.OpenKey(hkey, ms(key), 0, winreg.KEY_READ) 506 except (OSError, IOError): 507 continue 508 subkeys, values, _ = winreg.QueryInfoKey(bkey) 509 for i in range(values): 510 try: 511 ver = float(winreg.EnumValue(bkey, i)[0]) 512 if ver not in vc_vers: 513 vc_vers.append(ver) 514 except ValueError: 515 pass 516 for i in range(subkeys): 517 try: 518 ver = float(winreg.EnumKey(bkey, i)) 519 if ver not in vc_vers: 520 vc_vers.append(ver) 521 except ValueError: 522 pass 523 return sorted(vc_vers) 524 525 @property 526 def VSInstallDir(self): 527 """ 528 Microsoft Visual Studio directory. 529 """ 530 # Default path 531 name = 'Microsoft Visual Studio %0.1f' % self.vc_ver 532 default = os.path.join(self.ProgramFilesx86, name) 533 534 # Try to get path from registry, if fail use default path 535 return self.ri.lookup(self.ri.vs, '%0.1f' % self.vc_ver) or default 536 537 @property 538 def VCInstallDir(self): 539 """ 540 Microsoft Visual C++ directory. 541 """ 542 self.VSInstallDir 543 544 guess_vc = self._guess_vc() or self._guess_vc_legacy() 545 546 # Try to get "VC++ for Python" path from registry as default path 547 reg_path = os.path.join(self.ri.vc_for_python, '%0.1f' % self.vc_ver) 548 python_vc = self.ri.lookup(reg_path, 'installdir') 549 default_vc = os.path.join(python_vc, 'VC') if python_vc else guess_vc 550 551 # Try to get path from registry, if fail use default path 552 path = self.ri.lookup(self.ri.vc, '%0.1f' % self.vc_ver) or default_vc 553 554 if not os.path.isdir(path): 555 msg = 'Microsoft Visual C++ directory not found' 556 raise distutils.errors.DistutilsPlatformError(msg) 557 558 return path 559 560 def _guess_vc(self): 561 """ 562 Locate Visual C for 2017 563 """ 564 if self.vc_ver <= 14.0: 565 return 566 567 default = r'VC\Tools\MSVC' 568 guess_vc = os.path.join(self.VSInstallDir, default) 569 # Subdir with VC exact version as name 570 try: 571 vc_exact_ver = os.listdir(guess_vc)[-1] 572 return os.path.join(guess_vc, vc_exact_ver) 573 except (OSError, IOError, IndexError): 574 pass 575 576 def _guess_vc_legacy(self): 577 """ 578 Locate Visual C for versions prior to 2017 579 """ 580 default = r'Microsoft Visual Studio %0.1f\VC' % self.vc_ver 581 return os.path.join(self.ProgramFilesx86, default) 582 583 @property 584 def WindowsSdkVersion(self): 585 """ 586 Microsoft Windows SDK versions for specified MSVC++ version. 587 """ 588 if self.vc_ver <= 9.0: 589 return ('7.0', '6.1', '6.0a') 590 elif self.vc_ver == 10.0: 591 return ('7.1', '7.0a') 592 elif self.vc_ver == 11.0: 593 return ('8.0', '8.0a') 594 elif self.vc_ver == 12.0: 595 return ('8.1', '8.1a') 596 elif self.vc_ver >= 14.0: 597 return ('10.0', '8.1') 598 599 @property 600 def WindowsSdkLastVersion(self): 601 """ 602 Microsoft Windows SDK last version 603 """ 604 return self._use_last_dir_name(os.path.join( 605 self.WindowsSdkDir, 'lib')) 606 607 @property 608 def WindowsSdkDir(self): 609 """ 610 Microsoft Windows SDK directory. 611 """ 612 sdkdir = '' 613 for ver in self.WindowsSdkVersion: 614 # Try to get it from registry 615 loc = os.path.join(self.ri.windows_sdk, 'v%s' % ver) 616 sdkdir = self.ri.lookup(loc, 'installationfolder') 617 if sdkdir: 618 break 619 if not sdkdir or not os.path.isdir(sdkdir): 620 # Try to get "VC++ for Python" version from registry 621 path = os.path.join(self.ri.vc_for_python, '%0.1f' % self.vc_ver) 622 install_base = self.ri.lookup(path, 'installdir') 623 if install_base: 624 sdkdir = os.path.join(install_base, 'WinSDK') 625 if not sdkdir or not os.path.isdir(sdkdir): 626 # If fail, use default new path 627 for ver in self.WindowsSdkVersion: 628 intver = ver[:ver.rfind('.')] 629 path = r'Microsoft SDKs\Windows Kits\%s' % (intver) 630 d = os.path.join(self.ProgramFiles, path) 631 if os.path.isdir(d): 632 sdkdir = d 633 if not sdkdir or not os.path.isdir(sdkdir): 634 # If fail, use default old path 635 for ver in self.WindowsSdkVersion: 636 path = r'Microsoft SDKs\Windows\v%s' % ver 637 d = os.path.join(self.ProgramFiles, path) 638 if os.path.isdir(d): 639 sdkdir = d 640 if not sdkdir: 641 # If fail, use Platform SDK 642 sdkdir = os.path.join(self.VCInstallDir, 'PlatformSDK') 643 return sdkdir 644 645 @property 646 def WindowsSDKExecutablePath(self): 647 """ 648 Microsoft Windows SDK executable directory. 649 """ 650 # Find WinSDK NetFx Tools registry dir name 651 if self.vc_ver <= 11.0: 652 netfxver = 35 653 arch = '' 654 else: 655 netfxver = 40 656 hidex86 = True if self.vc_ver <= 12.0 else False 657 arch = self.pi.current_dir(x64=True, hidex86=hidex86) 658 fx = 'WinSDK-NetFx%dTools%s' % (netfxver, arch.replace('\\', '-')) 659 660 # liste all possibles registry paths 661 regpaths = [] 662 if self.vc_ver >= 14.0: 663 for ver in self.NetFxSdkVersion: 664 regpaths += [os.path.join(self.ri.netfx_sdk, ver, fx)] 665 666 for ver in self.WindowsSdkVersion: 667 regpaths += [os.path.join(self.ri.windows_sdk, 'v%sA' % ver, fx)] 668 669 # Return installation folder from the more recent path 670 for path in regpaths: 671 execpath = self.ri.lookup(path, 'installationfolder') 672 if execpath: 673 break 674 return execpath 675 676 @property 677 def FSharpInstallDir(self): 678 """ 679 Microsoft Visual F# directory. 680 """ 681 path = r'%0.1f\Setup\F#' % self.vc_ver 682 path = os.path.join(self.ri.visualstudio, path) 683 return self.ri.lookup(path, 'productdir') or '' 684 685 @property 686 def UniversalCRTSdkDir(self): 687 """ 688 Microsoft Universal CRT SDK directory. 689 """ 690 # Set Kit Roots versions for specified MSVC++ version 691 if self.vc_ver >= 14.0: 692 vers = ('10', '81') 693 else: 694 vers = () 695 696 # Find path of the more recent Kit 697 for ver in vers: 698 sdkdir = self.ri.lookup(self.ri.windows_kits_roots, 699 'kitsroot%s' % ver) 700 if sdkdir: 701 break 702 return sdkdir or '' 703 704 @property 705 def UniversalCRTSdkLastVersion(self): 706 """ 707 Microsoft Universal C Runtime SDK last version 708 """ 709 return self._use_last_dir_name(os.path.join( 710 self.UniversalCRTSdkDir, 'lib')) 711 712 @property 713 def NetFxSdkVersion(self): 714 """ 715 Microsoft .NET Framework SDK versions. 716 """ 717 # Set FxSdk versions for specified MSVC++ version 718 if self.vc_ver >= 14.0: 719 return ('4.6.1', '4.6') 720 else: 721 return () 722 723 @property 724 def NetFxSdkDir(self): 725 """ 726 Microsoft .NET Framework SDK directory. 727 """ 728 for ver in self.NetFxSdkVersion: 729 loc = os.path.join(self.ri.netfx_sdk, ver) 730 sdkdir = self.ri.lookup(loc, 'kitsinstallationfolder') 731 if sdkdir: 732 break 733 return sdkdir or '' 734 735 @property 736 def FrameworkDir32(self): 737 """ 738 Microsoft .NET Framework 32bit directory. 739 """ 740 # Default path 741 guess_fw = os.path.join(self.WinDir, r'Microsoft.NET\Framework') 742 743 # Try to get path from registry, if fail use default path 744 return self.ri.lookup(self.ri.vc, 'frameworkdir32') or guess_fw 745 746 @property 747 def FrameworkDir64(self): 748 """ 749 Microsoft .NET Framework 64bit directory. 750 """ 751 # Default path 752 guess_fw = os.path.join(self.WinDir, r'Microsoft.NET\Framework64') 753 754 # Try to get path from registry, if fail use default path 755 return self.ri.lookup(self.ri.vc, 'frameworkdir64') or guess_fw 756 757 @property 758 def FrameworkVersion32(self): 759 """ 760 Microsoft .NET Framework 32bit versions. 761 """ 762 return self._find_dot_net_versions(32) 763 764 @property 765 def FrameworkVersion64(self): 766 """ 767 Microsoft .NET Framework 64bit versions. 768 """ 769 return self._find_dot_net_versions(64) 770 771 def _find_dot_net_versions(self, bits): 772 """ 773 Find Microsoft .NET Framework versions. 774 775 Parameters 776 ---------- 777 bits: int 778 Platform number of bits: 32 or 64. 779 """ 780 # Find actual .NET version in registry 781 reg_ver = self.ri.lookup(self.ri.vc, 'frameworkver%d' % bits) 782 dot_net_dir = getattr(self, 'FrameworkDir%d' % bits) 783 ver = reg_ver or self._use_last_dir_name(dot_net_dir, 'v') or '' 784 785 # Set .NET versions for specified MSVC++ version 786 if self.vc_ver >= 12.0: 787 frameworkver = (ver, 'v4.0') 788 elif self.vc_ver >= 10.0: 789 frameworkver = ('v4.0.30319' if ver.lower()[:2] != 'v4' else ver, 790 'v3.5') 791 elif self.vc_ver == 9.0: 792 frameworkver = ('v3.5', 'v2.0.50727') 793 if self.vc_ver == 8.0: 794 frameworkver = ('v3.0', 'v2.0.50727') 795 return frameworkver 796 797 def _use_last_dir_name(self, path, prefix=''): 798 """ 799 Return name of the last dir in path or '' if no dir found. 800 801 Parameters 802 ---------- 803 path: str 804 Use dirs in this path 805 prefix: str 806 Use only dirs startings by this prefix 807 """ 808 matching_dirs = ( 809 dir_name 810 for dir_name in reversed(os.listdir(path)) 811 if os.path.isdir(os.path.join(path, dir_name)) and 812 dir_name.startswith(prefix) 813 ) 814 return next(matching_dirs, None) or '' 815 816 817class EnvironmentInfo: 818 """ 819 Return environment variables for specified Microsoft Visual C++ version 820 and platform : Lib, Include, Path and libpath. 821 822 This function is compatible with Microsoft Visual C++ 9.0 to 14.0. 823 824 Script created by analysing Microsoft environment configuration files like 825 "vcvars[...].bat", "SetEnv.Cmd", "vcbuildtools.bat", ... 826 827 Parameters 828 ---------- 829 arch: str 830 Target architecture. 831 vc_ver: float 832 Required Microsoft Visual C++ version. If not set, autodetect the last 833 version. 834 vc_min_ver: float 835 Minimum Microsoft Visual C++ version. 836 """ 837 838 # Variables and properties in this class use originals CamelCase variables 839 # names from Microsoft source files for more easy comparaison. 840 841 def __init__(self, arch, vc_ver=None, vc_min_ver=0): 842 self.pi = PlatformInfo(arch) 843 self.ri = RegistryInfo(self.pi) 844 self.si = SystemInfo(self.ri, vc_ver) 845 846 if self.vc_ver < vc_min_ver: 847 err = 'No suitable Microsoft Visual C++ version found' 848 raise distutils.errors.DistutilsPlatformError(err) 849 850 @property 851 def vc_ver(self): 852 """ 853 Microsoft Visual C++ version. 854 """ 855 return self.si.vc_ver 856 857 @property 858 def VSTools(self): 859 """ 860 Microsoft Visual Studio Tools 861 """ 862 paths = [r'Common7\IDE', r'Common7\Tools'] 863 864 if self.vc_ver >= 14.0: 865 arch_subdir = self.pi.current_dir(hidex86=True, x64=True) 866 paths += [r'Common7\IDE\CommonExtensions\Microsoft\TestWindow'] 867 paths += [r'Team Tools\Performance Tools'] 868 paths += [r'Team Tools\Performance Tools%s' % arch_subdir] 869 870 return [os.path.join(self.si.VSInstallDir, path) for path in paths] 871 872 @property 873 def VCIncludes(self): 874 """ 875 Microsoft Visual C++ & Microsoft Foundation Class Includes 876 """ 877 return [os.path.join(self.si.VCInstallDir, 'Include'), 878 os.path.join(self.si.VCInstallDir, r'ATLMFC\Include')] 879 880 @property 881 def VCLibraries(self): 882 """ 883 Microsoft Visual C++ & Microsoft Foundation Class Libraries 884 """ 885 if self.vc_ver >= 15.0: 886 arch_subdir = self.pi.target_dir(x64=True) 887 else: 888 arch_subdir = self.pi.target_dir(hidex86=True) 889 paths = ['Lib%s' % arch_subdir, r'ATLMFC\Lib%s' % arch_subdir] 890 891 if self.vc_ver >= 14.0: 892 paths += [r'Lib\store%s' % arch_subdir] 893 894 return [os.path.join(self.si.VCInstallDir, path) for path in paths] 895 896 @property 897 def VCStoreRefs(self): 898 """ 899 Microsoft Visual C++ store references Libraries 900 """ 901 if self.vc_ver < 14.0: 902 return [] 903 return [os.path.join(self.si.VCInstallDir, r'Lib\store\references')] 904 905 @property 906 def VCTools(self): 907 """ 908 Microsoft Visual C++ Tools 909 """ 910 si = self.si 911 tools = [os.path.join(si.VCInstallDir, 'VCPackages')] 912 913 forcex86 = True if self.vc_ver <= 10.0 else False 914 arch_subdir = self.pi.cross_dir(forcex86) 915 if arch_subdir: 916 tools += [os.path.join(si.VCInstallDir, 'Bin%s' % arch_subdir)] 917 918 if self.vc_ver == 14.0: 919 path = 'Bin%s' % self.pi.current_dir(hidex86=True) 920 tools += [os.path.join(si.VCInstallDir, path)] 921 922 elif self.vc_ver >= 15.0: 923 host_dir = (r'bin\HostX86%s' if self.pi.current_is_x86() else 924 r'bin\HostX64%s') 925 tools += [os.path.join( 926 si.VCInstallDir, host_dir % self.pi.target_dir(x64=True))] 927 928 if self.pi.current_cpu != self.pi.target_cpu: 929 tools += [os.path.join( 930 si.VCInstallDir, host_dir % self.pi.current_dir(x64=True))] 931 932 else: 933 tools += [os.path.join(si.VCInstallDir, 'Bin')] 934 935 return tools 936 937 @property 938 def OSLibraries(self): 939 """ 940 Microsoft Windows SDK Libraries 941 """ 942 if self.vc_ver <= 10.0: 943 arch_subdir = self.pi.target_dir(hidex86=True, x64=True) 944 return [os.path.join(self.si.WindowsSdkDir, 'Lib%s' % arch_subdir)] 945 946 else: 947 arch_subdir = self.pi.target_dir(x64=True) 948 lib = os.path.join(self.si.WindowsSdkDir, 'lib') 949 libver = self._sdk_subdir 950 return [os.path.join(lib, '%sum%s' % (libver , arch_subdir))] 951 952 @property 953 def OSIncludes(self): 954 """ 955 Microsoft Windows SDK Include 956 """ 957 include = os.path.join(self.si.WindowsSdkDir, 'include') 958 959 if self.vc_ver <= 10.0: 960 return [include, os.path.join(include, 'gl')] 961 962 else: 963 if self.vc_ver >= 14.0: 964 sdkver = self._sdk_subdir 965 else: 966 sdkver = '' 967 return [os.path.join(include, '%sshared' % sdkver), 968 os.path.join(include, '%sum' % sdkver), 969 os.path.join(include, '%swinrt' % sdkver)] 970 971 @property 972 def OSLibpath(self): 973 """ 974 Microsoft Windows SDK Libraries Paths 975 """ 976 ref = os.path.join(self.si.WindowsSdkDir, 'References') 977 libpath = [] 978 979 if self.vc_ver <= 9.0: 980 libpath += self.OSLibraries 981 982 if self.vc_ver >= 11.0: 983 libpath += [os.path.join(ref, r'CommonConfiguration\Neutral')] 984 985 if self.vc_ver >= 14.0: 986 libpath += [ 987 ref, 988 os.path.join(self.si.WindowsSdkDir, 'UnionMetadata'), 989 os.path.join( 990 ref, 991 'Windows.Foundation.UniversalApiContract', 992 '1.0.0.0', 993 ), 994 os.path.join( 995 ref, 996 'Windows.Foundation.FoundationContract', 997 '1.0.0.0', 998 ), 999 os.path.join( 1000 ref, 1001 'Windows.Networking.Connectivity.WwanContract', 1002 '1.0.0.0', 1003 ), 1004 os.path.join( 1005 self.si.WindowsSdkDir, 1006 'ExtensionSDKs', 1007 'Microsoft.VCLibs', 1008 '%0.1f' % self.vc_ver, 1009 'References', 1010 'CommonConfiguration', 1011 'neutral', 1012 ), 1013 ] 1014 return libpath 1015 1016 @property 1017 def SdkTools(self): 1018 """ 1019 Microsoft Windows SDK Tools 1020 """ 1021 return list(self._sdk_tools()) 1022 1023 def _sdk_tools(self): 1024 """ 1025 Microsoft Windows SDK Tools paths generator 1026 """ 1027 if self.vc_ver < 15.0: 1028 bin_dir = 'Bin' if self.vc_ver <= 11.0 else r'Bin\x86' 1029 yield os.path.join(self.si.WindowsSdkDir, bin_dir) 1030 1031 if not self.pi.current_is_x86(): 1032 arch_subdir = self.pi.current_dir(x64=True) 1033 path = 'Bin%s' % arch_subdir 1034 yield os.path.join(self.si.WindowsSdkDir, path) 1035 1036 if self.vc_ver == 10.0 or self.vc_ver == 11.0: 1037 if self.pi.target_is_x86(): 1038 arch_subdir = '' 1039 else: 1040 arch_subdir = self.pi.current_dir(hidex86=True, x64=True) 1041 path = r'Bin\NETFX 4.0 Tools%s' % arch_subdir 1042 yield os.path.join(self.si.WindowsSdkDir, path) 1043 1044 elif self.vc_ver >= 15.0: 1045 path = os.path.join(self.si.WindowsSdkDir, 'Bin') 1046 arch_subdir = self.pi.current_dir(x64=True) 1047 sdkver = self.si.WindowsSdkLastVersion 1048 yield os.path.join(path, '%s%s' % (sdkver, arch_subdir)) 1049 1050 if self.si.WindowsSDKExecutablePath: 1051 yield self.si.WindowsSDKExecutablePath 1052 1053 @property 1054 def _sdk_subdir(self): 1055 """ 1056 Microsoft Windows SDK version subdir 1057 """ 1058 ucrtver = self.si.WindowsSdkLastVersion 1059 return ('%s\\' % ucrtver) if ucrtver else '' 1060 1061 @property 1062 def SdkSetup(self): 1063 """ 1064 Microsoft Windows SDK Setup 1065 """ 1066 if self.vc_ver > 9.0: 1067 return [] 1068 1069 return [os.path.join(self.si.WindowsSdkDir, 'Setup')] 1070 1071 @property 1072 def FxTools(self): 1073 """ 1074 Microsoft .NET Framework Tools 1075 """ 1076 pi = self.pi 1077 si = self.si 1078 1079 if self.vc_ver <= 10.0: 1080 include32 = True 1081 include64 = not pi.target_is_x86() and not pi.current_is_x86() 1082 else: 1083 include32 = pi.target_is_x86() or pi.current_is_x86() 1084 include64 = pi.current_cpu == 'amd64' or pi.target_cpu == 'amd64' 1085 1086 tools = [] 1087 if include32: 1088 tools += [os.path.join(si.FrameworkDir32, ver) 1089 for ver in si.FrameworkVersion32] 1090 if include64: 1091 tools += [os.path.join(si.FrameworkDir64, ver) 1092 for ver in si.FrameworkVersion64] 1093 return tools 1094 1095 @property 1096 def NetFxSDKLibraries(self): 1097 """ 1098 Microsoft .Net Framework SDK Libraries 1099 """ 1100 if self.vc_ver < 14.0 or not self.si.NetFxSdkDir: 1101 return [] 1102 1103 arch_subdir = self.pi.target_dir(x64=True) 1104 return [os.path.join(self.si.NetFxSdkDir, r'lib\um%s' % arch_subdir)] 1105 1106 @property 1107 def NetFxSDKIncludes(self): 1108 """ 1109 Microsoft .Net Framework SDK Includes 1110 """ 1111 if self.vc_ver < 14.0 or not self.si.NetFxSdkDir: 1112 return [] 1113 1114 return [os.path.join(self.si.NetFxSdkDir, r'include\um')] 1115 1116 @property 1117 def VsTDb(self): 1118 """ 1119 Microsoft Visual Studio Team System Database 1120 """ 1121 return [os.path.join(self.si.VSInstallDir, r'VSTSDB\Deploy')] 1122 1123 @property 1124 def MSBuild(self): 1125 """ 1126 Microsoft Build Engine 1127 """ 1128 if self.vc_ver < 12.0: 1129 return [] 1130 elif self.vc_ver < 15.0: 1131 base_path = self.si.ProgramFilesx86 1132 arch_subdir = self.pi.current_dir(hidex86=True) 1133 else: 1134 base_path = self.si.VSInstallDir 1135 arch_subdir = '' 1136 1137 path = r'MSBuild\%0.1f\bin%s' % (self.vc_ver, arch_subdir) 1138 build = [os.path.join(base_path, path)] 1139 1140 if self.vc_ver >= 15.0: 1141 # Add Roslyn C# & Visual Basic Compiler 1142 build += [os.path.join(base_path, path, 'Roslyn')] 1143 1144 return build 1145 1146 @property 1147 def HTMLHelpWorkshop(self): 1148 """ 1149 Microsoft HTML Help Workshop 1150 """ 1151 if self.vc_ver < 11.0: 1152 return [] 1153 1154 return [os.path.join(self.si.ProgramFilesx86, 'HTML Help Workshop')] 1155 1156 @property 1157 def UCRTLibraries(self): 1158 """ 1159 Microsoft Universal C Runtime SDK Libraries 1160 """ 1161 if self.vc_ver < 14.0: 1162 return [] 1163 1164 arch_subdir = self.pi.target_dir(x64=True) 1165 lib = os.path.join(self.si.UniversalCRTSdkDir, 'lib') 1166 ucrtver = self._ucrt_subdir 1167 return [os.path.join(lib, '%sucrt%s' % (ucrtver, arch_subdir))] 1168 1169 @property 1170 def UCRTIncludes(self): 1171 """ 1172 Microsoft Universal C Runtime SDK Include 1173 """ 1174 if self.vc_ver < 14.0: 1175 return [] 1176 1177 include = os.path.join(self.si.UniversalCRTSdkDir, 'include') 1178 return [os.path.join(include, '%sucrt' % self._ucrt_subdir)] 1179 1180 @property 1181 def _ucrt_subdir(self): 1182 """ 1183 Microsoft Universal C Runtime SDK version subdir 1184 """ 1185 ucrtver = self.si.UniversalCRTSdkLastVersion 1186 return ('%s\\' % ucrtver) if ucrtver else '' 1187 1188 @property 1189 def FSharp(self): 1190 """ 1191 Microsoft Visual F# 1192 """ 1193 if self.vc_ver < 11.0 and self.vc_ver > 12.0: 1194 return [] 1195 1196 return self.si.FSharpInstallDir 1197 1198 @property 1199 def VCRuntimeRedist(self): 1200 """ 1201 Microsoft Visual C++ runtime redistribuable dll 1202 """ 1203 arch_subdir = self.pi.target_dir(x64=True) 1204 if self.vc_ver < 15: 1205 redist_path = self.si.VCInstallDir 1206 vcruntime = 'redist%s\\Microsoft.VC%d0.CRT\\vcruntime%d0.dll' 1207 else: 1208 redist_path = self.si.VCInstallDir.replace('\\Tools', '\\Redist') 1209 vcruntime = 'onecore%s\\Microsoft.VC%d0.CRT\\vcruntime%d0.dll' 1210 1211 # Visual Studio 2017 is still Visual C++ 14.0 1212 dll_ver = 14.0 if self.vc_ver == 15 else self.vc_ver 1213 1214 vcruntime = vcruntime % (arch_subdir, self.vc_ver, dll_ver) 1215 return os.path.join(redist_path, vcruntime) 1216 1217 def return_env(self, exists=True): 1218 """ 1219 Return environment dict. 1220 1221 Parameters 1222 ---------- 1223 exists: bool 1224 It True, only return existing paths. 1225 """ 1226 env = dict( 1227 include=self._build_paths('include', 1228 [self.VCIncludes, 1229 self.OSIncludes, 1230 self.UCRTIncludes, 1231 self.NetFxSDKIncludes], 1232 exists), 1233 lib=self._build_paths('lib', 1234 [self.VCLibraries, 1235 self.OSLibraries, 1236 self.FxTools, 1237 self.UCRTLibraries, 1238 self.NetFxSDKLibraries], 1239 exists), 1240 libpath=self._build_paths('libpath', 1241 [self.VCLibraries, 1242 self.FxTools, 1243 self.VCStoreRefs, 1244 self.OSLibpath], 1245 exists), 1246 path=self._build_paths('path', 1247 [self.VCTools, 1248 self.VSTools, 1249 self.VsTDb, 1250 self.SdkTools, 1251 self.SdkSetup, 1252 self.FxTools, 1253 self.MSBuild, 1254 self.HTMLHelpWorkshop, 1255 self.FSharp], 1256 exists), 1257 ) 1258 if self.vc_ver >= 14 and os.path.isfile(self.VCRuntimeRedist): 1259 env['py_vcruntime_redist'] = self.VCRuntimeRedist 1260 return env 1261 1262 def _build_paths(self, name, spec_path_lists, exists): 1263 """ 1264 Given an environment variable name and specified paths, 1265 return a pathsep-separated string of paths containing 1266 unique, extant, directories from those paths and from 1267 the environment variable. Raise an error if no paths 1268 are resolved. 1269 """ 1270 # flatten spec_path_lists 1271 spec_paths = itertools.chain.from_iterable(spec_path_lists) 1272 env_paths = safe_env.get(name, '').split(os.pathsep) 1273 paths = itertools.chain(spec_paths, env_paths) 1274 extant_paths = list(filter(os.path.isdir, paths)) if exists else paths 1275 if not extant_paths: 1276 msg = "%s environment variable is empty" % name.upper() 1277 raise distutils.errors.DistutilsPlatformError(msg) 1278 unique_paths = self._unique_everseen(extant_paths) 1279 return os.pathsep.join(unique_paths) 1280 1281 # from Python docs 1282 def _unique_everseen(self, iterable, key=None): 1283 """ 1284 List unique elements, preserving order. 1285 Remember all elements ever seen. 1286 1287 _unique_everseen('AAAABBBCCDAABBB') --> A B C D 1288 1289 _unique_everseen('ABBCcAD', str.lower) --> A B C D 1290 """ 1291 seen = set() 1292 seen_add = seen.add 1293 if key is None: 1294 for element in filterfalse(seen.__contains__, iterable): 1295 seen_add(element) 1296 yield element 1297 else: 1298 for element in iterable: 1299 k = key(element) 1300 if k not in seen: 1301 seen_add(k) 1302 yield element 1303